Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,21 @@ There are also CodeceptJS commands available:
- I.see - ...
... etc (all codeceptjs commands)

## Remote log destination (`src/remote.ts`)

`--ws <url>` (or `EXPLORBOT_WS_URL`) streams a run to a host UI over one WebSocket. Explorbot dials **out**, so the same code covers a child process the host spawned and a CI bot connecting from elsewhere.

One class in one file, and nothing in explorbot knows about it. `remote` (the singleton in `src/remote.ts`) plugs into the two extension points that already exist:

- `addDestination()` on the logger — `Remote` **implements `LogDestination`**, so it registers *itself* alongside the console and the file. It gets the entry the logger already built, so the `LogType` reaches the UI as the frame's `level` and each kind is styled there instead of scraped out of flattened text. `html` is dropped, ANSI stripped, content capped.
- `executionController.setInputCallback()` — every ask in the codebase already goes through the controller, so the host answers the Pilot's `askUser`, the Navigator's login prompt and `drill_ask` without any of them knowing where the answer came from. Installing it is also what stops a TTY-less child hanging on the readline fallback.

Consequently `isInteractive()` (`src/ai/task-agent.ts`) is **`INK_RUNNING || executionController.hasInputCallback()`** — "somebody can answer", asked of the controller rather than of any particular front end.

**There are no frame types.** A frame is `{type, ts, ...whatever}`; `send(type, data)` puts data on the wire and the UI renders what it recognises. Neither side validates the other's shape, so either can start sending more at any time. It queues while disconnected, reconnects with backoff, and `remote.close(exitCode)` flushes before exit (called from `showStatsAndExit`).

`remote.registerOption(program)` adds the flag through a Commander `preAction` hook, so it covers every command including the mounted `api`/`docs` subcommands and the standalone `boat/*` bins.

## Command Line Usage

Explorbot uses the `explorbot` CLI command (defined in `bin/explorbot-cli.ts`):
Expand Down
6 changes: 6 additions & 0 deletions bin/explorbot-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { App } from '../src/components/App.js';
import { StatusPane } from '../src/components/StatusPane.js';
import { ConfigParser, EXPLORBOT_ENV_VARS, PROVIDERS } from '../src/config.js';
import { ExplorBot, type ExplorBotOptions } from '../src/explorbot.js';
import { remote } from '../src/remote.js';
import { Stats } from '../src/stats.js';
import { Plan } from '../src/test-plan.js';
import { getCliName } from '../src/utils/cli-name.ts';
Expand All @@ -26,6 +27,7 @@ const pkgPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../p
const pkgVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version as string;

program.name(cli).description('AI-powered web exploration tool').version(pkgVersion, '-V, --version');
remote.registerOption(program);

if (!process.env.EXPLORBOT_NO_BANNER) {
console.log(`⛵ ${chalk.yellow.bold(`Explorbot v${pkgVersion}`)} ${chalk.dim('Autonomous Testing Agent')}`);
Expand Down Expand Up @@ -99,6 +101,10 @@ async function startTUI(explorBot: ExplorBot): Promise<void> {
}

async function showStatsAndExit(code: number): Promise<never> {
if (remote.isAttached()) {
await remote.close(code);
process.exit(code);
}
if (Stats.hasActivity()) {
await new Promise<void>((resolve) => {
const { unmount } = render(
Expand Down
2 changes: 2 additions & 0 deletions boat/api-tester/bin/apibot-cli.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/usr/bin/env bun
import { remote } from '../../../src/remote.ts';
import { createApiCommands } from '../src/cli.ts';

const program = createApiCommands('apibot');
remote.registerOption(program);
program.parse();
2 changes: 2 additions & 0 deletions boat/doc-collector/bin/doc-collector-cli.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/usr/bin/env bun
import { remote } from '../../../src/remote.ts';
import { createDocsCommands } from '../src/cli.ts';

const program = createDocsCommands('doc-collector');
remote.registerOption(program);
program.parse();
Loading
Loading