openmerit 0.1.12 → 0.3.0

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.
Files changed (108) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +11 -27
  3. package/dist/pi/scout-extension.mjs +23 -10
  4. package/dist/terminal/scout-update.json +1 -1
  5. package/dist/terminal/src/browser.mjs +2 -4
  6. package/dist/terminal/src/cli.mjs +6 -56
  7. package/dist/terminal/src/install.mjs +3 -2
  8. package/dist/terminal/src/scout-instructions.js +23 -10
  9. package/dist/terminal/src/scout-pi.js +1 -1
  10. package/dist/terminal/src/scout.js +181 -0
  11. package/dist/terminal/src/telemetry.mjs +8 -60
  12. package/docs/agent-evaluations.md +8 -148
  13. package/docs/commands.md +4 -86
  14. package/docs/getting-started.md +2 -16
  15. package/docs/index.md +4 -27
  16. package/docs/inference-terminal.md +13 -655
  17. package/docs/pi-extension.md +1 -1
  18. package/docs/roadmap.md +4 -17
  19. package/docs/scout.md +34 -251
  20. package/docs/security.md +8 -33
  21. package/docs/testing.md +7 -47
  22. package/docs/troubleshooting.md +3 -3
  23. package/package.json +13 -46
  24. package/dist/core/src/index.d.ts +0 -101
  25. package/dist/core/src/index.js +0 -1649
  26. package/dist/core/src/store.d.ts +0 -35
  27. package/dist/core/src/store.js +0 -102
  28. package/dist/eval/src/adapters.mjs +0 -112
  29. package/dist/eval/src/cli.mjs +0 -151
  30. package/dist/eval/src/config.mjs +0 -108
  31. package/dist/eval/src/process.mjs +0 -44
  32. package/dist/eval/src/report.mjs +0 -59
  33. package/dist/eval/src/runner.mjs +0 -123
  34. package/dist/eval/src/scout.mjs +0 -48
  35. package/dist/protocol/src/index.d.ts +0 -484
  36. package/dist/protocol/src/index.js +0 -47
  37. package/dist/protocol/src/schemas.d.ts +0 -576
  38. package/dist/protocol/src/schemas.js +0 -280
  39. package/dist/terminal/public/app.js +0 -298
  40. package/dist/terminal/public/brands/anthropic.png +0 -0
  41. package/dist/terminal/public/brands/baai.png +0 -0
  42. package/dist/terminal/public/brands/baseten.png +0 -0
  43. package/dist/terminal/public/brands/cerebras.png +0 -0
  44. package/dist/terminal/public/brands/cohere.png +0 -0
  45. package/dist/terminal/public/brands/deepseek.ico +0 -0
  46. package/dist/terminal/public/brands/google.png +0 -0
  47. package/dist/terminal/public/brands/groq.ico +0 -0
  48. package/dist/terminal/public/brands/huggingface.svg +0 -8
  49. package/dist/terminal/public/brands/lm-studio.png +0 -0
  50. package/dist/terminal/public/brands/meta.ico +0 -0
  51. package/dist/terminal/public/brands/mistral.png +0 -0
  52. package/dist/terminal/public/brands/nomic.png +0 -0
  53. package/dist/terminal/public/brands/ollama.png +0 -0
  54. package/dist/terminal/public/brands/openai.png +0 -0
  55. package/dist/terminal/public/brands/openrouter.png +0 -0
  56. package/dist/terminal/public/brands/qwen.png +0 -0
  57. package/dist/terminal/public/brands/vllm.ico +0 -0
  58. package/dist/terminal/public/brands/vllm.png +0 -0
  59. package/dist/terminal/public/favicon.svg +0 -1
  60. package/dist/terminal/public/flow.css +0 -1
  61. package/dist/terminal/public/flow.js +0 -770
  62. package/dist/terminal/public/index.html +0 -22
  63. package/dist/terminal/public/styles.css +0 -1046
  64. package/dist/terminal/src/activity-merge.mjs +0 -66
  65. package/dist/terminal/src/collect.mjs +0 -370
  66. package/dist/terminal/src/coverage.mjs +0 -33
  67. package/dist/terminal/src/demo-policy.mjs +0 -205
  68. package/dist/terminal/src/discovery.mjs +0 -99
  69. package/dist/terminal/src/enterprise-sample.mjs +0 -151
  70. package/dist/terminal/src/hardware.mjs +0 -57
  71. package/dist/terminal/src/history.mjs +0 -32
  72. package/dist/terminal/src/log-continuity.mjs +0 -26
  73. package/dist/terminal/src/project-activity.mjs +0 -157
  74. package/dist/terminal/src/repository-background.mjs +0 -37
  75. package/dist/terminal/src/repository-index.mjs +0 -267
  76. package/dist/terminal/src/repository-policy.mjs +0 -3
  77. package/dist/terminal/src/repository-syntax.mjs +0 -236
  78. package/dist/terminal/src/repository-worker.mjs +0 -15
  79. package/dist/terminal/src/sample.mjs +0 -171
  80. package/dist/terminal/src/scout-credentials.d.ts +0 -12
  81. package/dist/terminal/src/scout-credentials.ts +0 -44
  82. package/dist/terminal/src/scout-instructions.d.ts +0 -5
  83. package/dist/terminal/src/scout-instructions.ts +0 -106
  84. package/dist/terminal/src/scout-pi.d.ts +0 -44
  85. package/dist/terminal/src/scout-pi.ts +0 -76
  86. package/dist/terminal/src/server.mjs +0 -95
  87. package/dist/terminal/src/service-registry.mjs +0 -78
  88. package/dist/terminal/src/services.mjs +0 -44
  89. package/dist/terminal/src/topology.mjs +0 -30
  90. package/dist/terminal/src/updates.d.ts +0 -62
  91. package/dist/terminal/src/updates.ts +0 -313
  92. package/docs/adapter-guide.md +0 -191
  93. package/docs/architecture.md +0 -61
  94. package/docs/automation.md +0 -76
  95. package/docs/budgets.md +0 -39
  96. package/docs/demo-backfill.md +0 -31
  97. package/docs/demo-fieldkit.md +0 -49
  98. package/docs/demo-placement.md +0 -32
  99. package/docs/demo-spam.md +0 -17
  100. package/docs/demo-support.md +0 -44
  101. package/docs/demo.md +0 -74
  102. package/docs/first-trial.md +0 -62
  103. package/docs/interactive-demo.md +0 -118
  104. package/docs/lifecycle.md +0 -32
  105. package/docs/metrics-and-evidence.md +0 -48
  106. package/docs/operations.md +0 -40
  107. package/docs/pareto-spec.md +0 -76
  108. package/docs/ux-reference.md +0 -32
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 — 2026-11-04
4
+
5
+ - **Breaking:** retire the standalone `openmerit eval` CLI, including `init`, `doctor`, `plan`, `run`, `watch` and `report`. Existing local evaluation files are not deleted. The hosted `/scout/api/recommend` retirement ships separately with the Scout serving Worker.
6
+ - **Breaking:** retire the local Inference Terminal. `openmerit dash` and all of its options, including `--demo`, `--json` and `--diagnostics`, are removed, along with repository indexing, request-log and activity-export import, runtime and hardware discovery, the browser interface and its opt-in terminal analytics events. Existing `.openmerit/terminal.json`, `.openmerit/inference.jsonl` and application logs are not deleted. Scout setup, `openmerit scout` and `openmerit update` are unchanged.
7
+ - Drop the terminal-only runtime dependencies (`@iarna/toml`, `@lezer/python`) and browser bundle dependencies.
8
+
9
+ ## 0.2.0 — 2026-11-04
10
+
11
+ - Add `openmerit scout`, a conversation with Scout from the terminal: clarifying questions answered with `--brief` or `--skip`, then up to five models per task, cheapest first among models that reach Scout's quality bar. When you ask for the fastest or newest model, the output names that order and shows each model's time per task or release date. It uses this device's Claude Code or Pi key and limits new conversations to the endpoint IDs in `.openmerit/scout.json`.
12
+ - The installed Scout instructions for Claude Code and Pi tell the agent to relay Scout's questions to the user and to read `tasks[].models`. This needs the matching hosted Scout release, whose `scout_models` MCP tool now runs the Scout agent.
13
+ - **Breaking:** remove the `openmerit`, `openmerit/core` and `openmerit/protocol` package exports, along with the evaluation coordinator, harness protocol, Pareto conformance rules and the repository's `npm run demo` sandbox. The package keeps `openmerit/pi` and the `openmerit` command: `openmerit dash`, `openmerit scout`, Scout setup commands and `openmerit eval`.
14
+ - Remove the hosted Inference Terminal sample at `dash.openmerit.ai`, its `/demo` before/after comparison and the opt-in `?map=flow` map experiment. The local terminal, including its fictional sample (`--demo`), is unchanged.
15
+ - Remove the retired demo guides and the guides that documented only the coordinator: architecture, adapter development, Pareto specification, first trial, metrics and evidence, comparisons and swaps, automatic checks, budgets and permissions, and project files and logs.
16
+
3
17
  ## 0.1.12 — 2026-11-03
4
18
 
5
19
  - Replace the obsolete Pi 0.87 evaluation adapter with native Scout support for Pi 1.5.0. Share browser device approval, private client/scope/project credentials, installer lifecycle and Scout instructions with Claude Code; keep Pi's native MCP, tools, model, permissions and session ownership intact.
package/README.md CHANGED
@@ -1,24 +1,8 @@
1
1
  # OpenMerit
2
2
 
3
- OpenMerit provides a local Inference Terminal for understanding an application's models, services, recorded activity, and compute, plus Scout guidance for application-model choices.
3
+ OpenMerit provides Scout guidance for application-model choices: in the browser, from the `openmerit scout` command, and through MCP for Claude Code and Pi.
4
4
 
5
- The terminal reads existing evidence; Scout recommends candidates to evaluate. Neither is a request router or proof of application-quality improvements. A separate standalone evaluation CLI and reusable core coordinator support explicit evaluation workflows.
6
-
7
- ## Open the terminal
8
-
9
- Requires Node.js 22.19 or newer and npm:
10
-
11
- ```sh
12
- npm install -g openmerit
13
- cd /path/to/your/application
14
- openmerit dash
15
- ```
16
-
17
- No Pi installation or provider API key is needed. The terminal opens a browser connected to a read-only loopback service. Supported source/configuration declarations establish inventory; existing application logs or exports supply request history. Prompts, responses, and credentials are not retained by terminal collection. Unsupported or missing evidence remains unknown.
18
-
19
- The terminal and hosted dashboard follow the device's light/dark preference without a theme control. CLI and terminal product analytics are off unless explicitly enabled; see [the analytics contract](docs/inference-terminal.md#optional-cli-product-analytics).
20
-
21
- Explore the [hosted sample](https://dash.openmerit.ai/) or [before/after dashboard demo](https://dash.openmerit.ai/demo). Both use fictional data and make no inference calls. See [the terminal guide](docs/inference-terminal.md) for configuration, discovery, diagnostics, history limits, and agent access.
5
+ Scout recommends candidates to evaluate. It is not a request router or proof of application-quality improvements.
22
6
 
23
7
  ## Connect Scout to Claude Code
24
8
 
@@ -38,21 +22,22 @@ There are no `/openmerit` lifecycle commands, evaluation setup, scheduler, Launc
38
22
 
39
23
  Package publication and website deployment are separate releases. Pi setup requires OpenMerit 0.1.12 or newer; check the resolved package version when using `@latest`.
40
24
 
41
- ## Evaluate application changes
25
+ ## Evaluation CLI retirement — npm 0.3.0
26
+
27
+ OpenMerit 0.3.0 removes the standalone `openmerit eval` commands, including planning. Existing local evaluation files are not deleted. The old Scout JSON recommendation API is retired in source and goes offline with a separate Scout deployment. Scout's conversational agent, MCP, coding-agent setup and catalogue publication remain available; see [retirement details](docs/agent-evaluations.md).
42
28
 
43
- The standalone `openmerit eval` CLI is unchanged by the Pi replacement. Evaluate Claude Code, Codex and OpenCode with explicit tasks, model scope, budget and graders. Freeze Scout's shortlist and compare fresh repeated attempts after source changes. Budget and sandbox blocks stay separate from task scores; unknown costs stay unknown. See [agent evaluations](docs/agent-evaluations.md) for setup, graders, budgets and availability.
29
+ ## Inference Terminal retirement — npm 0.3.0
44
30
 
45
- The core coordinator remains available for custom harnesses: task profiles, controlled experiments, baseline and frontier validation, evidence contracts, budgets, approvals and rollback requests. The harness performs the work and supplies evidence; core validation does not establish that external measurements are truthful. The older Pi lifecycle walkthroughs are retained as historical/core references, not supported commands in the new Scout adapter.
31
+ OpenMerit 0.3.0 also removes the local Inference Terminal: `openmerit dash`, its `--demo` sample, collectors, repository index and browser interface. Existing `.openmerit/terminal.json`, `.openmerit/inference.jsonl` and application logs are not deleted. Older installed packages keep the command until they are upgraded. See [retirement details](docs/inference-terminal.md).
46
32
 
47
33
  ## Documentation
48
34
 
49
- The [documentation overview](docs/index.md) links current guides and historical references. The website at [openmerit.ai/docs](https://openmerit.ai/docs/) is deployed separately from the package and this checkout.
35
+ The [documentation overview](docs/index.md) links the maintained Markdown guides. The unpublished `/docs/` website shell is retired; the live Scout data policy and protected memo have separate builds.
50
36
 
51
37
  - [Getting started](docs/getting-started.md) and [troubleshooting](docs/troubleshooting.md)
52
38
  - [Scout](docs/scout.md) and [Pi adapter](docs/pi-extension.md)
53
- - [Inference Terminal](docs/inference-terminal.md) and [agent evaluations](docs/agent-evaluations.md)
39
+ - Retired: [Inference Terminal](docs/inference-terminal.md) and [agent evaluations](docs/agent-evaluations.md)
54
40
  - [Security and data handling](docs/security.md)
55
- - [Architecture](docs/architecture.md), [adapter development](docs/adapter-guide.md), and [Pareto specification](docs/pareto-spec.md)
56
41
  - [Testing](docs/testing.md) and [current limitations](docs/roadmap.md)
57
42
 
58
43
  ## Development
@@ -60,9 +45,8 @@ The [documentation overview](docs/index.md) links current guides and historical
60
45
  ```sh
61
46
  npm ci
62
47
  npm run verify
63
- npm run dash -- /path/to/application
64
48
  ```
65
49
 
66
- `packages/terminal` contains the CLI, collectors, hosted sample, and browser interface. `packages/protocol` and `packages/core` provide evaluation contracts and coordination; `packages/pi` supplies only the native Scout adapter. The package exports `openmerit/core`, `openmerit/protocol`, and `openmerit/pi`. See the [Pi package README](packages/pi/README.md) for bounded adapter tests. Provider credentials belong to the harness or its environment and must never be committed.
50
+ `packages/terminal` contains the `openmerit` CLI: Scout setup, updates and the `scout` command; `packages/pi` supplies only the native Scout adapter. The package exports `openmerit/pi` and the `openmerit` command. See the [Pi package README](packages/pi/README.md) for bounded adapter tests. Provider credentials belong to the harness or its environment and must never be committed.
67
51
 
68
- Edit documentation in `docs/`, then run `npm run check:docs`. The website is generated from those Markdown files. See [documentation maintenance](docs/site-documentation.md) and the [production-source reconciliation note](docs/site-domain-migration.md) before deployment; a local build does not publish changes.
52
+ Edit documentation in `docs/`, then run `npm run check:docs`. `npm run build:site` builds only the Scout data policy and protected memo, not a docs website. A local build does not publish changes; follow [deployment maintenance](docs/site-deployment.md) before any approved release.
@@ -46,6 +46,8 @@ count as model choices, not implementation details. Identify this decision befor
46
46
  writing model-specific code, installing a model package or downloading weights.
47
47
 
48
48
  Follow the skill's project access policy. If it is missing, ask and save it first.
49
+ Scout may answer with clarifying questions: relay them to the user and continue
50
+ the same Scout conversation with their answers before choosing.
49
51
  If Scout is unavailable, fails or has no suitable evidence/candidate, disclose the
50
52
  gap and pause the model decision; do not fall back to a remembered or SDK-default
51
53
  model. You may continue work that does not commit to a model.
@@ -92,17 +94,28 @@ is not permission to search all models.
92
94
 
93
95
  ## Searching
94
96
 
95
- Call \`scout_models\` with a non-confidential task description and
96
- \`endpointIds\` set to the saved inventory, including \`[]\` when empty.
97
- Existing-only never broadens. For existing-first, if no existing candidate fits,
98
- explain the gap and ask before a separate search without \`endpointIds\`. Send
97
+ Call \`scout_models\` with a non-confidential task description as \`prompt\` and
98
+ \`endpointIds\` set to the saved inventory, including \`[]\` when empty. Send
99
99
  only task requirements, never source files, secrets or real customer records.
100
-
101
- Compare model AND endpoint, evidence, reference cost/time, unknowns and
102
- warnings. For multiple tasks read \`tasks[].slots[0].recommendations\`; the
103
- top-level list is empty. Prefer the cheapest usable option the quality evidence
104
- supports, without claiming benchmarks prove application accuracy. Use the
105
- returned provider model ID and the project's actual SDK and protocol.
100
+ If the MCP tool is unavailable, run
101
+ \`npx -y openmerit@latest scout "<task>" --json\`; it applies the saved inventory.
102
+
103
+ Scout is a conversation with the agent behind openmerit.ai/scout. When
104
+ \`status\` is \`needs_answers\`, fill in what the user or project already states,
105
+ show the user Scout's brief and remaining questions, and wait for their answers.
106
+ Then call again with the returned \`conversationId\` and \`brief\` set to the
107
+ rewritten components, or \`skip: true\` if the user wants Scout to assume. Never
108
+ answer Scout's questions with guesses. Send later refinements to the same
109
+ \`conversationId\`. A call with only a \`conversationId\` reads an interrupted turn.
110
+
111
+ When \`status\` is \`recommended\`, Scout has split the work into \`tasks\`, one
112
+ per model, each with up to five ranked \`models\`; cover every task. Each
113
+ model's \`cost\` names the \`endpointId\` and \`providerModelId\` to call.
114
+ Existing-only never broadens. For existing-first, if no existing candidate fits a
115
+ task, explain the gap and ask before a new conversation without \`endpointIds\`.
116
+ Compare quality evidence, reference cost/time and watch-outs. Prefer the cheapest
117
+ usable option the evidence supports, without claiming benchmarks prove
118
+ application accuracy. Use the project's actual SDK and protocol.
106
119
 
107
120
  ## Boundaries
108
121
 
@@ -1 +1 @@
1
- {"format":1,"contract":"scout-recommend-v1","version":"0.1.12","skill":"---\nname: openmerit-scout\ndescription: Consults OpenMerit Scout before choosing, adding, downloading, replacing or configuring any application inference model, including implicit choices while building features, pretrained browser/local models, SDK defaults, embeddings, OCR, speech, classification, detection, segmentation, LLMs and agents. Not for unrelated edits, ordinary inference calls or this coding agent's own model.\n---\n\n# OpenMerit · Scout\n\nBefore introducing, recommending, replacing or configuring an application\ninference model, call the `scout_models` tool on the `openmerit` MCP server.\nThis applies even when the user never mentions models or Scout. Downloading local\nweights, using a pretrained browser model or accepting an SDK default is a model\ndecision: consult Scout before model-specific code, installs or downloads.\nPreserve the user's explicit model choice; assess it rather than silently replacing\nit. Leave unchanged, approved application selections in place.\n\n## Project policy\n\nRead `.openmerit/scout.json` before searching. If it is absent, ask once whether this\nproject uses **existing-only** (only endpoints it can already reach) or\n**existing-first** (prefer them, ask before searching the whole catalogue), and\nwhich public catalogue endpoint IDs it can access. Save the answer:\n\n```sh\nnpx -y openmerit@latest project --policy existing-only --endpoint-id <id> ...\n```\n\nNever infer access from a model name, SDK or environment-variable name. Never\nrecord credentials, private URLs, prompts or customer data. An empty inventory\nis not permission to search all models.\n\n## Searching\n\nCall `scout_models` with a non-confidential task description and\n`endpointIds` set to the saved inventory, including `[]` when empty.\nExisting-only never broadens. For existing-first, if no existing candidate fits,\nexplain the gap and ask before a separate search without `endpointIds`. Send\nonly task requirements, never source files, secrets or real customer records.\n\nCompare model AND endpoint, evidence, reference cost/time, unknowns and\nwarnings. For multiple tasks read `tasks[].slots[0].recommendations`; the\ntop-level list is empty. Prefer the cheapest usable option the quality evidence\nsupports, without claiming benchmarks prove application accuracy. Use the\nreturned provider model ID and the project's actual SDK and protocol.\n\n## Boundaries\n\nAsk before adding providers or credentials, spending, deploying or external\nwrites. Report recommendation, endpoint connectivity and application checks as\nseparate results, each only after it succeeds. Do not switch this coding agent's\nown model.\n\nIf Scout fails or has no suitable candidate/evidence for the required deployment,\nsay so and pause the model decision; do not substitute a remembered recommendation\nor SDK default. Continue only model-independent work. Local/browser models are not\nexempt when the catalogue lacks coverage. If it reports that the device key is missing,\nexpired or revoked, run `npx -y openmerit@latest login` and then\n`npx -y openmerit@latest install` again. Never ask the user to paste a key.\n","rule":"# Application model decisions · OpenMerit Scout\n\nBefore choosing, recommending, adding, downloading, replacing or configuring\nANY application inference model, load the `openmerit-scout` skill and consult\n`scout_models` on the `openmerit` MCP server before committing to a model.\nThis applies even when the user only asks you to build a feature, never mentions\nmodels or Scout, or names a model themselves (assess it; do not silently replace it).\n\nA pretrained model, local weights, browser inference, an SDK's default model,\nembeddings, OCR, speech, image classification, detection and segmentation all\ncount as model choices, not implementation details. Identify this decision before\nwriting model-specific code, installing a model package or downloading weights.\n\nFollow the skill's project access policy. If it is missing, ask and save it first.\nIf Scout is unavailable, fails or has no suitable evidence/candidate, disclose the\ngap and pause the model decision; do not fall back to a remembered or SDK-default\nmodel. You may continue work that does not commit to a model.\n\nDo not consult Scout for unrelated edits, normal inference calls using an unchanged\napproved model, or this coding agent's own model. Do not reselect approved models\nunless the task introduces or changes a model decision.\n\nWhen the user says \"update openmerit\", run\n`npx -y openmerit@latest update --scope user` (use `--scope project` if this\nrule is in the project's .claude/rules directory). Report the result, including\nany conflict or manual-upgrade requirement. Do not delete edited files, bypass\npermissions or rerun login automatically. A new session loads updated instructions.\n"}
1
+ {"format":1,"contract":"scout-recommend-v1","version":"0.3.0","skill":"---\nname: openmerit-scout\ndescription: Consults OpenMerit Scout before choosing, adding, downloading, replacing or configuring any application inference model, including implicit choices while building features, pretrained browser/local models, SDK defaults, embeddings, OCR, speech, classification, detection, segmentation, LLMs and agents. Not for unrelated edits, ordinary inference calls or this coding agent's own model.\n---\n\n# OpenMerit · Scout\n\nBefore introducing, recommending, replacing or configuring an application\ninference model, call the `scout_models` tool on the `openmerit` MCP server.\nThis applies even when the user never mentions models or Scout. Downloading local\nweights, using a pretrained browser model or accepting an SDK default is a model\ndecision: consult Scout before model-specific code, installs or downloads.\nPreserve the user's explicit model choice; assess it rather than silently replacing\nit. Leave unchanged, approved application selections in place.\n\n## Project policy\n\nRead `.openmerit/scout.json` before searching. If it is absent, ask once whether this\nproject uses **existing-only** (only endpoints it can already reach) or\n**existing-first** (prefer them, ask before searching the whole catalogue), and\nwhich public catalogue endpoint IDs it can access. Save the answer:\n\n```sh\nnpx -y openmerit@latest project --policy existing-only --endpoint-id <id> ...\n```\n\nNever infer access from a model name, SDK or environment-variable name. Never\nrecord credentials, private URLs, prompts or customer data. An empty inventory\nis not permission to search all models.\n\n## Searching\n\nCall `scout_models` with a non-confidential task description as `prompt` and\n`endpointIds` set to the saved inventory, including `[]` when empty. Send\nonly task requirements, never source files, secrets or real customer records.\nIf the MCP tool is unavailable, run\n`npx -y openmerit@latest scout \"<task>\" --json`; it applies the saved inventory.\n\nScout is a conversation with the agent behind openmerit.ai/scout. When\n`status` is `needs_answers`, fill in what the user or project already states,\nshow the user Scout's brief and remaining questions, and wait for their answers.\nThen call again with the returned `conversationId` and `brief` set to the\nrewritten components, or `skip: true` if the user wants Scout to assume. Never\nanswer Scout's questions with guesses. Send later refinements to the same\n`conversationId`. A call with only a `conversationId` reads an interrupted turn.\n\nWhen `status` is `recommended`, Scout has split the work into `tasks`, one\nper model, each with up to five ranked `models`; cover every task. Each\nmodel's `cost` names the `endpointId` and `providerModelId` to call.\nExisting-only never broadens. For existing-first, if no existing candidate fits a\ntask, explain the gap and ask before a new conversation without `endpointIds`.\nCompare quality evidence, reference cost/time and watch-outs. Prefer the cheapest\nusable option the evidence supports, without claiming benchmarks prove\napplication accuracy. Use the project's actual SDK and protocol.\n\n## Boundaries\n\nAsk before adding providers or credentials, spending, deploying or external\nwrites. Report recommendation, endpoint connectivity and application checks as\nseparate results, each only after it succeeds. Do not switch this coding agent's\nown model.\n\nIf Scout fails or has no suitable candidate/evidence for the required deployment,\nsay so and pause the model decision; do not substitute a remembered recommendation\nor SDK default. Continue only model-independent work. Local/browser models are not\nexempt when the catalogue lacks coverage. If it reports that the device key is missing,\nexpired or revoked, run `npx -y openmerit@latest login` and then\n`npx -y openmerit@latest install` again. Never ask the user to paste a key.\n","rule":"# Application model decisions · OpenMerit Scout\n\nBefore choosing, recommending, adding, downloading, replacing or configuring\nANY application inference model, load the `openmerit-scout` skill and consult\n`scout_models` on the `openmerit` MCP server before committing to a model.\nThis applies even when the user only asks you to build a feature, never mentions\nmodels or Scout, or names a model themselves (assess it; do not silently replace it).\n\nA pretrained model, local weights, browser inference, an SDK's default model,\nembeddings, OCR, speech, image classification, detection and segmentation all\ncount as model choices, not implementation details. Identify this decision before\nwriting model-specific code, installing a model package or downloading weights.\n\nFollow the skill's project access policy. If it is missing, ask and save it first.\nScout may answer with clarifying questions: relay them to the user and continue\nthe same Scout conversation with their answers before choosing.\nIf Scout is unavailable, fails or has no suitable evidence/candidate, disclose the\ngap and pause the model decision; do not fall back to a remembered or SDK-default\nmodel. You may continue work that does not commit to a model.\n\nDo not consult Scout for unrelated edits, normal inference calls using an unchanged\napproved model, or this coding agent's own model. Do not reselect approved models\nunless the task introduces or changes a model decision.\n\nWhen the user says \"update openmerit\", run\n`npx -y openmerit@latest update --scope user` (use `--scope project` if this\nrule is in the project's .claude/rules directory). Report the result, including\nany conflict or manual-upgrade requirement. Do not delete edited files, bypass\npermissions or rerun login automatically. A new session loads updated instructions.\n"}
@@ -1,9 +1,7 @@
1
1
  import { spawn } from 'node:child_process';
2
2
 
3
- // By default, only open the loopback address returned by our own server.
4
- // Callers can pass a stricter or different check. Never use a shell.
5
- const terminalURL = url => { const target = new URL(url); return target.protocol === 'http:' && target.hostname === '127.0.0.1' && /^http:\/\/127\.0\.0\.1:[0-9]+$/.test(url) && !target.username && !target.password && target.pathname === '/' && !target.search && !target.hash; };
6
- export function browserCommand(url, platform = process.platform, env = process.env, allowed = terminalURL) {
3
+ // Callers decide which URLs may open. Never use a shell.
4
+ export function browserCommand(url, platform = process.platform, env = process.env, allowed) {
7
5
  if (!allowed(url)) throw new Error('Refusing to open an unexpected URL.');
8
6
  if (env.SSH_CONNECTION || env.SSH_TTY) return null;
9
7
  if (platform === 'darwin') return ['open', [url]];
@@ -1,75 +1,25 @@
1
1
  #!/usr/bin/env node
2
- import { resolve } from 'node:path';
3
- import { stat } from 'node:fs/promises';
4
- import { startTerminal } from './server.mjs';
5
- import { createCollector, loadConfig } from './collect.mjs';
6
- import { sampleSnapshot } from './sample.mjs';
7
- import { openBrowser } from './browser.mjs';
8
2
  import { commandTelemetry } from './telemetry.mjs';
9
3
 
10
- const help = `OpenMerit · Inference Terminal
4
+ const help = `OpenMerit
11
5
 
12
- openmerit dash [path] [options]
6
+ openmerit scout --help Ask Scout which models fit your tasks
13
7
  openmerit install --help Connect OpenMerit to your coding agent
14
8
  openmerit update --help Update installed Scout instructions
15
- openmerit eval --help Evaluate CLI changes across coding agents
16
-
17
- --port <number> Require a specific local port (default: 4310 or a free port)
18
- --interval <seconds> Collection interval, 10–30 (default: 15)
19
- --no-discovery Skip project/log discovery and extra runtime discovery
20
- --no-open Print the URL without opening a browser
21
- --json Print one snapshot for an agent, then exit
22
- --diagnostics Print shareable index coverage counts, then exit
23
- --demo Explore a clearly labeled fictional environment
24
- --help Show this help
25
-
26
- Opens your default browser after the local terminal is ready.
27
- Reads application metadata and local runtime inventory. Makes no inference calls.
28
- Coding-harness usage is outside this terminal's scope.
9
+ openmerit --help Show this help
29
10
  `;
30
11
 
31
12
  async function main() {
32
13
  const args = process.argv.slice(2);
33
14
  if (['install', 'status', 'remove', 'login', 'logout', 'project', 'doctor', 'update'].includes(args[0])) { const { main } = await import('./install.mjs'); process.exitCode = await main(args); return; }
34
- if (args[0] === 'eval') { const { main } = await import('../../eval/src/cli.mjs'); await main(args.slice(1)); return; }
15
+ if (args[0] === 'scout') { const { main } = await import('./scout.js'); process.exitCode = await main(args.slice(1)); return; }
35
16
  if (!args.length || args.includes('--help') || args.includes('-h')) { console.log(help); return; }
36
- if (args.shift() !== 'dash') throw new Error('Unknown command. Run openmerit --help.');
37
- let root = process.cwd(), port = 4310, interval = 15, demo = false, json = false, pathSeen = false, discovery = true, noOpen = false, explicitPort = false, diagnostics = false;
38
- for (let i = 0; i < args.length; i++) {
39
- const arg = args[i];
40
- if (arg === '--port' || arg === '--interval') {
41
- const value = args[++i];
42
- if (!/^\d+$/.test(value ?? '')) throw new Error(`${arg} requires an integer.`);
43
- if (arg === '--port') { port = Number(value); explicitPort = true; } else interval = Number(value);
44
- } else if (arg === '--no-open') noOpen = true;
45
- else if (arg === '--no-discovery') discovery = false;
46
- else if (arg === '--access' || arg === '--revoke-access') throw new Error('Coding-harness history is no longer collected. Saved history permissions are ignored; these access options have been removed.');
47
- else if (arg === '--demo') demo = true;
48
- else if (arg === '--json') json = true;
49
- else if (arg === '--diagnostics') diagnostics = true;
50
- else if (!arg.startsWith('-') && !pathSeen) { root = resolve(arg); pathSeen = true; }
51
- else throw new Error(`Unknown argument: ${arg}`);
52
- }
53
- if (port < 1 || port > 65535) throw new Error('Port must be between 1 and 65535.');
54
- if (interval < 10 || interval > 30) throw new Error('Collection interval must be between 10 and 30 seconds.');
55
- if (!(await stat(root)).isDirectory()) throw new Error('Choose a project directory.');
56
- const config = demo ? null : await loadConfig(root);
57
- if (config && !discovery) config.discovery = false;
58
- if (diagnostics) { const {indexRepository,indexDiagnostics}=await import('./repository-index.mjs'); const index = !demo && config.discovery ? await indexRepository(root) : null; console.log(JSON.stringify(indexDiagnostics(index), null, 2)); return; }
59
- if (json) { console.log(JSON.stringify(demo ? sampleSnapshot() : await createCollector(root, config, interval)(), null, 2)); return; }
60
- const terminal = await startTerminal({ root, port, interval, demo, config, fallbackPort: !explicitPort });
61
- console.log(`\n OpenMerit · Inference Terminal\n\n ${terminal.url}\n\n ${demo ? 'Sample environment · fictional data' : 'Current project · passive collection'}\n Updates every ${interval}s. Press Ctrl+C to close.\n`);
62
- const close = () => { void terminal.close().then(() => process.exit(0)); };
63
- process.once('SIGINT', close); process.once('SIGTERM', close);
64
- if (!demo && config.discovery) {
65
- console.log(' Automatic discovery: repository code/configuration, saved request logs, and project-linked runtimes.');
66
- }
67
- if (!noOpen && !await openBrowser(terminal.url)) console.log(' Open the local URL above in your browser.');
17
+ throw new Error('Unknown command. Run openmerit --help.');
68
18
  }
69
19
  const completeTelemetry = commandTelemetry(process.argv.slice(2));
70
20
  const setupCodes = { 10: 'approval_pending', 11: 'access_denied', 20: 'install_conflict', 30: 'openmerit_unreachable' };
71
21
  main().then(() => completeTelemetry(process.exitCode ? 'error' : 'ok', undefined, setupCodes[process.exitCode]), async error => {
72
- console.error(`\n ${error.code === 'EADDRINUSE' ? 'Port is in use. Choose another with --port.' : error.message}\n`);
22
+ console.error(`\n ${error.message}\n`);
73
23
  process.exitCode = 1;
74
24
  await completeTelemetry('error', error);
75
25
  });
@@ -15,13 +15,13 @@ export { rule, skill } from './scout-instructions.js';
15
15
  // One-command coding-agent setup: device sign-in in the browser, then the MCP
16
16
  // server, startup rule and Scout skill. Built to be run by a person or by a coding agent:
17
17
  // no interactive prompts, bounded waits, resumable logins and explicit exit codes.
18
- export const exitCodes = { ok: 0, error: 1, pending: 10, denied: 11, conflict: 20, unreachable: 30 };
18
+ const exitCodes = { ok: 0, error: 1, pending: 10, denied: 11, conflict: 20, unreachable: 30 };
19
19
  const exec = promisify(execFile);
20
20
  const hash = value => createHash('sha256').update(value).digest('hex');
21
21
  const policyPath = '.openmerit/scout.json';
22
22
  const clients = { 'claude-code': 'Claude Code', pi: 'Pi' };
23
23
 
24
- export const help = `OpenMerit · coding-agent setup
24
+ const help = `OpenMerit · coding-agent setup
25
25
 
26
26
  openmerit install [--client claude-code|pi] [--scope user|project] [--setup-id UUID] [--auto-update on|off] [--plan] [--json] [--no-open]
27
27
  openmerit status [--client claude-code|pi] [--scope user|project] [--check] [--json]
@@ -31,6 +31,7 @@ export const help = `OpenMerit · coding-agent setup
31
31
  openmerit project --policy existing-only|existing-first [--endpoint-id <id> ...] [--plan]
32
32
  openmerit doctor [--client claude-code|pi] [--scope user|project] [--json]
33
33
  openmerit update [--client claude-code|pi] [--scope user|project] [--auto on|off] [--json]
34
+ openmerit scout --help Ask Scout from the terminal
34
35
 
35
36
  install signs this device in through the browser (one Approve click), adds the
36
37
  OpenMerit MCP server, startup rule and Scout skill, then verifies the connection.
@@ -12,6 +12,8 @@ count as model choices, not implementation details. Identify this decision befor
12
12
  writing model-specific code, installing a model package or downloading weights.
13
13
 
14
14
  Follow the skill's project access policy. If it is missing, ask and save it first.
15
+ Scout may answer with clarifying questions: relay them to the user and continue
16
+ the same Scout conversation with their answers before choosing.
15
17
  If Scout is unavailable, fails or has no suitable evidence/candidate, disclose the
16
18
  gap and pause the model decision; do not fall back to a remembered or SDK-default
17
19
  model. You may continue work that does not commit to a model.
@@ -58,17 +60,28 @@ is not permission to search all models.
58
60
 
59
61
  ## Searching
60
62
 
61
- Call \`scout_models\` with a non-confidential task description and
62
- \`endpointIds\` set to the saved inventory, including \`[]\` when empty.
63
- Existing-only never broadens. For existing-first, if no existing candidate fits,
64
- explain the gap and ask before a separate search without \`endpointIds\`. Send
63
+ Call \`scout_models\` with a non-confidential task description as \`prompt\` and
64
+ \`endpointIds\` set to the saved inventory, including \`[]\` when empty. Send
65
65
  only task requirements, never source files, secrets or real customer records.
66
-
67
- Compare model AND endpoint, evidence, reference cost/time, unknowns and
68
- warnings. For multiple tasks read \`tasks[].slots[0].recommendations\`; the
69
- top-level list is empty. Prefer the cheapest usable option the quality evidence
70
- supports, without claiming benchmarks prove application accuracy. Use the
71
- returned provider model ID and the project's actual SDK and protocol.
66
+ If the MCP tool is unavailable, run
67
+ \`npx -y openmerit@latest scout "<task>" --json\`; it applies the saved inventory.
68
+
69
+ Scout is a conversation with the agent behind openmerit.ai/scout. When
70
+ \`status\` is \`needs_answers\`, fill in what the user or project already states,
71
+ show the user Scout's brief and remaining questions, and wait for their answers.
72
+ Then call again with the returned \`conversationId\` and \`brief\` set to the
73
+ rewritten components, or \`skip: true\` if the user wants Scout to assume. Never
74
+ answer Scout's questions with guesses. Send later refinements to the same
75
+ \`conversationId\`. A call with only a \`conversationId\` reads an interrupted turn.
76
+
77
+ When \`status\` is \`recommended\`, Scout has split the work into \`tasks\`, one
78
+ per model, each with up to five ranked \`models\`; cover every task. Each
79
+ model's \`cost\` names the \`endpointId\` and \`providerModelId\` to call.
80
+ Existing-only never broadens. For existing-first, if no existing candidate fits a
81
+ task, explain the gap and ask before a new conversation without \`endpointIds\`.
82
+ Compare quality evidence, reference cost/time and watch-outs. Prefer the cheapest
83
+ usable option the evidence supports, without claiming benchmarks prove
84
+ application accuracy. Use the project's actual SDK and protocol.
72
85
 
73
86
  ## Boundaries
74
87
 
@@ -26,7 +26,7 @@ export function piPaths(root, scope) {
26
26
  const base = scope === 'user' ? agentDir : join(root, '.pi');
27
27
  return { skill: join(base, 'skills/openmerit-scout/SKILL.md'), rule: join(base, 'extensions/openmerit-scout.ts'),
28
28
  connection: join(base, 'extensions/openmerit-scout.json'), record: join(base, 'openmerit-install.json'),
29
- legacyRule: join(base, 'rules/openmerit.md'), mcpFile: join(base, 'mcp.json'), mcpScope: scope, agentDir };
29
+ mcpFile: join(base, 'mcp.json'), mcpScope: scope, agentDir };
30
30
  }
31
31
  export async function inspectPi(root, scope) {
32
32
  const paths = piPaths(root, scope);
@@ -0,0 +1,181 @@
1
+ // `openmerit scout`: a conversation with Scout, the agent behind openmerit.ai/scout,
2
+ // for coding agents without the MCP server and for people at a terminal. It calls
3
+ // the same scout_models MCP tool with this device's key, so every client gets the
4
+ // same clarifying questions and per-task shortlists.
5
+ import { readFile } from 'node:fs/promises';
6
+ import { join } from 'node:path';
7
+ import { credentialsPath, readCredentials } from "./scout-credentials.js";
8
+ const policyFile = '.openmerit/scout.json';
9
+ export const components = ['input', 'work', 'output', 'criteria', 'constraints'];
10
+ const help = `OpenMerit · Scout
11
+
12
+ openmerit scout "<the work models need to do>" [--all-endpoints] [--json]
13
+ openmerit scout --conversation <id> [--brief <component>=<text> ...] [--skip] ["<follow-up>"] [--json]
14
+
15
+ Asks Scout, the agent behind openmerit.ai/scout. Scout may first return a task
16
+ brief with clarifying questions: answer them with --brief (components: input,
17
+ work, output, criteria, constraints) in the same conversation, or --skip to let
18
+ Scout assume. It then splits the work into tasks and recommends up to five
19
+ models for each. A turn usually takes one to three minutes.
20
+
21
+ A new conversation is limited to the endpoint IDs in ${policyFile}
22
+ (set with openmerit project). --all-endpoints searches the whole catalogue and is
23
+ refused under the existing-only policy. Uses the device key that
24
+ "openmerit install" saved for Claude Code or Pi, this project's first. Never include secrets, source files or customer records.
25
+ `;
26
+ const isObject = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
27
+ async function readJSON(path) {
28
+ try {
29
+ const value = JSON.parse(await readFile(path, 'utf8'));
30
+ return isObject(value) ? value : null;
31
+ }
32
+ catch (error) {
33
+ if (error.code === 'ENOENT')
34
+ return null;
35
+ throw new Error(`Could not read ${path}.`);
36
+ }
37
+ }
38
+ // The device key an installation saved: this project's installation first, then the user's.
39
+ function deviceKey(root) {
40
+ for (const scope of ['project', 'user'])
41
+ for (const client of ['claude-code', 'pi']) {
42
+ const credentials = readCredentials(credentialsPath(client, scope, root));
43
+ if (credentials)
44
+ return credentials;
45
+ }
46
+ return null;
47
+ }
48
+ // One turn through the scout_models MCP tool. Errors are written for the person or agent running it.
49
+ async function askScout(root, request, signal) {
50
+ const credentials = deviceKey(root);
51
+ if (!credentials)
52
+ throw new Error('This device is not signed in to OpenMerit. Run: npx -y openmerit@latest install');
53
+ const server = credentials.server;
54
+ let response;
55
+ try {
56
+ response = await fetch(`${server}/scout/mcp`, {
57
+ method: 'POST', redirect: 'error',
58
+ // A turn can take minutes; Scout keeps working if the connection drops.
59
+ signal: AbortSignal.any([AbortSignal.timeout(10 * 60_000), ...(signal ? [signal] : [])]),
60
+ headers: { ...credentials.headers, 'Content-Type': 'application/json', Authorization: `Bearer ${credentials.key}`, 'X-Scout-Client': 'cli' },
61
+ body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: 'scout_models', arguments: request } }),
62
+ });
63
+ }
64
+ catch (error) {
65
+ const id = request.conversationId;
66
+ throw new Error(error.name === 'TimeoutError' && id ? `Scout is still working. Read the outcome with: openmerit scout --conversation ${id}` : `Could not reach ${server}. Check your connection and retry.`);
67
+ }
68
+ if (response.status === 401)
69
+ throw new Error('The device key was rejected. Run: npx -y openmerit@latest login, then install again.');
70
+ const body = await response.json().catch(() => null);
71
+ const result = isObject(body) && isObject(body.result) ? body.result : null;
72
+ if (!result)
73
+ throw new Error(`Scout returned HTTP ${response.status}${isObject(body) && isObject(body.error) ? `: ${String(body.error.message)}` : ''}.`);
74
+ const text = Array.isArray(result.content) && isObject(result.content[0]) ? String(result.content[0].text) : '';
75
+ if (result.isError && !isObject(result.structuredContent))
76
+ throw new Error(text || 'Scout could not finish this turn.');
77
+ return result.structuredContent;
78
+ }
79
+ // A turn under the project's access policy. A new conversation is limited to the saved
80
+ // inventory; the whole catalogue needs an explicit choice the policy permits.
81
+ export async function scoutTurn(root, request, signal) {
82
+ const { allEndpoints, ...turn } = request;
83
+ if (turn.conversationId) {
84
+ if (allEndpoints)
85
+ throw new Error('--all-endpoints applies when a conversation starts. Start a new conversation instead.');
86
+ return askScout(root, turn, signal);
87
+ }
88
+ const policy = await readJSON(join(root, policyFile));
89
+ if (!policy) {
90
+ if (!allEndpoints)
91
+ throw new Error(`${policyFile} is missing. Ask which endpoints this project can use and save them with "npx -y openmerit@latest project --policy existing-only|existing-first --endpoint-id <id> ...", or pass --all-endpoints to search the whole catalogue.`);
92
+ return askScout(root, turn, signal);
93
+ }
94
+ const ids = Array.isArray(policy.endpointIds) && policy.endpointIds.every(id => typeof id === 'string') ? policy.endpointIds : null;
95
+ if (!ids || !['existing-only', 'existing-first'].includes(String(policy.policy)))
96
+ throw new Error(`${policyFile} is not a valid Scout policy. Save it again with "openmerit project".`);
97
+ if (allEndpoints && policy.policy === 'existing-only')
98
+ throw new Error('This project uses the existing-only policy, so Scout cannot search the whole catalogue.');
99
+ return askScout(root, allEndpoints ? turn : { ...turn, endpointIds: ids }, signal);
100
+ }
101
+ const money = (usd) => `$${usd < 0.1 ? usd.toFixed(3) : usd.toFixed(2)}`;
102
+ const rankings = { quality: 'Highest quality estimate first.', latency: 'Fastest first.', recency: 'Newest first.' };
103
+ export function renderScout(result) {
104
+ const lines = [];
105
+ if (result.status === 'needs_answers') {
106
+ lines.push('Scout needs more detail. Its task brief:', '');
107
+ for (const c of components) {
108
+ const part = result.brief?.[c];
109
+ lines.push(` ${c.padEnd(12)}${part?.text || '(missing)'}${part ? ` [${part.source}]` : ''}`);
110
+ }
111
+ lines.push('', 'Questions:', ...(result.questions ?? []).map(q => ` ${q.component}: ${q.question}`), '', `Answer: openmerit scout --conversation ${result.conversationId} --brief <component>="<answer>" ...`, `Or skip: openmerit scout --conversation ${result.conversationId} --skip`);
112
+ }
113
+ else if (result.status === 'recommended') {
114
+ const tasks = result.tasks ?? [];
115
+ tasks.forEach((task, i) => {
116
+ if (tasks.length > 1)
117
+ lines.push(`${i + 1}. ${task.title}`, ` ${task.work}`);
118
+ // As on the page, the default cheapest-first order is unlabelled; another priority is named with the value it sorts by.
119
+ const priority = task.priority ?? 'cost';
120
+ if (priority !== 'cost')
121
+ lines.push(` ${rankings[priority]}`);
122
+ for (const m of task.models) {
123
+ const price = m.cost ? `${money(m.cost.inputPerMillion)} in / ${money(m.cost.outputPerMillion)} out per 1M via ${m.cost.route} (${m.cost.endpointId})` : 'no priced route';
124
+ const metric = priority === 'latency' ? ` · ${m.latency ? `${m.latency.seconds.toFixed(1)}s per task` : 'time unknown'}`
125
+ : priority === 'recency' ? ` · ${m.recency ? `released ${m.recency.released}` : 'release date unknown'}` : '';
126
+ lines.push(` ${m.rank}. ${m.name}${metric} · quality ${m.quality.score}/100 · ${price}`, ` ${m.rationale}`);
127
+ }
128
+ lines.push(` ${task.summary}`, '');
129
+ });
130
+ if (result.assumptions?.length)
131
+ lines.push('Assumptions:', ...result.assumptions.map(a => ` - ${a}`), '');
132
+ lines.push(`Refine: openmerit scout --conversation ${result.conversationId} "<follow-up>"`);
133
+ lines.push('Quality is Scout\'s estimate from public benchmarks, not measured accuracy for your application.');
134
+ }
135
+ else if (result.status === 'working')
136
+ lines.push(`Scout is still working. Read the outcome with: openmerit scout --conversation ${result.conversationId}`);
137
+ else
138
+ lines.push(result.text ?? result.error ?? 'Scout replied without an answer.');
139
+ return lines.join('\n');
140
+ }
141
+ export async function main(args, { root = process.cwd(), print = console.log } = {}) {
142
+ if (args.includes('--help') || args.includes('-h')) {
143
+ print(help);
144
+ return 0;
145
+ }
146
+ const request = {};
147
+ const words = [];
148
+ let json = false;
149
+ for (let i = 0; i < args.length; i++) {
150
+ const arg = args[i] ?? '';
151
+ const value = () => { const v = args[++i]; if (v === undefined || v.startsWith('--'))
152
+ throw new Error(`${arg} needs a value.`); return v; };
153
+ if (arg === '--conversation')
154
+ request.conversationId = value();
155
+ else if (arg === '--brief') {
156
+ const [component, ...text] = value().split('=');
157
+ if (!components.includes(component ?? '') || !text.length)
158
+ throw new Error(`--brief takes <component>=<text>; components: ${components.join(', ')}.`);
159
+ request.brief = { ...request.brief, [component]: text.join('=') };
160
+ }
161
+ else if (arg === '--skip')
162
+ request.skip = true;
163
+ else if (arg === '--all-endpoints')
164
+ request.allEndpoints = true;
165
+ else if (arg === '--json')
166
+ json = true;
167
+ else if (arg.startsWith('-'))
168
+ throw new Error(`Unknown option for scout: ${arg}`);
169
+ else
170
+ words.push(arg);
171
+ }
172
+ if (words.length)
173
+ request.prompt = words.join(' ');
174
+ if (!request.prompt && !request.conversationId) {
175
+ print(help);
176
+ return 1;
177
+ }
178
+ const result = await scoutTurn(root, request);
179
+ print(json ? JSON.stringify(result, null, 2) : renderScout(result));
180
+ return result.status === 'error' ? 1 : 0;
181
+ }