smart-context-mcp 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  **MCP server that reduces AI agent token usage by 90% and improves response quality.**
7
7
 
8
- Instead of reading entire files and repeating context, this MCP provides 7 smart tools that compress, rank, and maintain context efficiently.
8
+ Instead of reading entire files and repeating context, this MCP provides 8 focused tools that compress, rank, and maintain context efficiently.
9
9
 
10
10
  ## Why use this?
11
11
 
@@ -15,7 +15,7 @@ Instead of reading entire files and repeating context, this MCP provides 7 smart
15
15
 
16
16
  **Real metrics from production use:**
17
17
  - 14.5M tokens → 1.6M tokens (89.87% reduction)
18
- - 3,666 successful calls across 7 tools
18
+ - 3,666 successful calls across the original 7 core tools
19
19
  - Compression ratios: 3x to 46x depending on tool
20
20
 
21
21
  ## Quick Start (2 commands)
@@ -31,13 +31,15 @@ That's it. Restart your AI client (Cursor, Codex, Claude Desktop) and the tools
31
31
 
32
32
  ## What you get
33
33
 
34
- Seven focused tools that work automatically:
34
+ Eight focused tools that work automatically:
35
35
 
36
36
  - `smart_read`: compact file summaries instead of full file dumps (3x compression)
37
37
  - `smart_read_batch`: read multiple files in one call — reduces round-trip latency
38
38
  - `smart_search`: ripgrep-first code search with intent-aware ranking (21x compression)
39
39
  - `smart_context`: one-call context planner — search + read + graph expansion
40
40
  - `smart_summary`: maintain compressed conversation state across sessions (46x compression)
41
+ - `smart_turn`: one-call turn orchestration for start/end context recovery and checkpointing
42
+ - `smart_metrics`: inspect saved token metrics and recent usage through MCP
41
43
  - `smart_shell`: safe diagnostic shell execution with restricted commands (18x compression)
42
44
  - `build_index`: lightweight symbol index for faster lookups and smarter ranking
43
45
 
@@ -81,14 +83,18 @@ npx smart-context-init --target .
81
83
  ```
82
84
 
83
85
  This installs the MCP server and generates client configs for Cursor, Codex, Qwen, and Claude Code. Open the project with your IDE/agent and the server starts automatically.
86
+ If the target is a git repository, `smart-context-init` also installs an idempotent `pre-commit` hook that blocks commits when `.devctx/state.sqlite` is staged, tracked, or not properly ignored.
87
+ For Claude Code, `smart-context-init` also generates `.claude/settings.json` with native hooks so devctx context recovery and turn-end enforcement run automatically.
84
88
 
85
89
  ## Binaries
86
90
 
87
- The package exposes three binaries:
91
+ The package exposes five binaries:
88
92
 
93
+ - `smart-context-headless`
89
94
  - `smart-context-server`
90
95
  - `smart-context-init`
91
96
  - `smart-context-report`
97
+ - `smart-context-protect`
92
98
 
93
99
  Start the MCP server against the current project:
94
100
 
@@ -127,10 +133,12 @@ smart-context-init --target /path/to/project --command node --args '["./tools/de
127
133
  Each tool call persists token metrics to the target repo by default in:
128
134
 
129
135
  ```bash
130
- .devctx/metrics.jsonl
136
+ .devctx/state.sqlite
131
137
  ```
132
138
 
133
- This makes per-repo usage visible without digging into `node_modules`. Running `smart-context-init` also adds `.devctx/` to the target repo's `.gitignore` idempotently.
139
+ SQLite is now the primary project-local store for persisted context and metrics. Running `smart-context-init` also adds `.devctx/` to the target repo's `.gitignore` idempotently.
140
+
141
+ When an active session exists, metrics entries automatically inherit its `sessionId`, so you can inspect savings per task with `smart_metrics`.
134
142
 
135
143
  Show a quick report:
136
144
 
@@ -138,7 +146,7 @@ Show a quick report:
138
146
  smart-context-report
139
147
  ```
140
148
 
141
- Show JSON output or a custom file:
149
+ Show JSON output or inspect a legacy/custom JSONL file explicitly:
142
150
 
143
151
  ```bash
144
152
  smart-context-report --json
@@ -150,8 +158,8 @@ Example output:
150
158
  ```text
151
159
  devctx metrics report
152
160
 
153
- File: /path/to/repo/.devctx/metrics.jsonl
154
- Source: default
161
+ File: /path/to/repo/.devctx/state.sqlite
162
+ Source: sqlite
155
163
  Entries: 148
156
164
  Raw tokens: 182,340
157
165
  Final tokens: 41,920
@@ -163,7 +171,7 @@ By tool:
163
171
  smart_search count=35 raw=33,330 final=7,800 saved=25,530 (76.59%)
164
172
  ```
165
173
 
166
- If you want to override the location entirely, set `DEVCTX_METRICS_FILE`.
174
+ If you need JSONL compatibility for external tooling, set `DEVCTX_METRICS_FILE` or pass `--file`.
167
175
 
168
176
  ## Usage per client
169
177
 
@@ -171,7 +179,7 @@ After installing and running `smart-context-init`, each client picks up the serv
171
179
 
172
180
  ### Cursor
173
181
 
174
- Open the project in Cursor. The MCP server starts automatically. Enable it in **Cursor Settings > MCP** if needed. All seven tools are available in Agent mode.
182
+ Open the project in Cursor. The MCP server starts automatically. Enable it in **Cursor Settings > MCP** if needed. All eight tools are available in Agent mode.
175
183
 
176
184
  ### Codex CLI
177
185
 
@@ -189,7 +197,20 @@ cd /path/to/your-project
189
197
  claude
190
198
  ```
191
199
 
192
- Claude Code reads `.mcp.json` from the project root.
200
+ Claude Code reads `.mcp.json` from the project root and `.claude/settings.json` for native hook automation.
201
+
202
+ ### Codex/Qwen headless fallback
203
+
204
+ When a client does not expose native per-turn hooks, use `smart-context-headless` to wrap a headless CLI run and force `smart_turn(start)` plus a closing checkpoint around that invocation.
205
+
206
+ Examples:
207
+
208
+ ```bash
209
+ smart-context-headless --client codex --prompt "Finish the runtime repo-safety docs" -- codex exec
210
+ smart-context-headless --client qwen --prompt "Review the persisted session and propose the next step" -- qwen -p
211
+ ```
212
+
213
+ This is the current automation path for non-Claude CLI agents. GUI clients without hook support still rely on generated rules plus `smart_turn`.
193
214
 
194
215
  ### Qwen Code
195
216
 
@@ -215,9 +236,9 @@ The `intent` parameter in `smart_search` and `smart_context` adjusts ranking and
215
236
 
216
237
  - **Cursor**: `.cursor/rules/devctx.mdc` (always-apply rule)
217
238
  - **Codex**: `AGENTS.md` (devctx section with sentinel markers)
218
- - **Claude Code**: `CLAUDE.md` (devctx section with sentinel markers)
239
+ - **Claude Code**: `CLAUDE.md` (devctx section with sentinel markers) and `.claude/settings.json` (native hooks)
219
240
 
220
- The rules are idempotent — running `smart-context-init` again updates the section without duplicating it. Existing content in `AGENTS.md` and `CLAUDE.md` is preserved.
241
+ The generated files are idempotent — running `smart-context-init` again updates the devctx sections and Claude hook entries without duplicating them. Existing content in `AGENTS.md`, `CLAUDE.md`, and `.claude/settings.json` is preserved.
221
242
 
222
243
  ## Use against another repo
223
244
 
@@ -449,16 +470,20 @@ Maintain compressed conversation state across sessions. Solves the context-loss
449
470
 
450
471
  | Action | Purpose | Returns |
451
472
  |--------|---------|---------|
452
- | `get` | Retrieve current or specified session | Resume summary (≤500 tokens) + compression metadata |
473
+ | `get` | Retrieve current, explicit, or auto-resolved session | Resume summary (≤500 tokens) + compression metadata |
453
474
  | `update` | Create or replace session | New session with compressed state |
454
475
  | `append` | Add to existing session | Merged session state |
476
+ | `auto_append` | Add only when something meaningful changed | Merged session state or skipped no-op result |
477
+ | `checkpoint` | Event-driven orchestration for persistence decisions | Persisted update or skipped event with decision metadata |
455
478
  | `reset` | Clear session | Confirmation |
456
479
  | `list_sessions` | Show all available sessions | Array of sessions with metadata |
480
+ | `compact` | Apply retention/compaction to SQLite state | Counts for pruned sessions, events, and metrics |
481
+ | `cleanup_legacy` | Inspect or remove imported JSON/JSONL artifacts | Dry-run or deletion report |
457
482
 
458
483
  **Parameters:**
459
484
  - `action` (required) — one of the actions above
460
- - `sessionId` (optional) — session identifier; auto-generated from `goal` if omitted
461
- - `update` (required for update/append) — object with:
485
+ - `sessionId` (optional) — session identifier; auto-generated from `goal` if omitted. Pass `"auto"` to accept the recommended recent session when multiple candidates exist.
486
+ - `update` (required for update/append/auto_append/checkpoint) — object with:
462
487
  - `goal`: primary objective
463
488
  - `status`: current state (`planning` | `in_progress` | `blocked` | `completed`)
464
489
  - `pinnedContext`: critical context that should survive compression when possible
@@ -471,14 +496,27 @@ Maintain compressed conversation state across sessions. Solves the context-loss
471
496
  - `nextStep`: immediate next action
472
497
  - `touchedFiles`: array of modified files
473
498
  - `maxTokens` (optional, default 500) — hard cap on summary size
499
+ - `event` (optional for `checkpoint`) — one of `manual`, `milestone`, `decision`, `blocker`, `status_change`, `file_change`, `task_switch`, `task_complete`, `session_end`, `read_only`, `heartbeat`
500
+ - `force` (optional, default false) — override a suppressed checkpoint event
501
+ - `retentionDays` (optional, default 30) — used by `compact`
502
+ - `keepLatestEventsPerSession` (optional, default 20) — used by `compact`
503
+ - `keepLatestMetrics` (optional, default 1000) — used by `compact`
504
+ - `vacuum` (optional, default false) — run SQLite `VACUUM` after deletions during `compact`
505
+ - `apply` (optional, default false) — required to actually delete files during `cleanup_legacy`
474
506
 
475
- `update` replaces the stored session state for that `sessionId`, so omitted fields are cleared. Use `append` when you want to keep existing state and add progress incrementally.
507
+ `update` replaces the stored session state for that `sessionId`, so omitted fields are cleared. Use `append` when you want to keep existing state and add progress incrementally. Use `auto_append` when the caller may fire checkpoint saves often and you want the tool to skip no-op updates automatically. Use `checkpoint` when the caller has a meaningful event and wants the tool to decide whether that event deserves persistence.
476
508
 
477
509
  **Storage:**
478
- - Sessions persist in `.devctx/sessions/<sessionId>.json`
479
- - Active session tracked in `.devctx/sessions/active.json`
480
- - 30-day retention for inactive sessions
481
- - No expiration for active sessions
510
+ - Session state, session events, summary cache, and metrics persist in `.devctx/state.sqlite`
511
+ - Legacy `.devctx/sessions/*.json`, `.devctx/sessions/active.json`, and `.devctx/metrics.jsonl` are imported idempotently when present
512
+ - `compact` enforces retention without deleting the active session
513
+ - `cleanup_legacy` is dry-run by default and only deletes imported legacy artifacts when `apply: true`
514
+
515
+ **Auto-resume behavior:**
516
+ - `get` returns the active session immediately when `active.json` exists
517
+ - If there is no active session, `get` auto-resumes the best saved session when there is a single clear candidate
518
+ - If multiple recent sessions are plausible, `get` returns ordered `candidates` plus `recommendedSessionId`
519
+ - Passing `sessionId: "auto"` accepts that recommendation and restores it as the active session
482
520
 
483
521
  **Resume summary fields:**
484
522
  - `status` and `nextStep` are preserved with highest priority
@@ -493,6 +531,8 @@ Maintain compressed conversation state across sessions. Solves the context-loss
493
531
  - `truncated`: whether the resume summary had to be compressed
494
532
  - `compressionLevel`: `none` | `trimmed` | `reduced` | `status_only`
495
533
  - `omitted`: fields dropped from the resume summary to fit the token budget
534
+ - `repoSafety`: git hygiene signal for `.devctx/state.sqlite` (`isIgnored`, `isTracked`, `isStaged`, warnings, recommended actions)
535
+ - mutating actions (`update`, `append`, `auto_append`, `checkpoint`, `reset`, `compact`) are blocked at runtime when `.devctx/state.sqlite` is tracked or staged
496
536
 
497
537
  **Compression strategy:**
498
538
  - Keeps the persisted session state intact and compresses only the resume summary
@@ -505,11 +545,12 @@ Maintain compressed conversation state across sessions. Solves the context-loss
505
545
  ```javascript
506
546
  // Start of work session
507
547
  smart_summary({ action: "get" })
508
- // → retrieves last active session or returns "not found"
548
+ // → retrieves last active session or auto-resumes the best saved session
509
549
 
510
550
  // After implementing auth middleware
511
551
  smart_summary({
512
- action: "append",
552
+ action: "checkpoint",
553
+ event: "milestone",
513
554
  update: {
514
555
  completed: ["auth middleware"],
515
556
  decisions: ["JWT with 1h expiry, refresh tokens in Redis"],
@@ -525,6 +566,77 @@ smart_summary({ action: "get" })
525
566
  // List all sessions
526
567
  smart_summary({ action: "list_sessions" })
527
568
  // → see all available sessions, pick one to resume
569
+
570
+ // Inspect git safety for project-local state from any smart_summary response
571
+ smart_summary({ action: "get" })
572
+ // → repoSafety warns if .devctx/state.sqlite is tracked or not ignored
573
+
574
+ // Suppress noisy read-only exploration checkpoints
575
+ smart_summary({
576
+ action: "checkpoint",
577
+ event: "read_only",
578
+ update: { currentFocus: "inspect auth flow" }
579
+ })
580
+ // → skipped=true, no event persisted
581
+
582
+ // Compact old SQLite events while keeping recent history
583
+ smart_summary({ action: "compact", retentionDays: 30, keepLatestEventsPerSession: 20, keepLatestMetrics: 1000 })
584
+
585
+ // Inspect what legacy files are safe to remove
586
+ smart_summary({ action: "cleanup_legacy" })
587
+
588
+ // Remove imported legacy JSON/JSONL artifacts explicitly
589
+ smart_summary({ action: "cleanup_legacy", apply: true })
590
+ ```
591
+
592
+ ### `smart_metrics`
593
+
594
+ Inspect token metrics recorded in project-local SQLite storage without leaving MCP.
595
+
596
+ - Returns aggregated totals, savings percentage, and per-tool breakdowns
597
+ - Supports `window`: `24h` | `7d` | `30d` | `all`
598
+ - Supports filtering by `tool`
599
+ - Supports filtering by `sessionId`, including `sessionId: "active"`
600
+ - Includes `latestEntries` so an agent can explain recent savings without parsing storage manually
601
+ - Includes `overheadTokens` and `overheadTools` so hook/wrapper context cost stays measurable against the savings
602
+ - When `.devctx/state.sqlite` is tracked or staged, metric writes are skipped and reads fall back to a temporary read-only snapshot with `sideEffectsSuppressed: true`
603
+
604
+ **Example workflow:**
605
+
606
+ ```javascript
607
+ smart_metrics({ window: "7d", sessionId: "active" })
608
+ // → totals and recent entries for the current task/session
609
+ ```
610
+
611
+ ### `smart_turn`
612
+
613
+ Orchestrate the start or end of a meaningful agent turn with one MCP call.
614
+
615
+ - `phase: "start"` rehydrates context, classifies whether the current prompt aligns with persisted work, and can auto-create a planning session for a substantial new task
616
+ - `phase: "end"` writes a checkpoint through `smart_summary` and can optionally include compact metrics
617
+ - Designed to make context usage almost mandatory without forcing the agent to chain `smart_summary(get)` and `smart_summary(checkpoint)` manually on every turn
618
+ - Claude Code can invoke this automatically through generated native hooks on `SessionStart`, `UserPromptSubmit`, `PostToolUse`, and `Stop`
619
+ - Non-Claude CLI clients can approximate the same flow with `smart-context-headless`, which wraps one headless agent invocation around `smart_turn(start)` and `smart_turn(end)`
620
+
621
+ **Example workflow:**
622
+
623
+ ```javascript
624
+ smart_turn({
625
+ phase: "start",
626
+ prompt: "Finish runtime repo-safety enforcement for smart metrics",
627
+ ensureSession: true
628
+ })
629
+ // → summary + continuity classification + repoSafety
630
+
631
+ smart_turn({
632
+ phase: "end",
633
+ event: "milestone",
634
+ update: {
635
+ completed: ["Finished smart metrics repo-safety enforcement"],
636
+ nextStep: "Update docs and run the full suite"
637
+ }
638
+ })
639
+ // → checkpoint result + optional compact metrics
528
640
  ```
529
641
 
530
642
  ### `build_index`
@@ -575,9 +687,10 @@ Metrics include: P@5, P@10, Recall, wrong-file rate, retrieval honesty, follow-u
575
687
  ## Notes
576
688
 
577
689
  - `@vscode/ripgrep` provides a bundled `rg` binary, so a system install is not required.
578
- - Metrics are written to `<projectRoot>/.devctx/metrics.jsonl` (override with `DEVCTX_METRICS_FILE` env var).
690
+ - Persistent context and metrics live in `<projectRoot>/.devctx/state.sqlite`.
691
+ - `DEVCTX_METRICS_FILE` is now an explicit compatibility override for JSONL-based workflows and reports.
579
692
  - Symbol index stored in `<projectRoot>/.devctx/index.json` when `build_index` is used.
580
- - Conversation sessions stored in `<projectRoot>/.devctx/sessions/` when `smart_summary` is used.
693
+ - Legacy session JSON files in `<projectRoot>/.devctx/sessions/` are imported idempotently when present.
581
694
  - This package is a navigation and diagnostics layer, not a full semantic code intelligence system.
582
695
 
583
696
  ## Repository
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-context-mcp",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "MCP server that reduces agent token usage and improves response quality with compact file summaries, ranked code search, and curated context.",
5
5
  "author": "Francisco Caballero Portero <fcp1978@hotmail.com>",
6
6
  "type": "module",
@@ -13,9 +13,11 @@
13
13
  "url": "https://github.com/Arrayo/devctx-mcp-mvp/issues"
14
14
  },
15
15
  "bin": {
16
+ "smart-context-headless": "scripts/headless-wrapper.js",
16
17
  "smart-context-server": "scripts/devctx-server.js",
17
18
  "smart-context-init": "scripts/init-clients.js",
18
- "smart-context-report": "scripts/report-metrics.js"
19
+ "smart-context-report": "scripts/report-metrics.js",
20
+ "smart-context-protect": "scripts/check-repo-safety.js"
19
21
  },
20
22
  "exports": {
21
23
  ".": "./src/server.js",
@@ -23,7 +25,10 @@
23
25
  },
24
26
  "files": [
25
27
  "src/",
28
+ "scripts/claude-hook.js",
29
+ "scripts/check-repo-safety.js",
26
30
  "scripts/devctx-server.js",
31
+ "scripts/headless-wrapper.js",
27
32
  "scripts/init-clients.js",
28
33
  "scripts/report-metrics.js"
29
34
  ],
@@ -45,7 +50,7 @@
45
50
  "smoke:json": "node ./scripts/smoke-test.js --json",
46
51
  "init:clients": "node ./scripts/init-clients.js",
47
52
  "smoke:formats": "node ./scripts/format-smoke.js",
48
- "test": "node --test ./tests/*.test.js",
53
+ "test": "node --test --test-concurrency=1 ./tests/*.test.js",
49
54
  "eval": "node ./evals/harness.js",
50
55
  "eval:context": "node ./evals/harness.js --tool=context",
51
56
  "eval:both": "node ./evals/harness.js --tool=both",
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { enforceRepoSafety } from '../src/repo-safety.js';
5
+ import { setProjectRoot } from '../src/utils/runtime-config.js';
6
+
7
+ const writeStdout = (text) => {
8
+ fs.writeSync(process.stdout.fd, text);
9
+ };
10
+
11
+ const writeStderr = (text) => {
12
+ fs.writeSync(process.stderr.fd, text);
13
+ };
14
+
15
+ const parseArgs = (argv) => {
16
+ const options = {
17
+ projectRoot: process.cwd(),
18
+ json: false,
19
+ };
20
+
21
+ for (let index = 0; index < argv.length; index += 1) {
22
+ const token = argv[index];
23
+
24
+ if (token === '--project-root') {
25
+ const next = argv[index + 1];
26
+ if (!next || next.startsWith('--')) {
27
+ throw new Error('Missing value for --project-root');
28
+ }
29
+ options.projectRoot = path.resolve(next);
30
+ index += 1;
31
+ continue;
32
+ }
33
+
34
+ if (token === '--json') {
35
+ options.json = true;
36
+ continue;
37
+ }
38
+
39
+ throw new Error(`Unknown argument: ${token}`);
40
+ }
41
+
42
+ return options;
43
+ };
44
+
45
+ const printHuman = (result) => {
46
+ if (result.ok) {
47
+ writeStdout('devctx repo safety: ok\n');
48
+ return;
49
+ }
50
+
51
+ writeStderr('devctx repo safety: failed\n');
52
+ for (const violation of result.violations) {
53
+ writeStderr(`- ${violation}\n`);
54
+ }
55
+ for (const action of result.recommendedActions) {
56
+ writeStderr(`- ${action}\n`);
57
+ }
58
+ };
59
+
60
+ const main = () => {
61
+ const options = parseArgs(process.argv.slice(2));
62
+ setProjectRoot(options.projectRoot);
63
+
64
+ const result = enforceRepoSafety({ root: options.projectRoot });
65
+ if (options.json) {
66
+ const output = `${JSON.stringify(result, null, 2)}\n`;
67
+ if (result.ok) {
68
+ writeStdout(output);
69
+ } else {
70
+ writeStderr(output);
71
+ }
72
+ } else {
73
+ printHuman(result);
74
+ }
75
+
76
+ process.exitCode = result.ok ? 0 : 1;
77
+ };
78
+
79
+ try {
80
+ main();
81
+ } catch (error) {
82
+ writeStderr(`${error.message}\n`);
83
+ process.exitCode = 1;
84
+ }
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import { handleClaudeHookEvent } from '../src/hooks/claude-hooks.js';
3
+
4
+ const readStdin = async () => {
5
+ let buffer = '';
6
+ for await (const chunk of process.stdin) {
7
+ buffer += chunk;
8
+ }
9
+ return buffer.trim();
10
+ };
11
+
12
+ const main = async () => {
13
+ const raw = await readStdin();
14
+ if (!raw) {
15
+ return;
16
+ }
17
+
18
+ const input = JSON.parse(raw);
19
+ const result = await handleClaudeHookEvent(input);
20
+
21
+ if (!result) {
22
+ return;
23
+ }
24
+
25
+ process.stdout.write(`${JSON.stringify(result)}\n`);
26
+ };
27
+
28
+ main().catch((error) => {
29
+ if (process.env.DEVCTX_DEBUG === '1') {
30
+ process.stderr.write(`devctx claude hook error: ${error.message}\n`);
31
+ }
32
+ process.exit(0);
33
+ });
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ import { runHeadlessWrapper } from '../src/orchestration/headless-wrapper.js';
3
+
4
+ const requireValue = (argv, index, flag) => {
5
+ const value = argv[index + 1];
6
+ if (!value || value === '--') {
7
+ throw new Error(`Missing value for ${flag}`);
8
+ }
9
+ return value;
10
+ };
11
+
12
+ const parseArgs = (argv) => {
13
+ const options = {
14
+ client: 'generic',
15
+ prompt: '',
16
+ sessionId: undefined,
17
+ event: undefined,
18
+ stdinPrompt: false,
19
+ dryRun: false,
20
+ json: false,
21
+ streamOutput: true,
22
+ command: '',
23
+ args: [],
24
+ };
25
+
26
+ let commandIndex = argv.indexOf('--');
27
+ const head = commandIndex === -1 ? argv : argv.slice(0, commandIndex);
28
+
29
+ for (let index = 0; index < head.length; index += 1) {
30
+ const token = head[index];
31
+
32
+ if (token === '--client') {
33
+ options.client = requireValue(head, index, '--client');
34
+ index += 1;
35
+ continue;
36
+ }
37
+
38
+ if (token === '--prompt') {
39
+ options.prompt = requireValue(head, index, '--prompt');
40
+ index += 1;
41
+ continue;
42
+ }
43
+
44
+ if (token === '--session-id') {
45
+ options.sessionId = requireValue(head, index, '--session-id');
46
+ index += 1;
47
+ continue;
48
+ }
49
+
50
+ if (token === '--event') {
51
+ options.event = requireValue(head, index, '--event');
52
+ index += 1;
53
+ continue;
54
+ }
55
+
56
+ if (token === '--stdin-prompt') {
57
+ options.stdinPrompt = true;
58
+ continue;
59
+ }
60
+
61
+ if (token === '--dry-run') {
62
+ options.dryRun = true;
63
+ continue;
64
+ }
65
+
66
+ if (token === '--json') {
67
+ options.json = true;
68
+ continue;
69
+ }
70
+
71
+ if (token === '--quiet') {
72
+ options.streamOutput = false;
73
+ continue;
74
+ }
75
+
76
+ throw new Error(`Unknown argument: ${token}`);
77
+ }
78
+
79
+ if (commandIndex !== -1) {
80
+ const commandParts = argv.slice(commandIndex + 1);
81
+ if (commandParts.length > 0) {
82
+ [options.command, ...options.args] = commandParts;
83
+ }
84
+ }
85
+
86
+ return options;
87
+ };
88
+
89
+ const main = async () => {
90
+ const options = parseArgs(process.argv.slice(2));
91
+ if (options.json) {
92
+ options.streamOutput = false;
93
+ }
94
+ const result = await runHeadlessWrapper(options);
95
+ if (options.dryRun || options.json) {
96
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
97
+ }
98
+ if (!options.dryRun && result.exitCode !== 0) {
99
+ process.exitCode = result.exitCode;
100
+ }
101
+ };
102
+
103
+ main().catch((error) => {
104
+ console.error(error.message);
105
+ process.exit(1);
106
+ });