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
6 changes: 5 additions & 1 deletion .github/workflows/windows-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,12 @@ jobs:
$logPath = "$env:GITHUB_WORKSPACE\cli.log"
$errPath = "$env:GITHUB_WORKSPACE\cli.err.log"

# --no-agent is required as of 0.2.1: bare `agentmind-cli` now
# launches Claude Code, which would fail immediately on a
# runner with no stdin/no claude binary. We're testing the
# static-asset / capture-API endpoints here, not the launcher.
$proc = Start-Process node `
-ArgumentList 'bin/cli.js','--port','8189','--no-open' `
-ArgumentList 'bin/cli.js','--port','8189','--no-agent','--no-open' `
-PassThru `
-NoNewWindow `
-RedirectStandardOutput $logPath `
Expand Down
62 changes: 33 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,16 +51,16 @@ npm install -g agentmind-cli

# Launch your agent through AgentMind — one command, zero config,
# no API keys required.
agentmind-cli claude # or: agentmind-cli claude "fix the build"
agentmind-cli # defaults to Claude Code
agentmind-cli codex # or: agentmind-cli codex exec "ship the PR"
```

That's it. AgentMind boots the dashboard, opens it in your browser,
injects the right env / config so your agent talks to the proxy, and
hands the terminal off to the agent's TUI. Every prompt you type shows
up in the dashboard as a new project — live, while the agent is still
streaming. When the agent exits the dashboard keeps running so you can
browse the captured trace. Hit Ctrl+C to stop AgentMind.
That's it. AgentMind boots the dashboard, injects the right env / config
so your agent talks to the proxy, and hands the terminal off to the
agent's TUI. Every prompt you type shows up in the dashboard as a new
project — live, while the agent is still streaming. When the agent
exits the dashboard keeps running so you can browse the captured trace.
Hit Ctrl+C to stop AgentMind.

> Both agents work with their **existing logins** — `claude login` for
> Claude Code, `codex login` for Codex CLI. No API key wrangling. The
Expand All @@ -73,38 +73,41 @@ browse the captured trace. Hit Ctrl+C to stop AgentMind.
Prefer `npx`?

```bash
npx agentmind-cli # claude by default
npx agentmind-cli codex
```

### Just want the dashboard?

```bash
agentmind-cli # dashboard on http://127.0.0.1:8088
agentmind-cli --port 9090 # custom port
agentmind-cli --no-open # don't auto-open the browser
agentmind-cli --no-agent # dashboard on http://127.0.0.1:8088, no agent
agentmind-cli --no-agent --port 9090 # custom port
agentmind-cli --no-agent --no-open # don't auto-open the browser
```

Then start your agent yourself — see [Manual setup](#manual-setup) below.

**Supported agents** (v0.2):

| Agent | Upstream | Proxy endpoint we capture | Launcher |
| ---------- | ----------------------- | ------------------------- | ------------------ |
| Claude Code| `api.anthropic.com` | `POST /v1/messages` | `agentmind-cli claude` |
| Codex CLI | `api.openai.com` | `POST /v1/responses` | `agentmind-cli codex` |
| Agent | Upstream | Proxy endpoint we capture | Launcher |
| ---------- | ----------------------- | ------------------------- | ----------------------------------- |
| Claude Code| `api.anthropic.com` | `POST /v1/messages` | `agentmind-cli` (or `… claude`) |
| Codex CLI | `api.openai.com` | `POST /v1/responses` | `agentmind-cli codex` |

Both agents land in the same cwd-keyed project (so if you switch between
them in one directory, you see both threads against the same workspace).
The sidebar tags non-default agents with a small `codex` chip so a mixed
inbox stays scannable.
Projects are keyed by `(cwd, agent)` — running both Claude and Codex in
the same directory produces two distinct projects so each one's
conversation chain stays coherent. The sidebar tags every project with
a small agent chip (Claude = peach, Codex = lavender) so a mixed inbox
stays scannable at a glance.

Flags (must come BEFORE the subcommand):

| Flag | Default | Notes |
| ---------------- | ------------- | ------------------------------------------ |
| `--port <n>` | `8088` | Listen port (and the URL we hand the agent) |
| `--data <dir>` | `~/.agentmind` | Where the JSONL projects live |
| `--no-open` | _off_ | Skip the auto-open browser step |
| Flag | Default | Notes |
| ---------------- | ------------- | ------------------------------------------------------ |
| `--port <n>` | `8088` | Listen port (and the URL we hand the agent) |
| `--data <dir>` | `~/.agentmind` | Where the JSONL projects live |
| `--no-agent` | _off_ | Skip launching an agent — dashboard only |
| `--no-open` | _off_ | Skip the auto-open browser step (`--no-agent` only) |

Run `agentmind-cli --help` for the full reference.

Expand Down Expand Up @@ -145,12 +148,13 @@ All capture is local-first JSONL — never leaves your machine.
```
~/.agentmind/
└── projects/
└── <projectId>.jsonl # projectId = sha256(cwd).slice(0,16)
└── <projectId>.jsonl # projectId = sha256(cwd, agent).slice(0,16)
```

One file per cwd — every `claude` run in the same directory, across
days and proxy restarts, appends to the same file. Each line is one
JSON record:
One file per `(cwd, agent)` pair — every `claude` run in the same
directory appends to the Claude file for that cwd, every `codex` run
to the Codex file. Pre-0.2.2 single-cwd files are migrated to the new
scheme on first boot. Each line is one JSON record:

| `type` | When written | Purpose |
| ------------- | ----------------------------------------- | -------------------------------- |
Expand Down Expand Up @@ -188,8 +192,8 @@ Adding a third protocol is one `ProtocolAdapter` away — see

## Manual setup

You don't have to use the launchers — `agentmind-cli` (no subcommand)
just runs the dashboard, and you can point any agent at it yourself.
You don't have to use the launchers — `agentmind-cli --no-agent`
runs the dashboard alone, and you can point any agent at it yourself.

### Claude Code

Expand Down
92 changes: 63 additions & 29 deletions bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,36 @@
// a dist/ — i.e. `pnpm dev` was the developer's intent)
// Spawns `vite dev` so HMR/typecheck work while iterating.
//
// And two invocation shapes:
// And three invocation shapes:
//
// a. Dashboard-only: `agentmind-cli [flags...]`
// Boots the proxy and opens the browser. User wires their agent
// manually (or already has it pointed at us).
// a. Default: `agentmind-cli [flags...] [-- agent-args...]`
// Equivalent to `agentmind-cli claude`. Claude Code is the
// assumed agent because it's the one most users came here for —
// AgentMind is a Claude-first product that also happens to
// capture Codex. Any positional args after `--` are forwarded to
// claude verbatim.
//
// b. Launcher: `agentmind-cli {claude|codex} [agent-args...]`
// Boots the proxy AND spawns the named agent in the foreground
// with the right env/config overrides so it talks to us. The
// dashboard stays running after the agent exits so the captured
// trace stays browsable until the user hits Ctrl+C.
// b. Explicit launcher:
// `agentmind-cli {claude|codex} [agent-args...]`
// Same flow, but with the agent named on the command line.
// Pick this when you want Codex (default isn't codex) or when
// you want the choice to be obvious to a reader.
//
// c. Dashboard-only: `agentmind-cli --no-agent [flags...]`
// Boots the proxy + UI without launching an agent. Useful when
// you want to browse the captured trace later (your previous
// session already exited) or when you'd rather wire the agent
// up yourself.
//
// AgentMind flags must come BEFORE the subcommand. Anything after the
// subcommand (including `--`-prefixed args) is forwarded verbatim to
// the agent. Examples:
//
// agentmind-cli # dashboard only
// agentmind-cli --port 9090 # dashboard on custom port
// agentmind-cli codex # dashboard + codex
// agentmind-cli # = agentmind-cli claude
// agentmind-cli --port 9090 # claude on custom port
// agentmind-cli codex # launch codex instead
// agentmind-cli codex "fix the build" # passes the prompt through
// agentmind-cli --no-agent # dashboard only
// agentmind-cli --port 9090 claude exec --task "ship"

import { spawn } from 'node:child_process'
Expand Down Expand Up @@ -85,6 +95,7 @@ function getFlagValue(args, name, fallback) {

const wantsHelp = getFlag(preArgs, '--help') || getFlag(preArgs, '-h')
const noOpen = getFlag(preArgs, '--no-open')
const noAgent = getFlag(preArgs, '--no-agent')
const forceDev = getFlag(preArgs, '--dev')
const portArg = getFlagValue(preArgs, '--port', '8088')
const dataArg = getFlagValue(preArgs, '--data', undefined)
Expand All @@ -94,26 +105,32 @@ if (wantsHelp) {
agentmind-cli — a live window into your agent's mind

USAGE:
agentmind-cli start dashboard, open browser
agentmind-cli codex [args...] start dashboard + launch codex
agentmind-cli claude [args...] start dashboard + launch claude
agentmind-cli start dashboard + launch claude (default)
agentmind-cli claude [args...] same, explicit
agentmind-cli codex [args...] start dashboard + launch codex
agentmind-cli --no-agent dashboard only (browse past traces)

OPTIONS (must come BEFORE the subcommand):
--port <n> Listen port (default 8088)
--data <dir> Persist projects under <dir>/projects/<id>.jsonl
(default ~/.agentmind)
--no-open Skip auto-opening the browser (dashboard mode only)
--dev Force vite dev mode (developers only, dashboard only)
--no-open Skip auto-opening the browser (--no-agent only)
--no-agent Don't launch an agent; boot the dashboard alone
--dev Force vite dev mode (developers only, --no-agent only)
-h, --help Show this message

LAUNCHER NOTES:
* \`agentmind-cli codex\` and \`agentmind-cli claude\` inject the right
env / config so the agent talks to AgentMind. No manual setup.
* \`agentmind-cli\` defaults to Claude Code. Use \`codex\` explicitly
to launch Codex CLI instead.
* The launcher injects the right env / config so the agent talks to
AgentMind. No manual setup, no API key wrangling — it reuses your
existing \`claude login\` / \`codex login\`.
* Anything after the subcommand is forwarded verbatim to the agent.
* When the agent exits the dashboard stays running so the captured
trace stays browsable. Press Ctrl+C to stop AgentMind.

MANUAL SETUP (if you'd rather start your own agent):
agentmind-cli --no-agent # then in another terminal:
Claude Code: ANTHROPIC_BASE_URL=http://127.0.0.1:8088 claude
Codex CLI: see README.md "Manual setup" — needs a Codex provider
block with requires_openai_auth=true and
Expand All @@ -135,13 +152,18 @@ const distCli = resolve(projectRoot, 'dist', 'agentmind', 'cli.mjs')
const useProd = !forceDev && existsSync(distCli)

async function maybeOpenBrowser() {
// Only reachable from the --no-agent path. The default `agentmind-cli`
// invocation now launches Claude itself, so there's no "point your
// agent at us" step to print there.
if (noOpen) return
await delay(200)
process.stdout.write(
`\n► Point your agent at agentmind:\n\n` +
` Claude Code:\n` +
` ANTHROPIC_BASE_URL=${url} claude\n\n` +
` Codex CLI: agentmind-cli codex (one-liner — handles config)\n\n`,
`\n► Dashboard-only mode (no agent launched).\n\n` +
` Browse past traces at: ${url}\n` +
` Start a fresh capture session: agentmind-cli claude (or: codex)\n` +
` Wire your agent manually:\n` +
` Claude Code: ANTHROPIC_BASE_URL=${url} claude\n` +
` Codex CLI: see README.md "Manual setup"\n\n`,
)
try {
const { default: open } = await import('open')
Expand Down Expand Up @@ -332,24 +354,36 @@ async function runLauncher(agent, extraArgs) {
}

// ─── Dispatch ────────────────────────────────────────────────────────────

if (subcmd) {
//
// Three branches, picked in this order:
// 1. Launcher: subcommand was given OR no subcommand AND no --no-agent.
// The no-subcommand case fills in `claude` as the default agent —
// that's the 0.2.1 UX shift away from the older dashboard-only
// default. If the user really wants the dashboard alone they pass
// --no-agent (case 2).
// 2. Dashboard-only (prod): --no-agent or a non-launcher invocation
// that has dist/ available.
// 3. Dashboard-only (dev): same as (2) but no dist/ → spawn vite.

const resolvedSubcmd = subcmd ?? (noAgent ? null : 'claude')

if (resolvedSubcmd) {
if (!useProd) {
process.stderr.write(
`agentmind-cli: the \`${subcmd}\` launcher requires a built dist/.\n` +
`agentmind-cli: the \`${resolvedSubcmd}\` launcher requires a built dist/.\n` +
`If you're hacking on agentmind, run \`pnpm build\` first or use\n` +
`\`agentmind-cli\` (no subcommand) for the vite dev dashboard.\n`,
`\`agentmind-cli --no-agent --dev\` for the vite dev dashboard.\n`,
)
process.exit(1)
}
try {
await runLauncher(subcmd, agentArgs)
await runLauncher(resolvedSubcmd, agentArgs)
} catch (err) {
process.stderr.write(`agentmind-cli: launcher failed\n${err?.stack ?? err}\n`)
process.exit(1)
}
} else if (useProd) {
// Production dashboard-only: in-process, no child node, no vite.
// Dashboard-only (prod): in-process, no child node, no vite.
try {
const mod = await import(pathToFileURL(distCli).toString())
await mod.start({ port, host })
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "agentmind-cli",
"version": "0.2.0",
"version": "0.2.1",
"description": "A live window into your agent's mind — watch every thought, tool call and reply your coding agent makes, in real time, locally.",
"license": "MIT",
"type": "module",
Expand Down
7 changes: 7 additions & 0 deletions scripts/smoke-codex.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -275,9 +275,15 @@ log('chatgpt-route stub listening on', CHATGPT_UPSTREAM_PORT)
}

// 2. Boot agentmind-cli pointed at BOTH stubs.
// `--no-agent` is the 0.2.1 way to ask for dashboard-only mode. Without
// it the CLI would try to launch claude, which (a) we don't want here
// — these tests drive the HTTP proxy directly with stub bodies, not a
// real agent — and (b) would fail immediately on a CI runner that has
// no `claude` binary or no stdin attached.
const cli = spawn(process.execPath, [resolve(repo, 'bin', 'cli.js'),
'--port', String(PROXY_PORT),
'--data', dataDir,
'--no-agent',
'--no-open',
], {
cwd: repo,
Expand Down Expand Up @@ -640,6 +646,7 @@ await delay(200)
const cli2 = spawn(process.execPath, [resolve(repo, 'bin', 'cli.js'),
'--port', String(PROXY_PORT),
'--data', dataDir,
'--no-agent',
'--no-open',
], {
cwd: repo,
Expand Down
25 changes: 23 additions & 2 deletions scripts/smoke-launcher.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,15 @@ makeShim('claude')
const cliEntry = join(repo, 'bin', 'cli.js')
const PORT = 18293

async function runOne(agent) {
async function runOne(agent, opts = {}) {
if (existsSync(snapshotPath)) rmSync(snapshotPath)
// When `agent` is `null` we exercise the 0.2.1 default behavior:
// omit the subcommand entirely, expect the CLI to fill in `claude`
// on our behalf and pass no extra positional args through.
const subArgv = agent ? [agent, 'hello'] : []
const child = spawn(
process.execPath,
[cliEntry, '--port', String(PORT), '--data', dataDir, '--no-open', agent, 'hello'],
[cliEntry, '--port', String(PORT), '--data', dataDir, '--no-open', ...subArgv],
{
cwd: repo,
env: {
Expand Down Expand Up @@ -149,6 +153,23 @@ if (claudeSnap.env.ANTHROPIC_BASE_URL !== `http://127.0.0.1:${PORT}`) {
}
log('claude OK — argv forwarded verbatim, ANTHROPIC_BASE_URL injected')

// 0.2.1: `agentmind-cli` with NO subcommand should resolve to the
// claude launcher (replaces the pre-0.2.1 dashboard-only default).
// We pass `null` to runOne to omit the subcommand and `hello` arg
// entirely, then assert the shim was invoked with the env Claude Code
// expects.
log('testing default (no subcommand) launcher...')
const defaultSnap = await runOne(null)
if (defaultSnap.argv.length !== 0) {
throw new Error(`default argv unexpected: ${JSON.stringify(defaultSnap.argv)}`)
}
if (defaultSnap.env.ANTHROPIC_BASE_URL !== `http://127.0.0.1:${PORT}`) {
throw new Error(
`default mode should launch claude, ANTHROPIC_BASE_URL got: ${defaultSnap.env.ANTHROPIC_BASE_URL}`,
)
}
log('default OK — bare `agentmind-cli` launched claude shim')

rmSync(tmpBin, { recursive: true, force: true })
rmSync(dataDir, { recursive: true, force: true })
log('all passed')
Expand Down
Loading