Skip to content

Commit 9fa07ab

Browse files
chore: add shared agent workspace metadata
1 parent 6be82b5 commit 9fa07ab

3 files changed

Lines changed: 245 additions & 0 deletions

File tree

.claude/settings.json

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
{
2+
"$schema": "https://json.schemastore.org/claude-code-settings.json",
3+
"attribution": {
4+
"commit": "",
5+
"pr": ""
6+
},
7+
"permissions": {
8+
"allow": [
9+
"Bash(git *)",
10+
"Bash(uv *)",
11+
"Bash(uv run *)",
12+
"Bash(npm *)",
13+
"Bash(npx *)",
14+
"Bash(pnpm *)",
15+
"Bash(pytest *)",
16+
"Bash(python *)",
17+
"Bash(node *)",
18+
"Bash(docker *)",
19+
"Bash(docker-compose *)",
20+
"Bash(gh *)",
21+
"Bash(make *)",
22+
"Bash(cat *)",
23+
"Bash(ls *)",
24+
"Bash(find *)",
25+
"Bash(grep *)",
26+
"Bash(rg *)",
27+
"Bash(sort *)",
28+
"Bash(head *)",
29+
"Bash(tail *)",
30+
"Bash(wc *)",
31+
"Bash(echo *)",
32+
"Bash(mkdir *)",
33+
"Bash(cp *)",
34+
"Bash(mv *)",
35+
"Bash(pwd)",
36+
"Bash(which *)",
37+
"Bash(black *)",
38+
"Bash(ruff *)",
39+
"Bash(mypy *)",
40+
"Bash(eslint *)",
41+
"Bash(tsc *)",
42+
"Bash(tsc --noEmit *)",
43+
"Read",
44+
"Edit",
45+
"Write",
46+
"WebSearch"
47+
],
48+
"deny": [
49+
"Bash(rm -rf /*)",
50+
"Bash(rm -rf .)",
51+
"Bash(git push * master)",
52+
"Bash(git push * main)",
53+
"Bash(git push --no-verify *)",
54+
"Bash(git checkout master)",
55+
"Bash(git checkout main)",
56+
"Read(./.env)",
57+
"Read(./.env.*)",
58+
"Read(./infra/credentials.md)"
59+
],
60+
"defaultMode": "acceptEdits"
61+
},
62+
"enabledPlugins": {
63+
"superpowers@claude-plugins-official": true
64+
},
65+
"plansDirectory": ".claude/plans",
66+
"statusLine": {
67+
"type": "command",
68+
"command": "bash D:/mcp-comet/.claude/statusline-command.sh"
69+
}
70+
}

.claude/statusline-command.sh

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/usr/bin/env bash
2+
input=$(cat)
3+
4+
cwd=$(echo "$input" | jq -r '.workspace.current_dir // "."')
5+
project_dir=$(echo "$input" | jq -r '.workspace.project_dir // "."')
6+
model=$(echo "$input" | jq -r '.model.display_name // "unknown"')
7+
pct=$(echo "$input" | jq -r '.context_window.used_percentage // 0')
8+
ctx_size=$(echo "$input" | jq -r '.context_window.context_window_size // 200000')
9+
cost=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
10+
duration_ms=$(echo "$input" | jq -r '.cost.total_duration_ms // 0')
11+
12+
REMOTE=$(git -C "$cwd" remote get-url origin 2>/dev/null | sed 's/git@github.com:/https:\/\/github.com\//' | sed 's/\.git$//')
13+
BRANCH=$(git -C "$cwd" branch --show-current 2>/dev/null || echo "")
14+
cost_fmt=$(printf '$%.2f' "$cost")
15+
mins=$((duration_ms / 60000))
16+
secs=$(((duration_ms % 60000) / 1000))
17+
18+
ESC=$'\033'
19+
CYAN="${ESC}[36m"
20+
GREEN="${ESC}[32m"
21+
YELLOW="${ESC}[33m"
22+
RED="${ESC}[31m"
23+
DIM="${ESC}[2m"
24+
RESET="${ESC}[0m"
25+
26+
# RIGA 1
27+
if [ -n "$REMOTE" ]; then
28+
echo "${CYAN}${REMOTE}${RESET} ${DIM}|${RESET} ${GREEN}${BRANCH}${RESET} ${DIM}|${RESET} ${model} ${DIM}|${RESET} ${cost_fmt} ${DIM}|${RESET} ${mins}m ${secs}s"
29+
else
30+
echo "${CYAN}${project_dir##*/}${RESET} ${DIM}|${RESET} ${GREEN}${BRANCH}${RESET} ${DIM}|${RESET} ${model} ${DIM}|${RESET} ${cost_fmt} ${DIM}|${RESET} ${mins}m ${secs}s"
31+
fi
32+
33+
# RIGA 2: barra contesto
34+
pct_int=$(printf "%.0f" "$pct")
35+
if [ "$pct_int" -lt 50 ]; then BAR_COLOR="$GREEN"
36+
elif [ "$pct_int" -lt 80 ]; then BAR_COLOR="$YELLOW"
37+
else BAR_COLOR="$RED"
38+
fi
39+
40+
filled=$((pct_int * 20 / 100))
41+
empty=$((20 - filled))
42+
bar=""
43+
for ((i=0; i<filled; i++)); do bar+=""; done
44+
for ((i=0; i<empty; i++)); do bar+=""; done
45+
46+
if [ "$ctx_size" -ge 1000000 ]; then ctx_label="$((ctx_size / 1000000))M"
47+
elif [ "$ctx_size" -ge 1000 ]; then ctx_label="$((ctx_size / 1000))k"
48+
else ctx_label="$ctx_size"
49+
fi
50+
51+
echo "${BAR_COLOR}${RESET} ${bar} ${pct}% ${DIM}of ${ctx_label} | ${pct}% used${RESET}"

AGENTS.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# AGENTS.md — MCP Comet
2+
3+
MCP Comet is a TypeScript MCP (Model Context Protocol) server that automates the Perplexity Comet browser via Chrome DevTools Protocol (CDP). It exposes 13 tools over stdio for prompting, polling, screenshots, tab management, source extraction, and mode switching.
4+
5+
## Commands
6+
7+
```bash
8+
npm run build # tsc → dist/
9+
npm test # vitest run
10+
npm run test:watch # vitest in watch mode
11+
npm run test:ci # vitest run --coverage (CI uses this)
12+
npm run lint # biome check .
13+
npm run format # biome write . (auto-fix)
14+
npm run typecheck # tsc --noEmit
15+
npm start # node dist/index.js (stdio MCP server)
16+
```
17+
18+
CI pipeline (`.github/workflows/ci.yml`): `npm ci → build → lint → vitest run --coverage` on Node 22.
19+
20+
**Before committing**: run `npm run lint && npm test`.
21+
22+
## Architecture
23+
24+
Four layers, top to bottom:
25+
26+
```
27+
MCP Tools (server.ts) → UI Automation (src/ui/) → CDP Transport (src/cdp/) → Comet Browser
28+
```
29+
30+
- **server.ts** — Single file defining all 13 tools via `McpServer.tool()`. Contains `startServer()`, tool definitions, Zod schemas, and all handler logic. This is the main file to edit when adding/modifying tools.
31+
- **src/ui/** — Functions that return JavaScript strings (evaluated in the browser via `Runtime.evaluate`). Each `build*Script()` function returns a self-contained IIFE string. **Do not** pass complex objects — everything must serialize to a JS expression.
32+
- **src/cdp/client.ts**`CDPClient` singleton (`CDPClient.getInstance()`) managing WebSocket connections, auto-reconnect with exponential backoff, and an operation queue (`enqueue()`) to serialize concurrent CDP calls.
33+
- **src/selectors/** — Version-keyed CSS selector sets (`SelectorSet`). `v145.ts` is the current set. New Comet/Chrome versions get a new `v{version}.ts` file registered in `index.ts`. Unknown versions fall back to the latest known set.
34+
35+
### Key flow: `comet_ask` (fire-and-forget) + `comet_wait` (blocking poll)
36+
37+
`comet_ask` types the prompt via `execCommand('insertText')` and submits immediately — it does NOT wait for a response. `comet_wait` (or `comet_poll`) must be called separately to retrieve results. This is a deliberate design: decoupled prompt submission from response polling.
38+
39+
### Auto-connect
40+
41+
Every tool handler calls `ensureConnected()` first, which lazily launches/connects to Comet if no session exists. Connection health is verified by evaluating `1+1` via CDP with a timeout.
42+
43+
## Code Style
44+
45+
- **Formatter**: Biome (2-space indent, single quotes, no semicolons, trailing commas, 100 char line width)
46+
- **Module system**: ESM only (`"type": "module"`, `Node16` module resolution, `.js` extensions in imports)
47+
- **Imports**: Always use `.js` extension in import paths (TypeScript ESM requirement)
48+
- **Strict mode**: TypeScript strict enabled
49+
- **Lint rules**: `noExplicitAny: error` in source (off in tests), `noConsole: warn` in source (off in tests, suppress with `// biome-ignore lint/suspicious/noConsole: ...`)
50+
- **Zod for schemas**: Tool parameters defined as Zod raw shapes; `buildInputSchema()` converts to JSON schema for the exported registry
51+
52+
## Testing
53+
54+
Vitest with v8 coverage. Coverage thresholds: 75% statements/lines, 70% branches, 80% functions.
55+
56+
### Test structure
57+
58+
- **`tests/unit/`** — Mocked CDP, isolated component tests. UI script tests validate that `build*Script()` functions produce valid JS containing expected patterns.
59+
- **`tests/integration/tools/`** — End-to-end tool handler tests using the **harness pattern** (`tests/integration/tools/harness.ts`).
60+
61+
### Integration test harness
62+
63+
The harness (`harness.ts`) is critical to understand:
64+
65+
1. Mocks `McpServer` to capture handler functions during `startServer()` into `capturedHandlers`
66+
2. Mocks `CDPClient.getInstance()` to return a controllable `mocks` object
67+
3. Mocks `loadConfig` and `detectCometVersion`
68+
4. Tests call `registerHandlers()` in `beforeAll`, then `getHandler('tool_name')` to get the handler
69+
70+
When writing new tool tests:
71+
```ts
72+
import { getHandler, mocks, registerHandlers, resetHarness } from './harness.js'
73+
74+
beforeAll(async () => { await registerHandlers() })
75+
beforeEach(() => { resetHarness() })
76+
// Override mocks as needed per test, then call the handler
77+
```
78+
79+
### Unit test patterns
80+
81+
- **CDPClient tests**: Mock `chrome-remote-interface` and `globalThis.fetch` for HTTP endpoints (`/json/version`, `/json/list`)
82+
- **UI script tests**: Call `build*Script()` functions and assert on the returned string content (no browser needed)
83+
- Reset singleton between tests: `CDPClient.resetInstance()`
84+
85+
## Gotchas and Non-Obvious Patterns
86+
87+
- **UI scripts are strings, not functions**: Everything in `src/ui/` returns a JS string that gets evaluated remotely via `Runtime.evaluate`. You cannot pass closures or use Node APIs inside these scripts. All context must be embedded in the string via string interpolation.
88+
89+
- **Prompt injection uses `execCommand('insertText')`**: Comet uses a Lexical editor that ignores standard `value` assignment. Prompts are JSON.stringify'd before injection to prevent XSS/injection attacks.
90+
91+
- **Singleton CDPClient**: `CDPClient.getInstance()` returns one instance. Tests must call `CDPClient.resetInstance()` to clear state between test runs.
92+
93+
- **Operation queue**: `CDPClient.enqueue()` serializes all operations. Nested calls within an `enqueue` block are fine, but two concurrent external calls will be sequenced.
94+
95+
- **Auto-reconnect**: `withAutoReconnect()` wraps operations with retry logic. Health checks evaluate `1+1` and reconnect on failure. The reconnect itself is deduplicated via `reconnectPromise`.
96+
97+
- **Tab categorization**: Tabs are classified by URL patterns. `perplexity.ai` with `sidecar` in URL → Sidecar. `chrome://` tabs must never be closed (crashes Comet). Only `agentBrowsing` tabs are closed during cleanup.
98+
99+
- **`comet_mode` requires navigation to home**: Mode switching only works on a new chat page. The tool navigates to `https://www.perplexity.ai` before attempting the slash-command typeahead.
100+
101+
- **Collapsed citations**: Sources with empty URLs and text containing `+` (e.g., "wsj+3") are detected as collapsed. `comet_get_sources` does a two-pass extraction: first pass collects what's visible, second pass clicks collapsed items (via `buildExpandCollapsedCitationsScript`) and re-extracts, then merges by deduplicating on URL.
102+
103+
- **No `src/server.test.ts`**: Server logic is tested via the integration test harness in `tests/integration/tools/`, not via a unit test file.
104+
105+
- **`index.ts` is the stdio entry point**: It imports and calls `startServer()` from `server.ts`. The `cli.ts` file is the `mcp-comet` binary with subcommands (`start`, `call`, `detect`).
106+
107+
- **Logger writes to stderr**: All logging goes to stderr to avoid corrupting MCP stdio JSON-RPC messages on stdout.
108+
109+
- **Release**: Triggered by pushing `v*` tags. Publishes to npm as `@onestepat4time/mcp-comet`. Uses `release-please` for changelog/version management.
110+
111+
## Adding a New Comet Version
112+
113+
When Comet updates its Chrome version and CSS selectors change:
114+
115+
1. Run `mcp-comet detect` to get the Chrome major version
116+
2. Inspect Comet DOM with DevTools
117+
3. Create `src/selectors/v{version}.ts` implementing `SelectorSet` interface
118+
4. Register in `src/selectors/index.ts` selector map
119+
5. Add unit tests in `tests/unit/selectors/`
120+
6. Update `docs/comet-compatibility.md`
121+
122+
## Commit Conventions
123+
124+
Conventional commit prefixes: `feat:`, `fix:`, `docs:`, `test:`, `refactor:`, `chore:`. CI requires passing lint + tests before merge.

0 commit comments

Comments
 (0)