openmerit 0.1.12 → 0.2.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.
- package/CHANGELOG.md +8 -0
- package/README.md +5 -8
- package/dist/pi/scout-extension.mjs +23 -10
- package/dist/terminal/public/app.js +37 -37
- package/dist/terminal/public/styles.css +0 -125
- package/dist/terminal/scout-update.json +1 -1
- package/dist/terminal/src/cli.mjs +2 -0
- package/dist/terminal/src/install.mjs +1 -0
- package/dist/terminal/src/scout-instructions.d.ts +2 -2
- package/dist/terminal/src/scout-instructions.js +23 -10
- package/dist/terminal/src/scout-instructions.ts +23 -10
- package/dist/terminal/src/scout.d.ts +77 -0
- package/dist/terminal/src/scout.js +181 -0
- package/dist/terminal/src/scout.ts +164 -0
- package/dist/terminal/src/server.mjs +1 -1
- package/docs/commands.md +2 -78
- package/docs/getting-started.md +2 -2
- package/docs/index.md +4 -13
- package/docs/inference-terminal.md +2 -63
- package/docs/pi-extension.md +1 -1
- package/docs/roadmap.md +4 -17
- package/docs/scout.md +29 -250
- package/docs/security.md +5 -17
- package/docs/testing.md +7 -35
- package/docs/troubleshooting.md +1 -1
- package/package.json +4 -24
- package/dist/core/src/index.d.ts +0 -101
- package/dist/core/src/index.js +0 -1649
- package/dist/core/src/store.d.ts +0 -35
- package/dist/core/src/store.js +0 -102
- package/dist/protocol/src/index.d.ts +0 -484
- package/dist/protocol/src/index.js +0 -47
- package/dist/protocol/src/schemas.d.ts +0 -576
- package/dist/protocol/src/schemas.js +0 -280
- package/dist/terminal/public/brands/vllm.ico +0 -0
- package/dist/terminal/public/flow.css +0 -1
- package/dist/terminal/public/flow.js +0 -770
- package/dist/terminal/src/demo-policy.mjs +0 -205
- package/dist/terminal/src/enterprise-sample.mjs +0 -151
- package/docs/adapter-guide.md +0 -191
- package/docs/architecture.md +0 -61
- package/docs/automation.md +0 -76
- package/docs/budgets.md +0 -39
- package/docs/demo-backfill.md +0 -31
- package/docs/demo-fieldkit.md +0 -49
- package/docs/demo-placement.md +0 -32
- package/docs/demo-spam.md +0 -17
- package/docs/demo-support.md +0 -44
- package/docs/demo.md +0 -74
- package/docs/first-trial.md +0 -62
- package/docs/interactive-demo.md +0 -118
- package/docs/lifecycle.md +0 -32
- package/docs/metrics-and-evidence.md +0 -48
- package/docs/operations.md +0 -40
- package/docs/pareto-spec.md +0 -76
- package/docs/ux-reference.md +0 -32
|
@@ -914,133 +914,8 @@ progress:indeterminate { opacity:.55; }
|
|
|
914
914
|
.terminal-loading progress { max-width:260px;margin-bottom:16px; }
|
|
915
915
|
.terminal-loading>span { color:var(--muted);font-size:12px; }
|
|
916
916
|
@media(max-width:700px) { .index-progress { grid-template-columns:1fr auto;gap:14px; }.index-progress progress { grid-column:1 / -1;grid-row:2; }.index-progress small { line-height:1.5; } }
|
|
917
|
-
/* Hosted comparison uses the terminal's existing typography and neutral surfaces. */
|
|
918
|
-
.sample-disclosure a { color: inherit; white-space: nowrap; text-decoration: none; }
|
|
919
|
-
.sample-disclosure a:hover { color: var(--text); }
|
|
920
|
-
.sample-method { display: flex; justify-content: flex-end; margin: -10px 0 18px; font-size: 11px; }
|
|
921
|
-
.comparison { margin: 0 0 32px; padding: 28px 30px 20px; border: 1px solid var(--line-strong); border-radius: 16px; background: var(--stack); box-shadow: inset 0 1px 0 var(--panel-glint); }
|
|
922
|
-
.comparison-top { display: flex; align-items: center; gap: 28px; }
|
|
923
|
-
.comparison-bottom { display: grid; grid-template-columns: minmax(0,1fr) 24px minmax(0,1fr) minmax(0,1fr); align-items: center; gap: 26px; padding: 26px 0; border-block: 1px solid var(--line); }
|
|
924
|
-
.comparison-top { justify-content: space-between; margin-bottom: 26px; }
|
|
925
|
-
.comparison h2 { margin: 0 0 8px; font-size: 28px; line-height: 1.2; font-weight: 500; letter-spacing: -.85px; }
|
|
926
|
-
.comparison-top p { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.6; }
|
|
927
|
-
.comparison-switch { display: flex; padding: 4px; gap: 3px; border: 1px solid var(--track); border-radius: 9px; background: var(--bg); flex-shrink: 0; }
|
|
928
|
-
.comparison-switch button { border: 0; border-radius: 5px; background: transparent; color: var(--quiet); padding: 11px 16px; font: inherit; font-size: 12px; cursor: pointer; transition: background-color 160ms ease-out, color 160ms ease-out, transform 160ms ease-out; }
|
|
929
|
-
.comparison-switch button[aria-pressed="true"] { background: var(--accent); color: var(--on-accent); box-shadow: 0 1px 3px #0003; }
|
|
930
|
-
.comparison-switch button:active { transform: scale(.98); }
|
|
931
|
-
.comparison-switch button:focus-visible { outline: 2px solid var(--muted); outline-offset: 3px; }
|
|
932
|
-
.comparison-cost { display: grid; gap: 10px; min-width: 0; color: var(--muted); transition: color 180ms ease-out; }
|
|
933
|
-
.comparison-cost.selected { color: var(--strong); }
|
|
934
|
-
.comparison-cost > span { font-size: 12px; color: var(--muted); }
|
|
935
|
-
.comparison-cost strong { font-size: clamp(30px,3.15vw,44px); line-height: 1.15; letter-spacing: -1.5px; font-weight: 450; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
936
|
-
.comparison-arrow { color: var(--line-active); font-size: 24px; padding-top: 26px; text-align: center; }
|
|
937
|
-
.comparison-saving { min-width: 0; padding-left: 26px; border-left: 1px solid var(--line-strong); display: grid; gap: 7px; }
|
|
938
|
-
.comparison-saving strong { color: var(--amber); font-size: 28px; line-height: 1.2; letter-spacing: -.7px; font-weight: 450; }
|
|
939
|
-
.comparison-saving span { color: var(--muted); font-size: 12px; line-height: 1.5; font-variant-numeric: tabular-nums; }
|
|
940
|
-
.comparison-saving > button { justify-self: start; margin-top: 3px; padding: 0; font-size: 12px; color: var(--text); }
|
|
941
|
-
.comparison-note { margin: 12px 0 0; color: var(--quiet); font-size: 11px; line-height: 1.7; }
|
|
942
|
-
.pricing-method { font-size: 12px; line-height: 1.7; color: var(--muted); padding-bottom: 16px; }
|
|
943
|
-
.price-assignment { padding: 22px 0; border-top: 1px solid var(--line); font-size: 12px; }
|
|
944
|
-
.price-provider { display: flex; justify-content: space-between; margin-bottom: 16px; }
|
|
945
|
-
.price-provider > span:last-child { color: var(--quiet); font-variant-numeric: tabular-nums; }
|
|
946
|
-
.price-models { display: grid; grid-template-columns: minmax(0,1fr) 18px minmax(0,1fr); gap: 12px; }
|
|
947
|
-
.price-models > div { display: flex; flex-direction: column; gap: 7px; }
|
|
948
|
-
.price-models strong { font-size: 13px; font-weight: 450; }
|
|
949
|
-
.price-models span, .price-result span { color: var(--quiet); font-size: 11px; }
|
|
950
|
-
.price-assignment a { color: var(--muted); font-size: 11px; text-decoration-color: var(--scrollbar); text-underline-offset: 3px; }
|
|
951
|
-
.price-assignment a:hover { color: var(--strong); }
|
|
952
|
-
.price-result { margin-top: 18px; color: var(--text); font-variant-numeric: tabular-nums; }
|
|
953
|
-
@media (max-width: 1000px) {
|
|
954
|
-
.comparison { padding-inline: 24px; }
|
|
955
|
-
.comparison-bottom { grid-template-columns: minmax(0,1fr) 20px minmax(0,1fr); gap: 20px; }
|
|
956
|
-
.comparison-saving { grid-column: 1 / -1; grid-template-columns: 1fr auto; border-left: 0; border-top: 1px solid var(--line); padding: 20px 0 0; margin-top: 4px; }
|
|
957
|
-
.comparison-saving > span { grid-row: 2; }
|
|
958
|
-
.comparison-saving > button { grid-column: 2; grid-row: 1 / 3; align-self: center; }
|
|
959
|
-
}
|
|
960
917
|
@media (max-width: 650px) {
|
|
961
|
-
.comparison { margin-bottom: 24px; padding: 22px 18px 18px; border-radius: 12px; }
|
|
962
|
-
.comparison h2 { font-size: 24px; letter-spacing: -.65px; }
|
|
963
|
-
.comparison-top { align-items: flex-start; flex-direction: column; gap: 20px; margin-bottom: 22px; }
|
|
964
|
-
.comparison-top p { line-height: 1.6; }
|
|
965
|
-
.comparison-switch { width: 100%; }
|
|
966
|
-
.comparison-switch button { flex: 1; padding: 10px 8px; }
|
|
967
|
-
.comparison-bottom { gap: 12px; grid-template-columns: minmax(0,1fr) 16px minmax(0,1fr); padding: 22px 0; }
|
|
968
|
-
.comparison-cost { gap: 8px; }
|
|
969
|
-
.comparison-cost strong { font-size: clamp(19px,5.5vw,32px); letter-spacing: -.6px; }
|
|
970
|
-
.comparison-cost > span { font-size: 11px; }
|
|
971
|
-
.comparison-arrow { font-size: 18px; padding-top: 22px; }
|
|
972
|
-
.comparison-saving { grid-template-columns: minmax(0,1fr); padding-top: 18px; margin-top: 8px; }
|
|
973
|
-
.comparison-saving strong { font-size: 25px; }
|
|
974
|
-
.comparison-saving > button { grid-column: 1; grid-row: 3; margin-top: 5px; }
|
|
975
|
-
.comparison-note { margin-top: 15px; }
|
|
976
918
|
.sample-disclosure { align-items: flex-start; gap: 14px; }
|
|
977
|
-
.sample-disclosure a { white-space: normal; min-width: 70px; }
|
|
978
919
|
}
|
|
979
|
-
@media (prefers-reduced-motion: reduce) { .comparison-switch button, .comparison-cost { transition: none; } }
|
|
980
|
-
|
|
981
|
-
.pricing-assumptions { margin-top: 14px; }
|
|
982
|
-
.pricing-assumptions summary { cursor: pointer; color: var(--text); }
|
|
983
|
-
.pricing-assumptions p { margin-top: 12px; }
|
|
984
|
-
body.keyboard .comparison-switch button, body.keyboard .comparison-cost { transition: none; }
|
|
985
|
-
|
|
986
|
-
.comparison-controls { display: grid; gap: 10px; min-width: 314px; }
|
|
987
|
-
.comparison-preference { display: flex; align-items: center; gap: 12px; }
|
|
988
|
-
.comparison-preference > span { font-size: 11px; color: var(--quiet); white-space: nowrap; }
|
|
989
|
-
.comparison-preference .select-trigger { flex: 1; width: auto; min-width: 0; }
|
|
990
|
-
.comparison-eligibility { margin-top: 16px; display: flex; flex-wrap: wrap; gap: 8px 24px; color: var(--secondary); font-size: 12px; line-height: 1.6; }
|
|
991
|
-
.comparison-eligibility > span:last-child { color: var(--quiet); }
|
|
992
|
-
.preference-unresolved { color: var(--amber); }
|
|
993
|
-
.price-workload { display: flex; align-items: baseline; gap: 12px; justify-content: space-between; margin-bottom: 12px; }
|
|
994
|
-
.price-workload h3 { font-size: 14px; font-weight: 450; margin: 0; }
|
|
995
|
-
.price-workload > span { color: var(--muted); font-size: 11px; }
|
|
996
|
-
.price-workload > .preference-unresolved { color: var(--amber); }
|
|
997
|
-
.price-decision { line-height: 1.6; color: var(--secondary); margin: 16px 0 10px; }
|
|
998
|
-
.benchmark-checks { margin: 12px 0; border-top: 1px solid var(--node-hover); }
|
|
999
|
-
.benchmark-check { display: flex; justify-content: space-between; gap: 16px; align-items: center; border-bottom: 1px solid var(--node-hover); padding: 10px 0; }
|
|
1000
|
-
.benchmark-check > div { display: grid; gap: 4px; }
|
|
1001
|
-
.benchmark-check > div > span { color: var(--quiet); font-size: 11px; }
|
|
1002
|
-
.benchmark-check strong { font-weight: 400; font-size: 12px; color: var(--secondary); }
|
|
1003
|
-
.benchmark-values { text-align: right; color: var(--text); font-size: 12px; font-variant-numeric: tabular-nums; }
|
|
1004
|
-
.candidate-review { padding: 16px 0; border-bottom: 1px solid var(--node-hover); }
|
|
1005
|
-
.candidate-review > p { line-height: 1.6; color: var(--muted); }
|
|
1006
|
-
.candidate-review > a { display: block; margin: 10px 0; }
|
|
1007
|
-
.candidate-review .benchmark-method { font-size: 11px; color: var(--quiet); }
|
|
1008
|
-
@media (max-width: 650px) { .comparison-controls { width: 100%; min-width: 0; } .comparison-eligibility { gap: 6px; } .comparison-note { font-size: 12px; } }
|
|
1009
|
-
|
|
1010
|
-
.comparison-switch button { flex: 1; }
|
|
1011
|
-
|
|
1012
|
-
.comparison-decisions { margin-top: 18px; border-top: 1px solid var(--node-hover); padding-top: 14px; }
|
|
1013
|
-
.comparison-decisions > summary { cursor: pointer; width: fit-content; color: var(--secondary); font-size: 12px; }
|
|
1014
|
-
.comparison-decisions > summary > span { margin-left: 8px; color: var(--quiet); font-variant-numeric: tabular-nums; }
|
|
1015
|
-
.comparison-decisions .data-table { margin-top: 14px; }
|
|
1016
|
-
.comparison-decisions td { vertical-align: top; }
|
|
1017
|
-
.comparison-decisions td small { display: block; color: var(--quiet); font-size: 12px; margin-top: 6px; }
|
|
1018
|
-
.candidate-review > summary { display: flex; justify-content: space-between; gap: 16px; font-size: 12px; line-height: 1.5; }
|
|
1019
|
-
.candidate-review > summary > span:last-child { color: var(--quiet); white-space: nowrap; }
|
|
1020
|
-
.price-models > div > span { overflow-wrap: anywhere; }
|
|
1021
|
-
|
|
1022
|
-
.comparison-decisions .data-table th, .comparison-decisions .data-table td { text-align: left; font-family: var(--sans); }
|
|
1023
|
-
.comparison-decisions .data-table td { font-size: 12px; color: var(--text); }
|
|
1024
|
-
.comparison-decisions > summary::marker { color: var(--line-active); }
|
|
1025
|
-
.comparison-decisions .table-wrap { max-width: 100%; }
|
|
1026
|
-
.comparison-decisions .data-table td:last-child { color: var(--quiet); }
|
|
1027
|
-
|
|
1028
|
-
.comparison-unresolved { margin-top: 18px; padding: 16px 0; border-top: 1px solid var(--line-soft); border-bottom: 1px solid var(--line-soft); }
|
|
1029
|
-
.comparison-unresolved > div { display: flex; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
|
|
1030
|
-
.comparison-unresolved strong { font-size: 13px; font-weight: 450; color: var(--text); }
|
|
1031
|
-
.comparison-unresolved ul { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; list-style: none; padding: 0; margin: 16px 0; }
|
|
1032
|
-
.comparison-unresolved li { font-size: 12px; }
|
|
1033
|
-
.comparison-unresolved small { display: block; margin-top: 6px; font-size: 12px; color: var(--quiet); }
|
|
1034
|
-
.comparison-unresolved > p { font-size: 12px; color: var(--muted); line-height: 1.6; margin-bottom: 0; }
|
|
1035
|
-
@media (max-width: 650px) { .comparison-unresolved ul { grid-template-columns: 1fr; gap: 14px; } }
|
|
1036
|
-
|
|
1037
|
-
/* Preference exceptions use the terminal's existing warm evidence color. */
|
|
1038
|
-
.comparison-override{display:flex;align-items:baseline;gap:8px 14px;flex-wrap:wrap;margin:12px 0 0;padding:12px 0;border-top:1px solid var(--line);font-size:12px;line-height:1.6}
|
|
1039
|
-
.comparison-override strong,.preference-override,.price-workload .preference-override{color:var(--amber)}
|
|
1040
|
-
.comparison-override span{color:var(--muted)}
|
|
1041
|
-
.override-evidence{padding:14px 0;border-block:1px solid var(--line);font-size:12px;line-height:1.7}
|
|
1042
|
-
.override-evidence strong{color:var(--amber);font-weight:500}
|
|
1043
|
-
.override-evidence ul{margin:8px 0;padding-left:18px}
|
|
1044
|
-
.benchmark-check[data-check="regression"] .benchmark-values{color:var(--amber)}
|
|
1045
920
|
|
|
1046
921
|
::selection { background: var(--selected); color: var(--text); }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"format":1,"contract":"scout-recommend-v1","version":"0.
|
|
1
|
+
{"format":1,"contract":"scout-recommend-v1","version":"0.2.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"}
|
|
@@ -10,6 +10,7 @@ import { commandTelemetry } from './telemetry.mjs';
|
|
|
10
10
|
const help = `OpenMerit · Inference Terminal
|
|
11
11
|
|
|
12
12
|
openmerit dash [path] [options]
|
|
13
|
+
openmerit scout --help Ask Scout which models fit your tasks
|
|
13
14
|
openmerit install --help Connect OpenMerit to your coding agent
|
|
14
15
|
openmerit update --help Update installed Scout instructions
|
|
15
16
|
openmerit eval --help Evaluate CLI changes across coding agents
|
|
@@ -31,6 +32,7 @@ Coding-harness usage is outside this terminal's scope.
|
|
|
31
32
|
async function main() {
|
|
32
33
|
const args = process.argv.slice(2);
|
|
33
34
|
if (['install', 'status', 'remove', 'login', 'logout', 'project', 'doctor', 'update'].includes(args[0])) { const { main } = await import('./install.mjs'); process.exitCode = await main(args); return; }
|
|
35
|
+
if (args[0] === 'scout') { const { main } = await import('./scout.js'); process.exitCode = await main(args.slice(1)); return; }
|
|
34
36
|
if (args[0] === 'eval') { const { main } = await import('../../eval/src/cli.mjs'); await main(args.slice(1)); return; }
|
|
35
37
|
if (!args.length || args.includes('--help') || args.includes('-h')) { console.log(help); return; }
|
|
36
38
|
if (args.shift() !== 'dash') throw new Error('Unknown command. Run openmerit --help.');
|
|
@@ -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.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ScoutClient } from './scout-credentials.ts';
|
|
2
|
-
export declare const rule = "# Application model decisions \u00B7 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";
|
|
3
|
-
export declare const 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 \u00B7 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.\
|
|
2
|
+
export declare const rule = "# Application model decisions \u00B7 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";
|
|
3
|
+
export declare const 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 \u00B7 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";
|
|
4
4
|
export declare function scoutRule(client: ScoutClient): string;
|
|
5
5
|
export declare function scoutSkill(client: ScoutClient): string;
|
|
@@ -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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
|
@@ -14,6 +14,8 @@ count as model choices, not implementation details. Identify this decision befor
|
|
|
14
14
|
writing model-specific code, installing a model package or downloading weights.
|
|
15
15
|
|
|
16
16
|
Follow the skill's project access policy. If it is missing, ask and save it first.
|
|
17
|
+
Scout may answer with clarifying questions: relay them to the user and continue
|
|
18
|
+
the same Scout conversation with their answers before choosing.
|
|
17
19
|
If Scout is unavailable, fails or has no suitable evidence/candidate, disclose the
|
|
18
20
|
gap and pause the model decision; do not fall back to a remembered or SDK-default
|
|
19
21
|
model. You may continue work that does not commit to a model.
|
|
@@ -61,17 +63,28 @@ is not permission to search all models.
|
|
|
61
63
|
|
|
62
64
|
## Searching
|
|
63
65
|
|
|
64
|
-
Call \`scout_models\` with a non-confidential task description and
|
|
65
|
-
\`endpointIds\` set to the saved inventory, including \`[]\` when empty.
|
|
66
|
-
Existing-only never broadens. For existing-first, if no existing candidate fits,
|
|
67
|
-
explain the gap and ask before a separate search without \`endpointIds\`. Send
|
|
66
|
+
Call \`scout_models\` with a non-confidential task description as \`prompt\` and
|
|
67
|
+
\`endpointIds\` set to the saved inventory, including \`[]\` when empty. Send
|
|
68
68
|
only task requirements, never source files, secrets or real customer records.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
69
|
+
If the MCP tool is unavailable, run
|
|
70
|
+
\`npx -y openmerit@latest scout "<task>" --json\`; it applies the saved inventory.
|
|
71
|
+
|
|
72
|
+
Scout is a conversation with the agent behind openmerit.ai/scout. When
|
|
73
|
+
\`status\` is \`needs_answers\`, fill in what the user or project already states,
|
|
74
|
+
show the user Scout's brief and remaining questions, and wait for their answers.
|
|
75
|
+
Then call again with the returned \`conversationId\` and \`brief\` set to the
|
|
76
|
+
rewritten components, or \`skip: true\` if the user wants Scout to assume. Never
|
|
77
|
+
answer Scout's questions with guesses. Send later refinements to the same
|
|
78
|
+
\`conversationId\`. A call with only a \`conversationId\` reads an interrupted turn.
|
|
79
|
+
|
|
80
|
+
When \`status\` is \`recommended\`, Scout has split the work into \`tasks\`, one
|
|
81
|
+
per model, each with up to five ranked \`models\`; cover every task. Each
|
|
82
|
+
model's \`cost\` names the \`endpointId\` and \`providerModelId\` to call.
|
|
83
|
+
Existing-only never broadens. For existing-first, if no existing candidate fits a
|
|
84
|
+
task, explain the gap and ask before a new conversation without \`endpointIds\`.
|
|
85
|
+
Compare quality evidence, reference cost/time and watch-outs. Prefer the cheapest
|
|
86
|
+
usable option the evidence supports, without claiming benchmarks prove
|
|
87
|
+
application accuracy. Use the project's actual SDK and protocol.
|
|
75
88
|
|
|
76
89
|
## Boundaries
|
|
77
90
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export declare const policyFile = ".openmerit/scout.json";
|
|
2
|
+
export declare const components: readonly ["input", "work", "output", "criteria", "constraints"];
|
|
3
|
+
export declare const help = "OpenMerit \u00B7 Scout\n\n openmerit scout \"<the work models need to do>\" [--all-endpoints] [--json]\n openmerit scout --conversation <id> [--brief <component>=<text> ...] [--skip] [\"<follow-up>\"] [--json]\n\nAsks Scout, the agent behind openmerit.ai/scout. Scout may first return a task\nbrief with clarifying questions: answer them with --brief (components: input,\nwork, output, criteria, constraints) in the same conversation, or --skip to let\nScout assume. It then splits the work into tasks and recommends up to five\nmodels for each. A turn usually takes one to three minutes.\n\nA new conversation is limited to the endpoint IDs in .openmerit/scout.json\n(set with openmerit project). --all-endpoints searches the whole catalogue and is\nrefused under the existing-only policy. Uses the device key that\n\"openmerit install\" saved for Claude Code or Pi, this project's first. Never include secrets, source files or customer records.\n";
|
|
4
|
+
export interface ScoutRequest {
|
|
5
|
+
prompt?: string;
|
|
6
|
+
conversationId?: string;
|
|
7
|
+
brief?: Partial<Record<typeof components[number], string>>;
|
|
8
|
+
skip?: boolean;
|
|
9
|
+
endpointIds?: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface ScoutModel {
|
|
12
|
+
rank: number;
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
provider: string;
|
|
16
|
+
rationale: string;
|
|
17
|
+
watchOut: string | null;
|
|
18
|
+
quality: {
|
|
19
|
+
score: number;
|
|
20
|
+
evidence: Array<{
|
|
21
|
+
name: string;
|
|
22
|
+
rank: number;
|
|
23
|
+
of: number;
|
|
24
|
+
}>;
|
|
25
|
+
};
|
|
26
|
+
cost: {
|
|
27
|
+
usdPer1kTasks: number;
|
|
28
|
+
inputPerMillion: number;
|
|
29
|
+
outputPerMillion: number;
|
|
30
|
+
route: string;
|
|
31
|
+
endpointId: string;
|
|
32
|
+
providerModelId: string;
|
|
33
|
+
} | null;
|
|
34
|
+
latency?: {
|
|
35
|
+
seconds: number;
|
|
36
|
+
} | null;
|
|
37
|
+
recency?: {
|
|
38
|
+
released: string;
|
|
39
|
+
} | null;
|
|
40
|
+
}
|
|
41
|
+
type Priority = 'cost' | 'quality' | 'latency' | 'recency';
|
|
42
|
+
export interface ScoutResult {
|
|
43
|
+
conversationId: string;
|
|
44
|
+
status: 'needs_answers' | 'recommended' | 'working' | 'message' | 'error';
|
|
45
|
+
next?: string;
|
|
46
|
+
brief?: Record<string, {
|
|
47
|
+
text: string;
|
|
48
|
+
source: string;
|
|
49
|
+
}>;
|
|
50
|
+
questions?: Array<{
|
|
51
|
+
component: string;
|
|
52
|
+
question: string;
|
|
53
|
+
}>;
|
|
54
|
+
assumptions?: string[];
|
|
55
|
+
tasks?: Array<{
|
|
56
|
+
title: string;
|
|
57
|
+
work: string;
|
|
58
|
+
summary: string;
|
|
59
|
+
priority?: Priority;
|
|
60
|
+
models: ScoutModel[];
|
|
61
|
+
}>;
|
|
62
|
+
text?: string;
|
|
63
|
+
error?: string;
|
|
64
|
+
}
|
|
65
|
+
export declare function askScout(root: string, request: ScoutRequest, signal?: AbortSignal): Promise<ScoutResult>;
|
|
66
|
+
export declare function scoutTurn(root: string, request: Omit<ScoutRequest, 'endpointIds'> & {
|
|
67
|
+
allEndpoints?: boolean;
|
|
68
|
+
}, signal?: AbortSignal): Promise<ScoutResult>;
|
|
69
|
+
export declare function renderScout(result: ScoutResult): string;
|
|
70
|
+
export declare function main(args: string[], { root, print }?: {
|
|
71
|
+
root?: string | undefined;
|
|
72
|
+
print?: {
|
|
73
|
+
(...data: any[]): void;
|
|
74
|
+
(message?: any, ...optionalParams: any[]): void;
|
|
75
|
+
} | undefined;
|
|
76
|
+
}): Promise<number>;
|
|
77
|
+
export {};
|
|
@@ -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
|
+
export const policyFile = '.openmerit/scout.json';
|
|
9
|
+
export const components = ['input', 'work', 'output', 'criteria', 'constraints'];
|
|
10
|
+
export 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
|
+
export 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
|
+
}
|