metergraph-cli 0.2.3 → 0.2.5

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 (60) hide show
  1. package/README.md +78 -14
  2. package/assets/skill/SKILL.md +124 -31
  3. package/assets/skill/manifest.json +3 -3
  4. package/assets/skills/manifest.json +37 -31
  5. package/assets/skills/metergraph-analyze/SKILL.md +161 -58
  6. package/assets/skills/metergraph-candidates/SKILL.md +50 -8
  7. package/assets/skills/metergraph-evals/SKILL.md +83 -16
  8. package/assets/skills/metergraph-investigate/SKILL.md +90 -34
  9. package/assets/skills/metergraph-iterate/SKILL.md +82 -12
  10. package/assets/skills/metergraph-local-bundle/SKILL.md +146 -128
  11. package/assets/skills/metergraph-model-swap/SKILL.md +142 -20
  12. package/assets/skills/metergraph-onboarding/SKILL.md +93 -49
  13. package/assets/skills/metergraph-price-check/SKILL.md +219 -0
  14. package/assets/skills/metergraph-report/SKILL.md +60 -15
  15. package/assets/skills/metergraph-workloads/SKILL.md +97 -13
  16. package/native/README.md +85 -0
  17. package/native/anchored-fs.c +243 -0
  18. package/native/bin/anchored-fs-darwin-arm64 +0 -0
  19. package/native/bin/anchored-fs-darwin-x64 +0 -0
  20. package/native/bin/anchored-fs-linux-arm64 +0 -0
  21. package/native/bin/anchored-fs-linux-x64 +0 -0
  22. package/native/build.mjs +55 -0
  23. package/native/manifest.json +9 -0
  24. package/native/targets.mjs +24 -0
  25. package/package.json +10 -3
  26. package/src/action-help.js +130 -0
  27. package/src/agent-key.js +21 -14
  28. package/src/anchored-fs.js +163 -0
  29. package/src/args.js +125 -17
  30. package/src/auth-binding.js +12 -7
  31. package/src/auth-callback.js +10 -2
  32. package/src/auth-login.js +111 -28
  33. package/src/auth-oauth.js +89 -45
  34. package/src/auth-session.js +19 -12
  35. package/src/auth-store.js +28 -7
  36. package/src/cli.js +52 -8
  37. package/src/constants.js +37 -9
  38. package/src/experiments-output.js +7 -6
  39. package/src/experiments.js +26 -17
  40. package/src/output.js +274 -79
  41. package/src/read-contract.js +42 -8
  42. package/src/read-output.js +66 -13
  43. package/src/read.js +90 -15
  44. package/src/repository-identity.js +2 -2
  45. package/src/setup-env-git.js +1 -1
  46. package/src/setup-env.js +16 -9
  47. package/src/setup-interactive.js +19 -4
  48. package/src/setup-refusal.js +2 -2
  49. package/src/setup-state.js +9 -4
  50. package/src/setup.js +15 -3
  51. package/src/skill-bundle.js +2 -2
  52. package/src/skill-trusted.js +56 -0
  53. package/src/skill.js +28 -9
  54. package/src/style.js +33 -7
  55. package/src/tools-output.js +73 -0
  56. package/src/tools.js +249 -0
  57. package/src/trace-contract.js +1 -1
  58. package/src/transport.js +21 -0
  59. package/src/verify-output.js +2 -2
  60. package/src/verify.js +5 -2
package/src/output.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { actionHelpData, displayOptions } from "./action-help.js";
1
2
  import { PRICING_OPTIONS } from "./pricing-args.js";
2
3
  import {
3
4
  CONNECTION_GUIDE_URL,
@@ -28,7 +29,7 @@ import {
28
29
  SUPPORTED_PROFILES,
29
30
  VERSION,
30
31
  } from "./constants.js";
31
- import { PLAIN, fields, headline, paragraph, render, section, steps, toneFor } from "./style.js";
32
+ import { PLAIN, fields, headline, paragraph, render, section, steps, toneFor, workspaceShown } from "./style.js";
32
33
 
33
34
  // Every JSON result, success or failure, has the same top-level keys:
34
35
  // schema_version, command, ok, outcome, exit_code, data, error
@@ -49,7 +50,20 @@ export function envelope({ command, outcome, data = null, reason = null, message
49
50
  }
50
51
 
51
52
  export function toJsonLine(result) {
52
- return `${JSON.stringify(result)}\n`;
53
+ return `${jsonText(result)}\n`;
54
+ }
55
+
56
+ // JSON.stringify escapes C0 controls but leaves C1 controls (a terminal may
57
+ // read U+009B as an escape sequence) and bidirectional overrides as they are.
58
+ // Inside a JSON string \uXXXX means the same character, so escaping them
59
+ // changes nothing a parser sees and keeps them away from the terminal.
60
+ const TERMINAL_UNSAFE = /[\u061c\u007f-\u009f\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]/g;
61
+
62
+ export function jsonText(value, indent = undefined) {
63
+ return JSON.stringify(value, null, indent).replace(
64
+ TERMINAL_UNSAFE,
65
+ (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`,
66
+ );
53
67
  }
54
68
 
55
69
  const DOCTOR_OPTIONS = [
@@ -126,9 +140,14 @@ const AGENT_PATH_HELP = [
126
140
  " SSH, cloud workspace or CI A person creates a coding-agent key with Metadata access",
127
141
  " on the Keys page and saves it as the secret",
128
142
  " METERGRAPH_AGENT_TOKEN; then status --json, usage --json",
129
- " Metadata key, no saved sign in The same: reads use METERGRAPH_AGENT_TOKEN, or",
143
+ " A key, no saved sign in The same: reads use METERGRAPH_AGENT_TOKEN, or",
130
144
  " --agent-token-file PATH, after verifying the workspace",
131
- "Never pass a key as an argument. Debug and Replay keys are refused, not reduced.",
145
+ " Evaluations and analyses A workspace owner creates a key with the Debug role and",
146
+ " Allow evaluation writes, Allow evaluation previews and",
147
+ " Allow analysis starts; the same METERGRAPH_AGENT_TOKEN",
148
+ " serves the reads. status --json reports the role and each",
149
+ " permission in data.access",
150
+ "Never pass a key as an argument. Replay keys are refused.",
132
151
  ];
133
152
 
134
153
  const CONFIG_DIR_OPTION = {
@@ -144,14 +163,34 @@ const LOGIN_OPTIONS = [
144
163
  name: "--runtime",
145
164
  value: "RUNTIME",
146
165
  summary:
147
- "Required. Where this terminal runs, not which Metergraph service. local: your own machine, " +
166
+ "Required for scripts; inferred as local in an interactive local terminal. Where this terminal runs, not which Metergraph service. local: your own machine, " +
148
167
  "including for the hosted service. cloud, cloud-no-shell: a cloud sandbox; get the connection guide.",
149
168
  },
150
169
  { name: "--url", value: "ORIGIN", summary: `Origin to sign in to. Default ${DEFAULT_ORIGIN}.` },
151
170
  {
152
171
  name: "--workspace",
153
- value: "UUID",
154
- summary: "Expected workspace ID. Sign in fails unless the browser grants exactly this workspace.",
172
+ value: "SLUG",
173
+ summary:
174
+ "The workspace to sign in to, by its slug (on its Settings page) or its name. A service that can, offers " +
175
+ "approval for that workspace alone; sign in fails unless the browser grants it.",
176
+ },
177
+ {
178
+ name: "--role",
179
+ value: "ROLE",
180
+ summary:
181
+ "metadata (default): usage, cost and trace questions, no captured content. debug: also reads captured " +
182
+ "prompts and responses through Agent Access; the --allow-* options add workflow permissions to it.",
183
+ },
184
+ {
185
+ name: "--allow-evaluation-writes",
186
+ value: null,
187
+ summary: "With --role debug: Allow evaluation writes. Only a workspace owner approves the workflow permissions.",
188
+ },
189
+ { name: "--allow-evaluation-previews", value: null, summary: "With --role debug: Allow evaluation previews." },
190
+ {
191
+ name: "--allow-analysis-starts",
192
+ value: null,
193
+ summary: "With --role debug: Allow analysis starts, which replays captured prompts on model providers and spends money.",
155
194
  },
156
195
  { name: "--project", value: "DIR", summary: "Existing project directory to bind. Default: the current directory." },
157
196
  CONFIG_DIR_OPTION,
@@ -173,8 +212,9 @@ const LOGOUT_OPTIONS = [
173
212
  ];
174
213
 
175
214
  const LOGIN_USAGE =
176
- "metergraph login --runtime local [--url ORIGIN] [--workspace UUID] [--project DIR] [--config-dir DIR] " +
177
- "[--timeout-ms N] [--signup] [--no-browser] [--reconnect] [--json]";
215
+ "metergraph login --runtime local [--url ORIGIN] [--workspace SLUG] [--role metadata|debug] " +
216
+ "[--allow-evaluation-writes] [--allow-evaluation-previews] [--allow-analysis-starts] [--project DIR] " +
217
+ "[--config-dir DIR] [--timeout-ms N] [--signup] [--no-browser] [--reconnect] [--json]";
178
218
  const LOGOUT_USAGE = "metergraph logout [--project DIR] [--config-dir DIR] [--json]";
179
219
  const SETUP_USAGE = "metergraph setup --runtime local (--client codex|claude|cursor | --skip-skill) " +
180
220
  "[--url ORIGIN] [--workspace UUID] [--project DIR] [--config-dir DIR] [--env-file .env] " +
@@ -217,19 +257,21 @@ const READ_BASE_OPTIONS = [
217
257
  name: "--url",
218
258
  value: "ORIGIN",
219
259
  summary:
220
- "Where a Metadata key is sent when the project is not bound. Default METERGRAPH_URL, else the hosted service. " +
260
+ "Where a coding-agent key is sent when the project is not bound. Default METERGRAPH_URL, else the hosted service. " +
221
261
  "A bound project's origin cannot be changed here.",
222
262
  },
223
263
  {
224
264
  name: "--workspace",
225
- value: "UUID",
226
- summary: "The workspace the credential must be for. Without it, an unbound key's own workspace is used.",
265
+ value: "SLUG",
266
+ summary:
267
+ "The workspace the credential must be for, by its slug or name (or ID). Without it, an unbound key's own " +
268
+ "workspace is used.",
227
269
  },
228
270
  {
229
271
  name: "--agent-token-file",
230
272
  value: "PATH",
231
273
  summary:
232
- "Private (0600) file holding a coding-agent key with Metadata access. Used instead of a saved sign in. " +
274
+ "Private (0600) file holding a coding-agent key with the Metadata or Debug role. Used instead of a saved sign in. " +
233
275
  "Without it, METERGRAPH_AGENT_TOKEN is used when this project has no saved sign in on this machine.",
234
276
  },
235
277
  ];
@@ -259,11 +301,29 @@ const EXPERIMENTS_OPTIONS = [
259
301
  name: "--agent-token-file",
260
302
  value: "PATH",
261
303
  summary:
262
- "Private (0600) file holding a coding-agent key. For list, estimate and get: a key with Metadata access. " +
304
+ "Private (0600) file holding a coding-agent key. For list, estimate and get: a key with the Metadata or Debug role. " +
263
305
  "For start: a key with Debug access and Allow analysis starts, used for that one request.",
264
306
  },
265
307
  JSON_OPTION,
266
308
  ];
309
+ const TOOLS_USAGE = [
310
+ "metergraph tools list [--name TOOL] [read options] [--json]",
311
+ "metergraph tools call TOOL [--args JSON | --args-file PATH] [--yes] [read options] [--json]",
312
+ ];
313
+ const TOOLS_OPTIONS = [
314
+ { name: "--name", value: "TOOL", summary: "list only: show one tool, with its input schema under --json." },
315
+ { name: "--args", value: "JSON", summary: "call only: the tool's arguments as one JSON object. Default {}." },
316
+ { name: "--args-file", value: "PATH", summary: "call only: a file holding the arguments as one JSON object, up to 1 MiB." },
317
+ {
318
+ name: "--yes",
319
+ value: null,
320
+ summary:
321
+ "call only: run a tool that writes, starts work or calls a model provider. Pass it only after the person " +
322
+ "has approved this exact call.",
323
+ },
324
+ ...READ_BASE_OPTIONS,
325
+ JSON_OPTION,
326
+ ];
267
327
  const DAYS_OPTION = {
268
328
  name: "--days",
269
329
  value: "N",
@@ -291,21 +351,23 @@ const limitOption = (fallback, extra = "") => ({
291
351
  summary: `Rows to return, 1 to ${READ_MAX_LIMIT}. Default ${fallback}.${extra}`,
292
352
  });
293
353
  const READ_BASE_USAGE =
294
- "[--url ORIGIN] [--workspace UUID] [--agent-token-file PATH] [--project DIR] [--config-dir DIR] [--timeout-ms N] [--json]";
354
+ "[--url ORIGIN] [--workspace SLUG] [--agent-token-file PATH] [--project DIR] [--config-dir DIR] [--timeout-ms N] [--json]";
295
355
  const REFUSED_NOTE =
296
356
  "--environment, --workload, --since, --until, --sql, --query, --content, --include-content, --debug and --replay " +
297
357
  "are recognized and refused with exit code 6 before any request, except --environment, --since and --until on usage.";
298
358
 
299
359
  // The read commands. Each uses the project's saved Metadata grant or a
300
- // coding-agent key with Metadata access, makes GET requests to fixed paths on
301
- // the verified origin and never opens a browser.
360
+ // coding-agent key with the Metadata or Debug role, makes GET requests to fixed
361
+ // paths on the verified origin and never opens a browser.
302
362
  export const READ_HELP = Object.freeze([
303
363
  {
304
364
  name: "status",
305
365
  usage: `metergraph status ${READ_BASE_USAGE}`,
306
366
  summary:
307
367
  "Show whether this project is configured, the service is reachable and reports the bound deployment " +
308
- "profile, and the grant is verified for the bound workspace, with its Metadata capabilities. " +
368
+ "profile, and the grant is verified for the bound workspace, with its Metadata capabilities. Reports " +
369
+ "the credential's role (Metadata or Debug) and its workflow permissions (Allow evaluation writes, Allow " +
370
+ "evaluation previews, Allow analysis starts) in data.access, and how to grant them. " +
309
371
  "Never claims application traffic is verified.",
310
372
  options: [...READ_BASE_OPTIONS, JSON_OPTION],
311
373
  },
@@ -449,8 +511,8 @@ function optionLines(options, minimum) {
449
511
  return options.map((option, index) => ` ${flags[index].padEnd(width)}${option.summary}`);
450
512
  }
451
513
 
452
- export function helpData(topic) {
453
- return {
514
+ export function helpData(topic, action = null) {
515
+ const data = {
454
516
  topic,
455
517
  usage: [
456
518
  "metergraph --help [--json]",
@@ -465,6 +527,7 @@ export function helpData(topic) {
465
527
  VERIFY_USAGE,
466
528
  ...READ_HELP.map((entry) => entry.usage),
467
529
  ...EXPERIMENTS_USAGE,
530
+ ...TOOLS_USAGE,
468
531
  ],
469
532
  commands: [
470
533
  { name: "pricing", summary: "Public model discovery, lookup and calculation. Current market board; historical charts in local reference mode.",
@@ -492,7 +555,7 @@ export function helpData(topic) {
492
555
  {
493
556
  name: "skills install",
494
557
  summary:
495
- "Copy the Metergraph workflow skills bundled with this CLI, such as the model-swap loop, into one " +
558
+ "Copy the Metergraph workflow skills bundled with this CLI, such as model swap, into one " +
496
559
  "client's project skill directory. Never replaces a skill it did not install. No network requests, no sign in.",
497
560
  options: SKILL_OPTIONS,
498
561
  },
@@ -525,13 +588,13 @@ export function helpData(topic) {
525
588
  name: "setup",
526
589
  summary: "Guide browser sign in and workspace choice, approve an ingest-only key, and install the selected client skill. Does not verify application traffic.",
527
590
  options: [
528
- { name: "--runtime", value: "RUNTIME", summary: "Required unless a person runs setup at a terminal (then local). local only; other runtimes get a handoff." },
591
+ { name: "--runtime", value: "RUNTIME", summary: "Local terminal: inferred automatically. Scripts: required. Other runtimes get a handoff." },
529
592
  { name: "--url", value: "ORIGIN", summary: "Deployment origin. Defaults to an existing project binding, else the hosted origin." },
530
593
  { name: "--workspace", value: "UUID", summary: "Expected workspace; the browser must approve this exact workspace." },
531
594
  { name: "--project", value: "DIR", summary: "Project directory. Default: current directory." },
532
595
  CONFIG_DIR_OPTION,
533
596
  { name: "--env-file", value: "FILE", summary: "Project-relative env file. Default: .env." },
534
- { name: "--client", value: "CLIENT", summary: "Install the bundled skill for codex, claude or cursor." },
597
+ { name: "--client", value: "CLIENT", summary: "codex, claude or cursor. A local interactive setup asks when omitted." },
535
598
  { name: "--skip-skill", value: null, summary: "Explicitly leave client skill installation pending." },
536
599
  { name: "--timeout-ms", value: "N", summary: "Time to wait for browser approval." },
537
600
  { name: "--signup", value: null, summary: "Start at hosted sign up when the project needs login." },
@@ -559,6 +622,13 @@ export function helpData(topic) {
559
622
  "reads; start needs a key with Debug access and Allow analysis starts and spends within the server's budget.",
560
623
  options: EXPERIMENTS_OPTIONS,
561
624
  },
625
+ {
626
+ name: "tools",
627
+ summary:
628
+ "List the Agent Access tools this credential can use and call one, as an MCP client would. A tool that " +
629
+ "writes, starts work or calls a model provider runs only with --yes.",
630
+ options: TOOLS_OPTIONS,
631
+ },
562
632
  ...READ_HELP.map(({ name, summary, options, examples }) => ({
563
633
  name,
564
634
  summary,
@@ -576,12 +646,34 @@ export function helpData(topic) {
576
646
  meaning: EXIT_CODE_MEANINGS[outcome],
577
647
  })),
578
648
  };
649
+ if (action) data.action = { name: action, ...actionHelpData(topic, action, data.commands) };
650
+ return data;
579
651
  }
580
652
 
581
- export function helpText(topic, style = PLAIN) {
653
+ export function helpText(topic, style = PLAIN, action = null, advanced = false) {
582
654
  const lines = [];
655
+ if (action) {
656
+ const entry = actionHelpData(topic, action, helpData(topic).commands);
657
+ if (entry) return formatHelp([
658
+ `Usage: ${entry.usage}`, "", entry.summary, "", "Examples:",
659
+ ...entry.examples.map(example => ` ${example}`), "", "Options:",
660
+ ...optionLines(displayOptions(entry.options, topic, action, advanced), 20), "", entry.note, "",
661
+ ...(advanced ? [] : [`Connection settings: metergraph ${topic} ${action} --help --advanced`]),
662
+ ], style);
663
+ }
664
+ if (topic === "exit-codes") {
665
+ lines.push("Exit codes:", "", "For scripts. Human output describes the result and what to do next.", "");
666
+ for (const [outcome, code] of Object.entries(EXIT_CODES)) {
667
+ lines.push(` ${String(code).padEnd(4)}${outcome}`, ...paragraph(style, EXIT_CODE_MEANINGS[outcome], " "), "");
668
+ }
669
+ return formatHelp(lines, style);
670
+ }
671
+ if (topic === "agents") return formatHelp([
672
+ "Connect a coding agent", "", ...AGENT_PATH_HELP, "", ...AGENT_APPROVAL_HELP,
673
+ "", 'Use --json for structured results. Run "metergraph help exit-codes" for scripting outcomes.',
674
+ ], style);
583
675
  if (topic === "pricing") {
584
- return `Usage: metergraph pricing models|lookup|calculate|cost|history|market [options] [--json]
676
+ return formatHelp(`Usage: metergraph pricing models|lookup|calculate|cost|history|market [options] [--json]
585
677
 
586
678
  Public catalog prices. No account, project or token is required.
587
679
  models [--model ID] [--provider ID | --channel ID] [--region ID] [--limit 1..100]
@@ -615,7 +707,7 @@ Public API: GET /v1/pricing/models; POST /v1/pricing/lookup and /calculate.
615
707
  No credentials, traces, prompts or responses are loaded from project files.
616
708
  The CLI constructs metadata-only provider-format traces from the supplied counts.
617
709
  See docs/pricing.md for the supported contract, limitations and reference previews.
618
- `;
710
+ `.split("\n"), style);
619
711
  }
620
712
  if (topic === "doctor") {
621
713
  lines.push(
@@ -654,15 +746,18 @@ See docs/pricing.md for the supported contract, limitations and reference previe
654
746
  lines.push(
655
747
  "Opens your browser on the service's own sign in and workspace consent pages, then",
656
748
  "binds this project to the workspace you approve. The CLI receives a delegated grant",
657
- `limited to the Metadata scope (${METADATA_SCOPE}) and verifies the workspace, profile`,
658
- "and scope with the service before saving anything. Your browser keeps its own sign in.",
749
+ `limited to the Metadata role (${METADATA_SCOPE}), or with --role debug to the Debug role and`,
750
+ "the workflow permissions the --allow-* options name, which only a workspace owner approves.",
751
+ "It verifies the workspace, profile and scope with the service before saving anything. Your",
752
+ "browser keeps its own sign in. Signing in again with another role or other permissions",
753
+ "replaces the grant.",
659
754
  "The grant is saved in your private config directory; the project gets only",
660
755
  ".metergraph/project.json, which holds no credentials.",
661
756
  "",
662
757
  "Login does not create an application ingest key, and no manual API key is required.",
663
- "The service records the grant as a Metadata-only OAuth connection, separate from any",
664
- "API or ingest key you manage. Login reads no telemetry or content and never asks for",
665
- "Debug or Replay access. Rerunning it on a signed in project changes nothing.",
758
+ "The service records the grant as an OAuth connection, separate from any API or ingest",
759
+ "key you manage. Login reads no telemetry or content and never asks for Replay access.",
760
+ "Rerunning it on a signed in project with the same role and permissions changes nothing.",
666
761
  "",
667
762
  );
668
763
  } else {
@@ -683,6 +778,28 @@ See docs/pricing.md for the supported contract, limitations and reference previe
683
778
  "Opening requires a server link that selects the verified workspace; older links get a manual handoff.",
684
779
  "", "Options:");
685
780
  lines.push(...optionLines(VERIFY_OPTIONS, 22));
781
+ } else if (topic === "tools") {
782
+ lines.push(
783
+ "Usage:",
784
+ ...TOOLS_USAGE.map((usage) => ` ${usage}`),
785
+ "",
786
+ "Every Agent Access tool the service offers this credential, through its MCP endpoint, with the same",
787
+ "saved sign in or coding-agent key as the read commands and the same checks first. The service decides",
788
+ "which tools a credential sees: a Metadata credential gets the Metadata tools; the Debug role adds trace",
789
+ "content and evaluation reads, and Allow evaluation writes, Allow evaluation previews and Allow analysis",
790
+ "starts add the tools they name. Replay keys are refused.",
791
+ "",
792
+ "list prints the tools with whether each only reads. Under --json each carries its title, description",
793
+ "and input schema. call sends one tools/call with --args or --args-file and prints the tool's structured",
794
+ "result in data.result. A tool the service does not mark read-only, or marks as calling a model provider,",
795
+ "needs --yes: without it the command exits 18 (action_required, confirmation_required) and sends",
796
+ "nothing. Pass --yes only after the person has approved that exact call. A refusal by the tool exits 19",
797
+ "(refused) with the service's error in data.tool_error; a write whose answer was lost exits 20",
798
+ "(unconfirmed).",
799
+ "",
800
+ "Options:",
801
+ );
802
+ lines.push(...optionLines(TOOLS_OPTIONS, 22));
686
803
  } else if (topic === "experiments") {
687
804
  lines.push(
688
805
  "Usage:",
@@ -697,12 +814,12 @@ See docs/pricing.md for the supported contract, limitations and reference previe
697
814
  "same arguments, so it starts exactly what was priced; poll it with get until it is terminal.",
698
815
  "Each start creates a new experiment, so a start that succeeded is never retried.",
699
816
  "",
700
- "list, estimate and get use this project's saved Metadata grant or a Metadata key, as the read",
817
+ "list, estimate and get use this project's saved sign in or a coding-agent key, as the read",
701
818
  "commands do, and never read inputs, outputs or tracked field values; those are in the dashboard.",
702
- "start needs a coding-agent key with Debug access and Allow analysis starts, which only a workspace",
703
- "owner can grant, in --agent-token-file or METERGRAPH_AGENT_TOKEN. The CLI checks the key has exactly",
704
- "that before it sends the one start request, and never uses it for anything else. The service then",
705
- "replays the sampled calls' captured prompts on each treatment's provider within its budget.",
819
+ "start needs a coding-agent key with the Debug role and Allow analysis starts, which only a",
820
+ "workspace owner can grant, in --agent-token-file or METERGRAPH_AGENT_TOKEN.",
821
+ "The CLI checks the key before it sends the one start request. The service then replays the",
822
+ "sampled calls' captured prompts on each treatment's provider within its budget.",
706
823
  "",
707
824
  "Options:",
708
825
  );
@@ -715,10 +832,10 @@ See docs/pricing.md for the supported contract, limitations and reference previe
715
832
  entry.summary,
716
833
  "",
717
834
  "Uses this project's saved Metadata grant (see \"metergraph help login\"), or, where there is none on",
718
- "this machine, a coding-agent key with Metadata access from METERGRAPH_AGENT_TOKEN or",
719
- "--agent-token-file. A key is sent only after the origin answers as a supported Metergraph service,",
720
- "and nothing is read until the service shows exactly the Metadata scope, no content access and the",
721
- "expected workspace. Sends GET requests only, to fixed paths on that origin, follows no redirects and",
835
+ "this machine, a coding-agent key from METERGRAPH_AGENT_TOKEN or --agent-token-file, with the",
836
+ "Metadata role or the Debug role. A key is sent only after the origin answers as a supported",
837
+ "Metergraph service, and nothing is read until the service shows one of those roles (never",
838
+ "Replay) and the expected workspace. Sends GET requests only, to fixed paths on that origin, follows no redirects and",
722
839
  "reads bounded responses. Never opens a browser, signs in, writes project files or the key, reads",
723
840
  "retained content, replays or calls a model provider. It may refresh its own saved grant, and the",
724
841
  "service may record the access (for example last used times); it never changes workspace",
@@ -764,7 +881,7 @@ See docs/pricing.md for the supported contract, limitations and reference previe
764
881
  "Copies the Metergraph workflow skills bundled with this CLI into one client's project",
765
882
  "skill directory, one folder per skill, and records ownership of each in",
766
883
  ".metergraph/skills/NAME.json. The skills come from https://github.com/metergraph/skills",
767
- "at the commit this release pins. They cover investigation and the model-swap loop:",
884
+ "at the commit this release pins. They cover investigation and model swap:",
768
885
  "choose a workload, choose traces and models, define the eval, run the analysis,",
769
886
  "summarize the report and rerun. The setup skill is installed by \"metergraph skill\".",
770
887
  "",
@@ -788,45 +905,110 @@ See docs/pricing.md for the supported contract, limitations and reference previe
788
905
  "Discovery stays pending until the client itself loads the skills.",
789
906
  );
790
907
  } else {
791
- lines.push(
792
- `metergraph ${VERSION} (preview)`,
793
- "",
794
- "Usage:",
795
- " metergraph --help [--json] Show this help",
796
- " metergraph --version [--json] Show the CLI version",
797
- " metergraph doctor [options] Check a Metergraph service, read only",
798
- " metergraph skill install|update Install or update the agent skill in a project",
799
- " metergraph skills install|update|list Workflow skills, such as the model-swap loop",
800
- " metergraph login [options] Sign in and bind a project to a workspace",
801
- " metergraph logout [options] Revoke and remove a project's sign in",
802
- " metergraph setup [options] Approve and write a private ingest key",
803
- " metergraph verify [options] Find one exact processed trace",
804
- " metergraph status [options] Show configured, reachable and verified state",
805
- " metergraph context [options] Show the verified workspace",
806
- " metergraph capabilities [opts] Show the agent reads offered to this grant",
807
- " metergraph usage [options] Daily usage per route, Metadata only",
808
- " metergraph routes [options] Routes and evaluation contract versions",
809
- " metergraph traces [options] One page of trace metadata",
810
- " metergraph experiments list|estimate|start|get Replay a workload on other models",
811
- " metergraph pricing <action> Public lookup, cost, history and market board",
812
- "",
813
- 'Run "metergraph help doctor" for doctor options.',
814
- 'Run "metergraph help skill" for skill options.',
815
- 'Run "metergraph help skills" for workflow skill options.',
816
- 'Run "metergraph help login" or "metergraph help logout" for sign in options.',
817
- 'Run "metergraph help verify" for exact-trace verification options.',
818
- 'Run "metergraph help COMMAND" for status, context, capabilities, usage, routes or traces.',
819
- 'Run "metergraph help experiments" for experiment options.',
820
- "",
821
- ...AGENT_PATH_HELP,
822
- );
908
+ return rootHelp(style);
823
909
  }
824
- lines.push("", "Exit codes:");
825
- for (const [outcome, code] of Object.entries(EXIT_CODES)) {
826
- lines.push(` ${String(code).padEnd(3)}${outcome.padEnd(25)}${style.dim(EXIT_CODE_MEANINGS[outcome])}`);
910
+ // Reference material is opt-in; everyday help stays focused on this command.
911
+ return commandHelp(topic, lines, style, advanced);
912
+ }
913
+
914
+ function commandHelp(topic, lines, style, advanced) {
915
+ const optionsAt = lines.indexOf("Options:");
916
+ if (optionsAt < 0) return formatHelp(lines, style);
917
+ const optionsEnd = lines.indexOf("", optionsAt + 1);
918
+ const usageEnd = lines.indexOf("");
919
+ const examples = {
920
+ setup: ["metergraph setup", "metergraph setup --runtime local --client codex --json --no-browser"],
921
+ login: ["metergraph login", "metergraph login --runtime local --no-browser"],
922
+ logout: ["metergraph logout"], doctor: ["metergraph doctor"],
923
+ verify: ["metergraph verify --trace-id TRACE_ID --since START_ISO_TIME --until END_ISO_TIME"],
924
+ status: ["metergraph status"], context: ["metergraph context"], capabilities: ["metergraph capabilities"],
925
+ usage: ["metergraph usage --days 7"], routes: ["metergraph routes"], traces: ["metergraph traces --limit 10"],
926
+ skill: ["metergraph skill install --client codex"], skills: ["metergraph skills list"],
927
+ experiments: ["metergraph experiments list", "metergraph experiments estimate --help"],
928
+ }[topic] ?? [];
929
+ const notes = lines.slice(usageEnd + 1, optionsAt);
930
+ const command = helpData(topic).commands.find(entry => entry.name === topic);
931
+ const options = command ? ["Options:", ...optionLines(displayOptions(command.options, topic, null, advanced), 22)]
932
+ : lines.slice(optionsAt, optionsEnd < 0 ? lines.length : optionsEnd);
933
+ const synopsis = {
934
+ setup: "metergraph setup [--client CLIENT | --skip-skill] [options]",
935
+ login: "metergraph login [--runtime local] [options]",
936
+ logout: "metergraph logout [options]",
937
+ verify: "metergraph verify (--trace-id ID | --request-id ID) --since TIME --until TIME [options]",
938
+ ...Object.fromEntries(READ_HELP.map(entry => [entry.name, `metergraph ${entry.name} [options]`])),
939
+ }[topic];
940
+ return formatHelp([
941
+ ...(synopsis ? [`Usage: ${synopsis}`] : lines.slice(0, usageEnd)), "", "Examples:", ...examples.map(example => ` ${example}`),
942
+ ...(topic === "setup" || topic === "login" ? ["", "Local terminals guide missing choices. Scripts require explicit flags.",
943
+ "With --json --no-browser, a step that needs browser approval exits 18 (action_required) with the URL in " +
944
+ "data.next_action.url; approve it, then run the same command again."] : []),
945
+ "", ...options,
946
+ ...(advanced ? ["", "Details", ...notes, ...lines.slice(optionsEnd < 0 ? lines.length : optionsEnd)]
947
+ : ["", `Connection settings and details: metergraph ${topic} --help --advanced`]),
948
+ ], style);
949
+ }
950
+
951
+ function rootHelp(style) {
952
+ const groups = [
953
+ ["Get started", [
954
+ ["setup", "Connect your app and install an agent skill"],
955
+ ["login", "Sign in to a workspace"],
956
+ ["status", "Check this project's connection"],
957
+ ]],
958
+ ["Explore", [
959
+ ["usage", "View costs and token usage"],
960
+ ["traces", "Browse trace metadata"],
961
+ ["routes", "List routes and evaluation contracts"],
962
+ ["experiments", "Estimate and run model comparisons"],
963
+ ["tools", "List and call the tools your coding agent can use"],
964
+ ["pricing", "Look up public model prices; no account needed"],
965
+ ]],
966
+ ["Manage", [
967
+ ["verify", "Verify one processed trace"],
968
+ ["doctor", "Check service health"],
969
+ ["context", "Show the connected workspace"],
970
+ ["capabilities", "Show available workspace reads"],
971
+ ["skill", "Install or update the setup skill"],
972
+ ["skills", "Install, update or list workflow skills"],
973
+ ["logout", "Sign out of this project"],
974
+ ]],
975
+ ];
976
+ const lines = [style.bold("Metergraph") + " " + style.dim(`${VERSION} · preview`),
977
+ "Understand your AI usage and compare models.", "", "Usage: metergraph <command> [options]", ""];
978
+ for (const [title, commands] of groups) {
979
+ lines.push(section(style, title), ...fields(style, commands), "");
827
980
  }
828
- // Section labels ("Usage:", "Options:", "Exit codes:") in bold.
829
- return `${lines.map((line) => line.replace(/^(Usage|Options|Exit codes):/, (label) => style.bold(label))).join("\n")}\n`;
981
+ lines.push(section(style, "Try it"),
982
+ " metergraph setup --runtime local --client codex",
983
+ " metergraph pricing models --limit 10", "",
984
+ 'Run "metergraph <command> --help" for options and examples.',
985
+ 'Use --json for scripts; --version to show the version.',
986
+ 'Scripting reference: metergraph help exit-codes.',
987
+ 'Connecting a cloud agent? Run "metergraph help agents".');
988
+ return formatHelp(lines, style);
989
+ }
990
+
991
+ function formatHelp(lines, style) {
992
+ return render(lines.flatMap((line) => {
993
+ if (!line) return [""];
994
+ if (line.length <= style.width && !/^(Usage|Options|Examples|Exit codes):/.test(line)) return [line];
995
+ if (/^(Usage|Options|Examples|Exit codes):/.test(line)) {
996
+ const [first, ...rest] = paragraph(style, line, "");
997
+ return [style.bold(first), ...rest];
998
+ }
999
+ // Preserve option and command columns, with a hanging indent on small terminals.
1000
+ const row = /^( \S.*?) {2,}(\S.*)$/.exec(line);
1001
+ if (row && Number.isFinite(style.width)) {
1002
+ const prefix = row[1] + " ";
1003
+ if (prefix.length > style.width / 2) {
1004
+ return [row[1], ...paragraph(style, row[2], " ")];
1005
+ }
1006
+ const wrapped = paragraph(style, row[2], " ".repeat(prefix.length));
1007
+ return [prefix + wrapped[0].trimStart(), ...wrapped.slice(1)];
1008
+ }
1009
+ return paragraph(style, line.trimStart(), line.match(/^ */)[0]);
1010
+ }));
1011
+
830
1012
  }
831
1013
 
832
1014
  export function skillText(result, message, style = PLAIN) {
@@ -987,6 +1169,8 @@ export function doctorText(result, style = PLAIN) {
987
1169
  // Fixed text for every login, logout and session reason. Reasons shared
988
1170
  // with doctor fall back to its wording.
989
1171
  const AUTH_MESSAGES = {
1172
+ local_docker_transport_required:
1173
+ "Local credentials and consent require a verified Docker transport. This CLI has no configured Docker transport; nothing was sent. Update the local bundle and use its Docker-mediated bin/agent-key command, or connect this CLI to an authenticated HTTPS deployment.",
990
1174
  runtime_not_supported:
991
1175
  "--runtime says where this terminal runs, not which Metergraph service you sign in to. " +
992
1176
  "A cloud runtime is a remote sandbox, and sign in needs a browser on the same machine as the CLI, " +
@@ -1021,6 +1205,7 @@ const AUTH_MESSAGES = {
1021
1205
  oauth_metadata_missing: "This service does not support CLI sign in yet. Nothing was written.",
1022
1206
  metadata_scope_unsupported:
1023
1207
  "This service does not offer Metadata-only access for the CLI yet. The CLI does not fall back to broader access.",
1208
+ debug_scope_unsupported: "This service does not offer Debug access for CLI sign in. Nothing was saved.",
1024
1209
  endpoint_not_allowed: "The service advertised an authorization endpoint outside its own origin or known paths.",
1025
1210
  resource_mismatch: "The service's protected resource does not match this origin.",
1026
1211
  issuer_mismatch: "The service's authorization issuer does not match this origin.",
@@ -1037,6 +1222,14 @@ const AUTH_MESSAGES = {
1037
1222
  client_mismatch: "The issued grant is for a different client. Nothing was saved.",
1038
1223
  scope_mismatch: "The issued grant is not limited to exactly the Metadata scope. It was not kept.",
1039
1224
  workspace_mismatch: "The browser granted a different workspace than --workspace. It was not kept.",
1225
+ workspace_hint_ambiguous:
1226
+ "More than one of your workspaces has the name --workspace gave, so the consent page offered no approval. " +
1227
+ "Nothing was granted.",
1228
+ workspace_hint_unknown:
1229
+ "None of your workspaces has the slug or name --workspace gave, so the consent page offered no approval. " +
1230
+ "Nothing was granted.",
1231
+ workspace_name_unsupported:
1232
+ "This service cannot match a workspace by name. Use its slug, which its Settings page shows. Nothing was sent.",
1040
1233
  workspace_context_mismatch: "The service reported a different workspace than the grant names. It was not kept.",
1041
1234
  profile_mismatch: "The service reported a different deployment profile than before sign in. It was not kept.",
1042
1235
  deployment_profile_mismatch:
@@ -1047,6 +1240,8 @@ const AUTH_MESSAGES = {
1047
1240
  "The grant would allow content, replay or provider access, which login never accepts. It was not kept.",
1048
1241
  access_rejected: "The service refused the new grant. Nothing was saved.",
1049
1242
  invalid_project: "--project must name an existing directory.",
1243
+ secure_writes_unavailable:
1244
+ "Secure automatic file writes are unavailable on this platform or the bundled filesystem helper failed verification. Nothing was written.",
1050
1245
  invalid_config_dir: "METERGRAPH_CONFIG_DIR must be an absolute path.",
1051
1246
  unsafe_path: "A path in .metergraph is a symbolic link or is not a regular file or directory. Nothing was changed.",
1052
1247
  binding_invalid: "The project binding .metergraph/project.json is not valid. Nothing was changed.",
@@ -1123,7 +1318,7 @@ export function authText(result, message, style = PLAIN) {
1123
1318
  const lines = [headline(style, toneFor(result.outcome), title, result.outcome), ""];
1124
1319
  const rows = [
1125
1320
  ["Origin", report.origin],
1126
- ["Workspace", report.workspace?.id ?? null],
1321
+ ["Workspace", workspaceShown(report.workspace)],
1127
1322
  ["Profile", report.deployment_profile || null],
1128
1323
  ];
1129
1324
  if (login && result.ok) {