metergraph-cli 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -11
- package/assets/skill/SKILL.md +52 -3
- package/assets/skill/manifest.json +3 -3
- package/assets/skills/manifest.json +13 -13
- package/assets/skills/metergraph-analyze/SKILL.md +62 -4
- package/assets/skills/metergraph-investigate/SKILL.md +222 -35
- package/assets/skills/metergraph-onboarding/SKILL.md +7 -0
- package/assets/skills/metergraph-report/SKILL.md +19 -2
- package/bin/metergraph.js +1 -1
- package/package.json +1 -1
- package/src/agent-key.js +160 -0
- package/src/approval-handoff.js +5 -0
- package/src/args.js +572 -17
- package/src/auth-browser.js +5 -19
- package/src/auth-callback.js +24 -15
- package/src/auth-login.js +55 -12
- package/src/auth-oauth.js +77 -7
- package/src/callback-page.js +58 -0
- package/src/cli.js +190 -45
- package/src/constants.js +73 -3
- package/src/environment.js +169 -0
- package/src/experiments-contract.js +462 -0
- package/src/experiments-output.js +189 -0
- package/src/experiments.js +397 -0
- package/src/origin.js +6 -0
- package/src/output.js +437 -131
- package/src/pricing-args.js +163 -0
- package/src/pricing-output.js +206 -0
- package/src/pricing-reference.js +195 -0
- package/src/pricing.js +307 -0
- package/src/read-contract.js +976 -16
- package/src/read-output.js +449 -30
- package/src/read.js +312 -60
- package/src/setup-deployment.js +11 -3
- package/src/setup-interactive.js +341 -0
- package/src/setup-refusal.js +172 -0
- package/src/setup-state.js +4 -2
- package/src/setup.js +227 -53
- package/src/skill-bundle.js +2 -2
- package/src/style.js +101 -0
- package/src/transport.js +13 -7
- package/src/usage-aggregates.js +197 -0
- package/src/verify-output.js +21 -11
package/src/output.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PRICING_OPTIONS } from "./pricing-args.js";
|
|
1
2
|
import {
|
|
2
3
|
CONNECTION_GUIDE_URL,
|
|
3
4
|
DEFAULT_ORIGIN,
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
SUPPORTED_PROFILES,
|
|
28
29
|
VERSION,
|
|
29
30
|
} from "./constants.js";
|
|
31
|
+
import { PLAIN, fields, headline, paragraph, render, section, steps, toneFor } from "./style.js";
|
|
30
32
|
|
|
31
33
|
// Every JSON result, success or failure, has the same top-level keys:
|
|
32
34
|
// schema_version, command, ok, outcome, exit_code, data, error
|
|
@@ -115,6 +117,20 @@ const AGENT_APPROVAL_HELP = [
|
|
|
115
117
|
"and after they approve, run the same command again to continue.",
|
|
116
118
|
];
|
|
117
119
|
|
|
120
|
+
// One recommended sequence per place an agent runs. login and setup detect
|
|
121
|
+
// SSH, cloud workspaces and CI and answer with a structured next_action.
|
|
122
|
+
const AGENT_PATH_HELP = [
|
|
123
|
+
"Where the agent runs:",
|
|
124
|
+
" Local shell or local container setup --runtime local --client C --json --no-browser,",
|
|
125
|
+
" rerun after each approval, then status --json",
|
|
126
|
+
" SSH, cloud workspace or CI A person creates a coding-agent key with Metadata access",
|
|
127
|
+
" on the Keys page and saves it as the secret",
|
|
128
|
+
" METERGRAPH_AGENT_TOKEN; then status --json, usage --json",
|
|
129
|
+
" Metadata key, no saved sign in The same: reads use METERGRAPH_AGENT_TOKEN, or",
|
|
130
|
+
" --agent-token-file PATH, after verifying the workspace",
|
|
131
|
+
"Never pass a key as an argument. Debug and Replay keys are refused, not reduced.",
|
|
132
|
+
];
|
|
133
|
+
|
|
118
134
|
const CONFIG_DIR_OPTION = {
|
|
119
135
|
name: "--config-dir",
|
|
120
136
|
value: "DIR",
|
|
@@ -127,7 +143,9 @@ const LOGIN_OPTIONS = [
|
|
|
127
143
|
{
|
|
128
144
|
name: "--runtime",
|
|
129
145
|
value: "RUNTIME",
|
|
130
|
-
summary:
|
|
146
|
+
summary:
|
|
147
|
+
"Required. Where this terminal runs, not which Metergraph service. local: your own machine, " +
|
|
148
|
+
"including for the hosted service. cloud, cloud-no-shell: a cloud sandbox; get the connection guide.",
|
|
131
149
|
},
|
|
132
150
|
{ name: "--url", value: "ORIGIN", summary: `Origin to sign in to. Default ${DEFAULT_ORIGIN}.` },
|
|
133
151
|
{
|
|
@@ -195,24 +213,92 @@ const READ_BASE_OPTIONS = [
|
|
|
195
213
|
`Total time for every request of the command, ${READ_MIN_TIMEOUT_MS} to ${READ_MAX_TIMEOUT_MS}. ` +
|
|
196
214
|
`Default ${READ_DEFAULT_TIMEOUT_MS}.`,
|
|
197
215
|
},
|
|
216
|
+
{
|
|
217
|
+
name: "--url",
|
|
218
|
+
value: "ORIGIN",
|
|
219
|
+
summary:
|
|
220
|
+
"Where a Metadata key is sent when the project is not bound. Default METERGRAPH_URL, else the hosted service. " +
|
|
221
|
+
"A bound project's origin cannot be changed here.",
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
name: "--workspace",
|
|
225
|
+
value: "UUID",
|
|
226
|
+
summary: "The workspace the credential must be for. Without it, an unbound key's own workspace is used.",
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: "--agent-token-file",
|
|
230
|
+
value: "PATH",
|
|
231
|
+
summary:
|
|
232
|
+
"Private (0600) file holding a coding-agent key with Metadata access. Used instead of a saved sign in. " +
|
|
233
|
+
"Without it, METERGRAPH_AGENT_TOKEN is used when this project has no saved sign in on this machine.",
|
|
234
|
+
},
|
|
235
|
+
];
|
|
236
|
+
const EXPERIMENTS_USAGE = [
|
|
237
|
+
"metergraph experiments list [--workload ID] [--limit N] [read options] [--json]",
|
|
238
|
+
"metergraph experiments estimate --workload ID --treatment SPEC [--treatment SPEC]... [--last-n N] " +
|
|
239
|
+
"[--sample-model MODEL] [--track PATH]... [read options] [--json]",
|
|
240
|
+
"metergraph experiments start (same as estimate) [--agent-token-file PATH] [--json]",
|
|
241
|
+
"metergraph experiments get --id UUID [read options] [--json]",
|
|
242
|
+
];
|
|
243
|
+
const EXPERIMENTS_OPTIONS = [
|
|
244
|
+
{ name: "--workload", value: "ID", summary: "The workload (route) whose recent production calls are replayed. Required for estimate and start; a filter for list." },
|
|
245
|
+
{
|
|
246
|
+
name: "--treatment",
|
|
247
|
+
value: "SPEC",
|
|
248
|
+
summary:
|
|
249
|
+
"1 to 3 times. PROVIDER/MODEL, optionally followed by ,label=NAME ,temperature=0..2 ,max_tokens=1..32000 " +
|
|
250
|
+
"or ,reasoning_effort=none|minimal|low|medium|high|xhigh|max. Unset settings follow production.",
|
|
251
|
+
},
|
|
252
|
+
{ name: "--last-n", value: "N", summary: "Replay the last N replayable calls, 10 to 40. Default 20." },
|
|
253
|
+
{ name: "--sample-model", value: "MODEL", summary: "Only production calls made with this exact model, for a single-model control." },
|
|
254
|
+
{ name: "--track", value: "PATH", summary: "Up to 10 times. A JSON path such as $.priority whose values the dashboard compares." },
|
|
255
|
+
{ name: "--id", value: "UUID", summary: "The experiment to read (get)." },
|
|
256
|
+
{ name: "--limit", value: "N", summary: "Experiments to list, 1 to 50. Default 20." },
|
|
257
|
+
...READ_BASE_OPTIONS.filter((option) => option.name !== "--agent-token-file"),
|
|
258
|
+
{
|
|
259
|
+
name: "--agent-token-file",
|
|
260
|
+
value: "PATH",
|
|
261
|
+
summary:
|
|
262
|
+
"Private (0600) file holding a coding-agent key. For list, estimate and get: a key with Metadata access. " +
|
|
263
|
+
"For start: a key with Debug access and Allow analysis starts, used for that one request.",
|
|
264
|
+
},
|
|
265
|
+
JSON_OPTION,
|
|
198
266
|
];
|
|
199
267
|
const DAYS_OPTION = {
|
|
200
268
|
name: "--days",
|
|
201
269
|
value: "N",
|
|
202
270
|
summary: `Window of the last N days, 1 to ${READ_MAX_DAYS}. Default ${READ_DEFAULT_DAYS}.`,
|
|
203
271
|
};
|
|
272
|
+
const METADATA_OPTIONS = [
|
|
273
|
+
{
|
|
274
|
+
name: "--metadata",
|
|
275
|
+
value: "KEY=VALUE",
|
|
276
|
+
summary:
|
|
277
|
+
"Only calls whose metadata (SDK tags) has exactly this string value: case-sensitive, no prefix or " +
|
|
278
|
+
"trimming. Repeat for up to 5 keys, all must match. A call without the key never matches; KEY= " +
|
|
279
|
+
"matches only an empty value. Splits at the first =, so a value may contain =.",
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
name: "--metadata-json",
|
|
283
|
+
value: "JSON",
|
|
284
|
+
summary:
|
|
285
|
+
'The same filters as one JSON object, for keys that contain = (for example {"a=b":"v"}). Not with --metadata.',
|
|
286
|
+
},
|
|
287
|
+
];
|
|
204
288
|
const limitOption = (fallback, extra = "") => ({
|
|
205
289
|
name: "--limit",
|
|
206
290
|
value: "N",
|
|
207
291
|
summary: `Rows to return, 1 to ${READ_MAX_LIMIT}. Default ${fallback}.${extra}`,
|
|
208
292
|
});
|
|
209
|
-
const READ_BASE_USAGE =
|
|
293
|
+
const READ_BASE_USAGE =
|
|
294
|
+
"[--url ORIGIN] [--workspace UUID] [--agent-token-file PATH] [--project DIR] [--config-dir DIR] [--timeout-ms N] [--json]";
|
|
210
295
|
const REFUSED_NOTE =
|
|
211
296
|
"--environment, --workload, --since, --until, --sql, --query, --content, --include-content, --debug and --replay " +
|
|
212
|
-
"are recognized and refused with exit code 6 before any request.";
|
|
297
|
+
"are recognized and refused with exit code 6 before any request, except --environment, --since and --until on usage.";
|
|
213
298
|
|
|
214
|
-
// The read commands. Each uses the project's saved Metadata grant
|
|
215
|
-
//
|
|
299
|
+
// 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.
|
|
216
302
|
export const READ_HELP = Object.freeze([
|
|
217
303
|
{
|
|
218
304
|
name: "status",
|
|
@@ -237,9 +323,82 @@ export const READ_HELP = Object.freeze([
|
|
|
237
323
|
},
|
|
238
324
|
{
|
|
239
325
|
name: "usage",
|
|
240
|
-
usage:
|
|
241
|
-
|
|
242
|
-
|
|
326
|
+
usage:
|
|
327
|
+
`metergraph usage [--days N | --today | --since TIME [--until TIME]] [--group-by DIMS] ` +
|
|
328
|
+
`[--route NAME]... [--provider NAME]... [--model NAME]... [--environment NAME]... ` +
|
|
329
|
+
`[--sort ORDER] [--compare previous|previous-same-time] [--limit N] ` +
|
|
330
|
+
`[--metadata KEY=VALUE]... [--metadata-json JSON] [--group-by-metadata KEY] ${READ_BASE_USAGE}`,
|
|
331
|
+
summary:
|
|
332
|
+
"Show calls, errors, cost, tokens and latency. With --group-by, a filter, a UTC window, --sort or --compare " +
|
|
333
|
+
"it reads grouped usage: cost by provider, model, route or UTC day, computed per call by the service, with " +
|
|
334
|
+
"totals over every matching call and coverage, pricing and attribution evidence. Without them it lists cost " +
|
|
335
|
+
"per route and day for the last N days. A route is not a prompt: one route can carry several prompt IDs, " +
|
|
336
|
+
'so "metergraph usage --group-by metadata.prompt_id" splits cost by prompt. "metergraph routes" lists ' +
|
|
337
|
+
"route definitions, not their cost.",
|
|
338
|
+
examples: [
|
|
339
|
+
["Most expensive route today", "metergraph usage --group-by route --today"],
|
|
340
|
+
["Spend with one provider this week", "metergraph usage --group-by provider --provider NAME --days 7"],
|
|
341
|
+
["Spend by model", "metergraph usage --group-by model --days 7"],
|
|
342
|
+
["One route's daily trend", "metergraph usage --route NAME --group-by day --days 14"],
|
|
343
|
+
["Cost by prompt ID", "metergraph usage --group-by metadata.prompt_id --days 7"],
|
|
344
|
+
["Today against yesterday", "metergraph usage --group-by route --today --compare previous"],
|
|
345
|
+
],
|
|
346
|
+
options: [
|
|
347
|
+
...READ_BASE_OPTIONS,
|
|
348
|
+
{
|
|
349
|
+
name: "--days",
|
|
350
|
+
value: "N",
|
|
351
|
+
summary:
|
|
352
|
+
`Window, 1 to ${READ_MAX_DAYS}. Default ${READ_DEFAULT_DAYS}. Grouped: the current UTC day so far and the ` +
|
|
353
|
+
"N - 1 whole UTC days before it (window.kind utc_days). Otherwise the last N x 24 hours, rolling and not " +
|
|
354
|
+
"aligned to UTC days (window.kind rolling).",
|
|
355
|
+
},
|
|
356
|
+
{ name: "--today", value: null, summary: "Grouped: the current UTC day so far (--days 1)." },
|
|
357
|
+
{
|
|
358
|
+
name: "--since",
|
|
359
|
+
value: "TIME",
|
|
360
|
+
summary:
|
|
361
|
+
"Grouped: window start, inclusive. A UTC date (YYYY-MM-DD, 00:00 UTC) or an ISO 8601 timestamp with Z " +
|
|
362
|
+
`or an offset. At most ${READ_MAX_DAYS} days before --until.`,
|
|
363
|
+
},
|
|
364
|
+
{ name: "--until", value: "TIME", summary: "Grouped: window end, exclusive, in the same form. Default now." },
|
|
365
|
+
{
|
|
366
|
+
name: "--group-by",
|
|
367
|
+
value: "DIMS",
|
|
368
|
+
summary: "Comma-separated, in order: provider, model, route, day (UTC calendar day). Default provider,model.",
|
|
369
|
+
},
|
|
370
|
+
{ name: "--route", value: "NAME", summary: "Grouped: only this route, exactly. Repeat for up to 20; (unrouted) selects calls without one." },
|
|
371
|
+
{ name: "--provider", value: "NAME", summary: "Grouped: only this provider, exactly. Repeat for up to 20." },
|
|
372
|
+
{ name: "--model", value: "NAME", summary: "Grouped: only this model, exactly. Repeat for up to 20." },
|
|
373
|
+
{ name: "--environment", value: "NAME", summary: "Grouped: only this environment. Repeat for several. A restricted grant reads its own environments only." },
|
|
374
|
+
{
|
|
375
|
+
name: "--sort",
|
|
376
|
+
value: "ORDER",
|
|
377
|
+
summary:
|
|
378
|
+
"cost (default), cost-per-call, calls, errors, avg-latency (mean over calls that reported latency), " +
|
|
379
|
+
"p95-latency (estimated from a fixed histogram) or day (oldest first; the default for --group-by day). " +
|
|
380
|
+
"The service returns the highest-cost groups, so another order over a cut list is marked partial.",
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
name: "--compare",
|
|
384
|
+
value: "MODE",
|
|
385
|
+
summary:
|
|
386
|
+
"Also read the period before and show the change per group. previous: the whole prior period, which is " +
|
|
387
|
+
"longer than a window ending part way through today (a warning says so). previous-same-time: the prior " +
|
|
388
|
+
"period to the same elapsed time. Not with --group-by day.",
|
|
389
|
+
},
|
|
390
|
+
limitOption(READ_DEFAULT_LIMIT, " Grouped: the most groups listed; totals still cover every call."),
|
|
391
|
+
...METADATA_OPTIONS,
|
|
392
|
+
{
|
|
393
|
+
name: "--group-by-metadata",
|
|
394
|
+
value: "KEY",
|
|
395
|
+
summary:
|
|
396
|
+
"Group by one metadata key: the same as ending --group-by with metadata.KEY, and the way to give a key " +
|
|
397
|
+
"that holds a comma. Calls without the key or with a non-string value are one group with no value, and " +
|
|
398
|
+
"the spend not attributed to any value is totalled.",
|
|
399
|
+
},
|
|
400
|
+
JSON_OPTION,
|
|
401
|
+
],
|
|
243
402
|
},
|
|
244
403
|
{
|
|
245
404
|
name: "routes",
|
|
@@ -260,7 +419,7 @@ export const READ_HELP = Object.freeze([
|
|
|
260
419
|
name: "traces",
|
|
261
420
|
usage:
|
|
262
421
|
`metergraph traces [--days N] [--limit N] [--route NAME] [--status success|error] ` +
|
|
263
|
-
`[--cursor CURSOR] ${READ_BASE_USAGE}`,
|
|
422
|
+
`[--metadata KEY=VALUE]... [--metadata-json JSON] [--cursor CURSOR] ${READ_BASE_USAGE}`,
|
|
264
423
|
summary:
|
|
265
424
|
"List one page of trace metadata: status, span count, tokens, cost, routes, providers and models. " +
|
|
266
425
|
"No prompts, responses or tool calls.",
|
|
@@ -270,6 +429,7 @@ export const READ_HELP = Object.freeze([
|
|
|
270
429
|
limitOption(TRACES_DEFAULT_LIMIT),
|
|
271
430
|
{ name: "--route", value: "NAME", summary: "Only traces that include this route." },
|
|
272
431
|
{ name: "--status", value: "STATUS", summary: "Only success or error traces." },
|
|
432
|
+
...METADATA_OPTIONS,
|
|
273
433
|
{
|
|
274
434
|
name: "--cursor",
|
|
275
435
|
value: "CURSOR",
|
|
@@ -280,6 +440,15 @@ export const READ_HELP = Object.freeze([
|
|
|
280
440
|
},
|
|
281
441
|
]);
|
|
282
442
|
|
|
443
|
+
// One " FLAG summary" line per option. The flag column keeps its usual
|
|
444
|
+
// width but grows to fit the longest flag, so a flag never runs into its
|
|
445
|
+
// summary.
|
|
446
|
+
function optionLines(options, minimum) {
|
|
447
|
+
const flags = options.map((option) => (option.value ? `${option.name} ${option.value}` : option.name));
|
|
448
|
+
const width = Math.max(minimum, ...flags.map((flag) => flag.length + 2));
|
|
449
|
+
return options.map((option, index) => ` ${flags[index].padEnd(width)}${option.summary}`);
|
|
450
|
+
}
|
|
451
|
+
|
|
283
452
|
export function helpData(topic) {
|
|
284
453
|
return {
|
|
285
454
|
topic,
|
|
@@ -287,6 +456,7 @@ export function helpData(topic) {
|
|
|
287
456
|
"metergraph --help [--json]",
|
|
288
457
|
"metergraph --version [--json]",
|
|
289
458
|
"metergraph doctor [--url ORIGIN] [--timeout-ms N] [--json]",
|
|
459
|
+
"metergraph pricing models|lookup|calculate|cost|history|market [options] [--json]",
|
|
290
460
|
...SKILL_USAGE,
|
|
291
461
|
...SKILLS_USAGE,
|
|
292
462
|
LOGIN_USAGE,
|
|
@@ -294,8 +464,12 @@ export function helpData(topic) {
|
|
|
294
464
|
SETUP_USAGE,
|
|
295
465
|
VERIFY_USAGE,
|
|
296
466
|
...READ_HELP.map((entry) => entry.usage),
|
|
467
|
+
...EXPERIMENTS_USAGE,
|
|
297
468
|
],
|
|
298
469
|
commands: [
|
|
470
|
+
{ name: "pricing", summary: "Public model discovery, lookup and calculation. Current market board; historical charts in local reference mode.",
|
|
471
|
+
options: [...PRICING_OPTIONS.map(name => ({ name, value: "VALUE", summary: "See metergraph help pricing and docs/pricing.md." })),
|
|
472
|
+
{ name: "--batch", value: null, summary: "Use batch pricing for cost." }, JSON_OPTION] },
|
|
299
473
|
{
|
|
300
474
|
name: "doctor",
|
|
301
475
|
summary:
|
|
@@ -351,7 +525,7 @@ export function helpData(topic) {
|
|
|
351
525
|
name: "setup",
|
|
352
526
|
summary: "Guide browser sign in and workspace choice, approve an ingest-only key, and install the selected client skill. Does not verify application traffic.",
|
|
353
527
|
options: [
|
|
354
|
-
{ name: "--runtime", value: "RUNTIME", summary: "Required. local only; other runtimes get a handoff." },
|
|
528
|
+
{ name: "--runtime", value: "RUNTIME", summary: "Required unless a person runs setup at a terminal (then local). local only; other runtimes get a handoff." },
|
|
355
529
|
{ name: "--url", value: "ORIGIN", summary: "Deployment origin. Defaults to an existing project binding, else the hosted origin." },
|
|
356
530
|
{ name: "--workspace", value: "UUID", summary: "Expected workspace; the browser must approve this exact workspace." },
|
|
357
531
|
{ name: "--project", value: "DIR", summary: "Project directory. Default: current directory." },
|
|
@@ -377,7 +551,20 @@ export function helpData(topic) {
|
|
|
377
551
|
summary: "Poll Metadata for one exact trace in an explicit invocation window. Never sends application traffic.",
|
|
378
552
|
options: VERIFY_OPTIONS,
|
|
379
553
|
},
|
|
380
|
-
|
|
554
|
+
{
|
|
555
|
+
name: "experiments",
|
|
556
|
+
summary:
|
|
557
|
+
"Estimate, start, list and read experiments: a workload's recent production calls replayed on 1 to 3 " +
|
|
558
|
+
"treatments, with built-in statistics per arm next to production. list, estimate and get are Metadata " +
|
|
559
|
+
"reads; start needs a key with Debug access and Allow analysis starts and spends within the server's budget.",
|
|
560
|
+
options: EXPERIMENTS_OPTIONS,
|
|
561
|
+
},
|
|
562
|
+
...READ_HELP.map(({ name, summary, options, examples }) => ({
|
|
563
|
+
name,
|
|
564
|
+
summary,
|
|
565
|
+
options,
|
|
566
|
+
...(examples ? { examples: examples.map(([question, command]) => ({ question, command })) } : {}),
|
|
567
|
+
})),
|
|
381
568
|
],
|
|
382
569
|
skill_clients: Object.keys(SKILL_CLIENTS),
|
|
383
570
|
skill_runtimes: [...SKILL_RUNTIMES],
|
|
@@ -391,8 +578,45 @@ export function helpData(topic) {
|
|
|
391
578
|
};
|
|
392
579
|
}
|
|
393
580
|
|
|
394
|
-
export function helpText(topic) {
|
|
581
|
+
export function helpText(topic, style = PLAIN) {
|
|
395
582
|
const lines = [];
|
|
583
|
+
if (topic === "pricing") {
|
|
584
|
+
return `Usage: metergraph pricing models|lookup|calculate|cost|history|market [options] [--json]
|
|
585
|
+
|
|
586
|
+
Public catalog prices. No account, project or token is required.
|
|
587
|
+
models [--model ID] [--provider ID | --channel ID] [--region ID] [--limit 1..100]
|
|
588
|
+
lookup --model ID [--provider ID | --channel ID] [--region ID] [--at YYYY-MM-DD]
|
|
589
|
+
calculate (or cost) Same selectors; --input-tokens N --output-tokens N [--batch]
|
|
590
|
+
--cache-read-tokens N; --trace-format anthropic.messages|openai.responses|
|
|
591
|
+
openai.chat_completions|otel.gen_ai. Native format inferred for direct
|
|
592
|
+
Anthropic/OpenAI; otherwise otel.gen_ai. Only supplied counts are sent.
|
|
593
|
+
Anthropic also accepts --cache-write-tokens N or
|
|
594
|
+
--cache-write-5m-tokens N / --cache-write-1h-tokens N.
|
|
595
|
+
market --channel ID [--region ID] [--limit 1..100] (current rates, no history)
|
|
596
|
+
history --model ID --channel ID [--region ID] [--format table|chart|html]
|
|
597
|
+
Requires --api reference --url LOOPBACK_ORIGIN; no public history API.
|
|
598
|
+
|
|
599
|
+
All actions: --url ORIGIN --timeout-ms 100..30000 --json --api public|reference
|
|
600
|
+
Default API: public; default origin: https://app.metergraph.dev.
|
|
601
|
+
--at selects UTC midnight; without it public lookup/calculation use server time.
|
|
602
|
+
Models and public market discovery are current-only. Filters on models run locally.
|
|
603
|
+
History/market region defaults to global. Omitted input/output counts stay unknown;
|
|
604
|
+
partial costs are known subtotals, not complete costs. OpenAI cache counts must
|
|
605
|
+
not exceed total input. --json cannot be combined with --format.
|
|
606
|
+
|
|
607
|
+
Examples:
|
|
608
|
+
metergraph pricing models --limit 10
|
|
609
|
+
metergraph pricing lookup --model gpt-5 --provider openai --region global --json
|
|
610
|
+
metergraph pricing calculate --model claude-sonnet-5 --provider anthropic --input-tokens 1000 --cache-read-tokens 800 --output-tokens 200
|
|
611
|
+
metergraph pricing market --channel openai-api --limit 5
|
|
612
|
+
metergraph pricing history --model openai/gpt-5.6-terra --channel openai-api --api reference --url http://127.0.0.1:8765 --format html > history.html
|
|
613
|
+
|
|
614
|
+
Public API: GET /v1/pricing/models; POST /v1/pricing/lookup and /calculate.
|
|
615
|
+
No credentials, traces, prompts or responses are loaded from project files.
|
|
616
|
+
The CLI constructs metadata-only provider-format traces from the supplied counts.
|
|
617
|
+
See docs/pricing.md for the supported contract, limitations and reference previews.
|
|
618
|
+
`;
|
|
619
|
+
}
|
|
396
620
|
if (topic === "doctor") {
|
|
397
621
|
lines.push(
|
|
398
622
|
"Usage: metergraph doctor [--url ORIGIN] [--timeout-ms N] [--json]",
|
|
@@ -402,10 +626,7 @@ export function helpText(topic) {
|
|
|
402
626
|
"",
|
|
403
627
|
"Options:",
|
|
404
628
|
);
|
|
405
|
-
|
|
406
|
-
const flag = option.value ? `${option.name} ${option.value}` : option.name;
|
|
407
|
-
lines.push(` ${flag.padEnd(18)}${option.summary}`);
|
|
408
|
-
}
|
|
629
|
+
lines.push(...optionLines(DOCTOR_OPTIONS, 18));
|
|
409
630
|
lines.push(
|
|
410
631
|
"",
|
|
411
632
|
"ORIGIN must be a bare https origin such as https://metergraph.example.com.",
|
|
@@ -421,11 +642,10 @@ export function helpText(topic) {
|
|
|
421
642
|
"--skip-skill is explicit. Non-hosted setup requires an explicit origin, workspace and",
|
|
422
643
|
"operator prerequisite attestation; OSS remains an operator handoff. No application",
|
|
423
644
|
"traffic is claimed until an exact instrumented invocation is separately verified.",
|
|
645
|
+
"", "At a terminal without --json, setup asks for any of these it needs that are",
|
|
646
|
+
"missing, prints the full command, then runs it. Scripts and agents get no questions.",
|
|
424
647
|
"", "Options:");
|
|
425
|
-
|
|
426
|
-
const flag = option.value ? `${option.name} ${option.value}` : option.name;
|
|
427
|
-
lines.push(` ${flag.padEnd(25)}${option.summary}`);
|
|
428
|
-
}
|
|
648
|
+
lines.push(...optionLines(helpData(null).commands.find((entry) => entry.name === "setup").options, 25));
|
|
429
649
|
lines.push("", ...AGENT_APPROVAL_HELP);
|
|
430
650
|
} else if (topic === "login" || topic === "logout") {
|
|
431
651
|
const login = topic === "login";
|
|
@@ -455,20 +675,38 @@ export function helpText(topic) {
|
|
|
455
675
|
);
|
|
456
676
|
}
|
|
457
677
|
lines.push("Options:");
|
|
458
|
-
|
|
459
|
-
const flag = option.value ? `${option.name} ${option.value}` : option.name;
|
|
460
|
-
lines.push(` ${flag.padEnd(18)}${option.summary}`);
|
|
461
|
-
}
|
|
678
|
+
lines.push(...optionLines(login ? LOGIN_OPTIONS : LOGOUT_OPTIONS, 18));
|
|
462
679
|
if (login) lines.push("", ...AGENT_APPROVAL_HELP);
|
|
463
680
|
} else if (topic === "verify") {
|
|
464
681
|
lines.push(`Usage: ${VERIFY_USAGE}`, "", "Uses the saved Metadata grant to poll for one exact, processed trace.",
|
|
465
682
|
"The source label is supplied by the caller. A Metadata match alone does not prove application traffic.",
|
|
466
683
|
"Opening requires a server link that selects the verified workspace; older links get a manual handoff.",
|
|
467
684
|
"", "Options:");
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
685
|
+
lines.push(...optionLines(VERIFY_OPTIONS, 22));
|
|
686
|
+
} else if (topic === "experiments") {
|
|
687
|
+
lines.push(
|
|
688
|
+
"Usage:",
|
|
689
|
+
...EXPERIMENTS_USAGE.map((usage) => ` ${usage}`),
|
|
690
|
+
"",
|
|
691
|
+
"An experiment replays a workload's last 10 to 40 replayable production calls on 1 to 3 treatments",
|
|
692
|
+
"(a provider, model and settings) and compares each with what production returned on the same calls:",
|
|
693
|
+
"cost per call, p50 latency and output tokens, output length, JSON parse rate, same top-level keys,",
|
|
694
|
+
"empty or refused and cut-off rates. The statistics show differences; they do not judge them.",
|
|
695
|
+
"",
|
|
696
|
+
"estimate prices it against the server's budget and daily cap and writes nothing. start takes the",
|
|
697
|
+
"same arguments, so it starts exactly what was priced; poll it with get until it is terminal.",
|
|
698
|
+
"Each start creates a new experiment, so a start that succeeded is never retried.",
|
|
699
|
+
"",
|
|
700
|
+
"list, estimate and get use this project's saved Metadata grant or a Metadata key, as the read",
|
|
701
|
+
"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.",
|
|
706
|
+
"",
|
|
707
|
+
"Options:",
|
|
708
|
+
);
|
|
709
|
+
lines.push(...optionLines(EXPERIMENTS_OPTIONS, 22));
|
|
472
710
|
} else if (READ_HELP.some((entry) => entry.name === topic)) {
|
|
473
711
|
const entry = READ_HELP.find((candidate) => candidate.name === topic);
|
|
474
712
|
lines.push(
|
|
@@ -476,18 +714,23 @@ export function helpText(topic) {
|
|
|
476
714
|
"",
|
|
477
715
|
entry.summary,
|
|
478
716
|
"",
|
|
479
|
-
"Uses this project's saved Metadata grant (see \"metergraph help login\")
|
|
480
|
-
"
|
|
481
|
-
"
|
|
482
|
-
"
|
|
483
|
-
"
|
|
717
|
+
"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",
|
|
722
|
+
"reads bounded responses. Never opens a browser, signs in, writes project files or the key, reads",
|
|
723
|
+
"retained content, replays or calls a model provider. It may refresh its own saved grant, and the",
|
|
724
|
+
"service may record the access (for example last used times); it never changes workspace",
|
|
725
|
+
"configuration or telemetry and never sends ingest data.",
|
|
484
726
|
REFUSED_NOTE,
|
|
485
727
|
"",
|
|
486
728
|
"Options:",
|
|
487
729
|
);
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
lines.push(
|
|
730
|
+
lines.push(...optionLines(entry.options, 25));
|
|
731
|
+
if (entry.examples) {
|
|
732
|
+
lines.push("", "Examples (add --json for one JSON line):");
|
|
733
|
+
for (const [question, command] of entry.examples) lines.push(` ${question}:`, ` ${command}`);
|
|
491
734
|
}
|
|
492
735
|
} else if (topic === "skill") {
|
|
493
736
|
lines.push(
|
|
@@ -501,10 +744,7 @@ export function helpText(topic) {
|
|
|
501
744
|
"",
|
|
502
745
|
"Options:",
|
|
503
746
|
);
|
|
504
|
-
|
|
505
|
-
const flag = option.value ? `${option.name} ${option.value}` : option.name;
|
|
506
|
-
lines.push(` ${flag.padEnd(18)}${option.summary}`);
|
|
507
|
-
}
|
|
747
|
+
lines.push(...optionLines(SKILL_OPTIONS, 18));
|
|
508
748
|
lines.push(
|
|
509
749
|
"",
|
|
510
750
|
"install never replaces an existing skill. update replaces only a skill this CLI",
|
|
@@ -533,10 +773,7 @@ export function helpText(topic) {
|
|
|
533
773
|
"",
|
|
534
774
|
"Options:",
|
|
535
775
|
);
|
|
536
|
-
|
|
537
|
-
const flag = option.value ? `${option.name} ${option.value}` : option.name;
|
|
538
|
-
lines.push(` ${flag.padEnd(18)}${option.summary}`);
|
|
539
|
-
}
|
|
776
|
+
lines.push(...optionLines(SKILL_OPTIONS, 18));
|
|
540
777
|
lines.push(
|
|
541
778
|
"",
|
|
542
779
|
"install never replaces an existing skill. update replaces only skills this CLI",
|
|
@@ -570,6 +807,8 @@ export function helpText(topic) {
|
|
|
570
807
|
" metergraph usage [options] Daily usage per route, Metadata only",
|
|
571
808
|
" metergraph routes [options] Routes and evaluation contract versions",
|
|
572
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",
|
|
573
812
|
"",
|
|
574
813
|
'Run "metergraph help doctor" for doctor options.',
|
|
575
814
|
'Run "metergraph help skill" for skill options.',
|
|
@@ -577,77 +816,94 @@ export function helpText(topic) {
|
|
|
577
816
|
'Run "metergraph help login" or "metergraph help logout" for sign in options.',
|
|
578
817
|
'Run "metergraph help verify" for exact-trace verification options.',
|
|
579
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,
|
|
580
822
|
);
|
|
581
823
|
}
|
|
582
824
|
lines.push("", "Exit codes:");
|
|
583
825
|
for (const [outcome, code] of Object.entries(EXIT_CODES)) {
|
|
584
|
-
lines.push(` ${String(code).padEnd(3)}${outcome.padEnd(25)}${EXIT_CODE_MEANINGS[outcome]}`);
|
|
826
|
+
lines.push(` ${String(code).padEnd(3)}${outcome.padEnd(25)}${style.dim(EXIT_CODE_MEANINGS[outcome])}`);
|
|
585
827
|
}
|
|
586
|
-
|
|
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`;
|
|
587
830
|
}
|
|
588
831
|
|
|
589
|
-
export function skillText(result, message) {
|
|
832
|
+
export function skillText(result, message, style = PLAIN) {
|
|
590
833
|
const report = result.data;
|
|
591
834
|
const label = SKILL_CLIENTS[report.client]?.label ?? HANDOFF_SKILL_CLIENTS[report.client];
|
|
592
|
-
const
|
|
593
|
-
|
|
835
|
+
const verb = result.command === "skill update" ? "updated" : "installed";
|
|
836
|
+
const lines = [
|
|
837
|
+
result.ok
|
|
838
|
+
? headline(style, "ok", `Metergraph skill ${report.status === "unchanged" ? "already up to date" : verb} for ${label}`)
|
|
839
|
+
: headline(style, toneFor(result.outcome), `Metergraph skill not ${verb} for ${label}`, result.outcome),
|
|
840
|
+
"",
|
|
841
|
+
];
|
|
842
|
+
const rows = [["Runtime", report.runtime], ["Path", report.path]];
|
|
594
843
|
if (result.ok) {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
"Authenticated
|
|
600
|
-
"",
|
|
601
|
-
message,
|
|
602
|
-
`Next: ${report.next_action.message}`,
|
|
844
|
+
rows.push(
|
|
845
|
+
["Status", report.status],
|
|
846
|
+
["Source", `${report.source.revision} ${style.dim(`(sha256 ${report.source.sha256})`)}`],
|
|
847
|
+
["Discovery", `pending until ${label} loads the skill`],
|
|
848
|
+
["Authenticated", "no"],
|
|
603
849
|
);
|
|
604
|
-
} else {
|
|
605
|
-
lines.push("", `Result: ${result.outcome} (exit ${result.exit_code})`, message);
|
|
606
|
-
if (report.next_action?.kind === "connection_guide") {
|
|
607
|
-
lines.push(`Connection guide: ${report.next_action.url}`);
|
|
608
|
-
}
|
|
609
850
|
}
|
|
610
|
-
|
|
851
|
+
lines.push(...fields(style, rows), "");
|
|
852
|
+
if (message) lines.push(...paragraph(style, message), "");
|
|
853
|
+
lines.push(...steps(style, [
|
|
854
|
+
["", result.ok ? report.next_action.message : null],
|
|
855
|
+
["Connection guide", !result.ok && report.next_action?.kind === "connection_guide" ? report.next_action.url : null],
|
|
856
|
+
]));
|
|
857
|
+
return render(lines);
|
|
611
858
|
}
|
|
612
859
|
|
|
613
|
-
export function skillsText(result, message) {
|
|
860
|
+
export function skillsText(result, message, style = PLAIN) {
|
|
614
861
|
const report = result.data;
|
|
615
862
|
const lines = [];
|
|
863
|
+
const source = report.source
|
|
864
|
+
? ["Source", `${report.source.repository} ${style.dim(`at ${report.source.commit.slice(0, 12)}`)}`]
|
|
865
|
+
: ["Source", null];
|
|
616
866
|
if (result.command === "skills list") {
|
|
617
|
-
lines.push(
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
867
|
+
lines.push(
|
|
868
|
+
result.ok
|
|
869
|
+
? headline(style, "info", "Metergraph skills bundled with this CLI")
|
|
870
|
+
: headline(style, toneFor(result.outcome), "Could not list Metergraph skills", result.outcome),
|
|
871
|
+
"",
|
|
872
|
+
...fields(style, [source]),
|
|
873
|
+
);
|
|
874
|
+
if (!result.ok) return render([...lines, "", ...paragraph(style, message)]);
|
|
623
875
|
for (const skill of report.skills) {
|
|
624
|
-
lines.push("", `${skill.name}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
}
|
|
876
|
+
lines.push("", `${section(style, skill.name)} ${style.dim(skill.revision)}`);
|
|
877
|
+
lines.push(...fields(style, Object.entries(skill.installs).map(([client, state]) =>
|
|
878
|
+
[SKILL_CLIENTS[client].label, state === "installed" ? style.green(SKILLS_STATE_LABELS[state]) : (SKILLS_STATE_LABELS[state] ?? state)])));
|
|
628
879
|
}
|
|
629
|
-
return
|
|
880
|
+
return render(lines);
|
|
630
881
|
}
|
|
631
882
|
const label = SKILL_CLIENTS[report.client]?.label ?? HANDOFF_SKILL_CLIENTS[report.client] ?? report.client;
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
883
|
+
const verb = result.command === "skills update" ? "updated" : "installed";
|
|
884
|
+
lines.push(
|
|
885
|
+
result.ok
|
|
886
|
+
? headline(style, "ok", `Metergraph skills ${verb} for ${label}`)
|
|
887
|
+
: headline(style, toneFor(result.outcome), `Metergraph skills not ${verb} for ${label}`, result.outcome),
|
|
888
|
+
"",
|
|
889
|
+
...fields(style, [["Runtime", report.runtime], source]),
|
|
890
|
+
);
|
|
891
|
+
if (report.skills.length > 0) {
|
|
892
|
+
lines.push("", ...fields(style, report.skills.map((skill) => {
|
|
893
|
+
const status = skill.status === "failed" ? style.red(`failed (${skill.reason})`) : skill.status;
|
|
894
|
+
return [skill.name, `${status} ${style.dim(skill.path ?? "")}`];
|
|
895
|
+
})));
|
|
637
896
|
}
|
|
638
897
|
if (result.ok) {
|
|
639
|
-
lines.push(
|
|
640
|
-
|
|
641
|
-
"Authenticated: no",
|
|
642
|
-
"",
|
|
643
|
-
`Next: ${report.next_action.message}`,
|
|
644
|
-
);
|
|
898
|
+
lines.push("", ...fields(style, [["Discovery", `pending until ${label} loads the skills`], ["Authenticated", "no"]]),
|
|
899
|
+
"", ...steps(style, [["", report.next_action.message]]));
|
|
645
900
|
} else {
|
|
646
|
-
lines.push("",
|
|
647
|
-
|
|
648
|
-
|
|
901
|
+
lines.push("", ...paragraph(style, message), "", ...steps(style, [
|
|
902
|
+
["", report.next_action?.kind === "plugin_marketplace" ? report.next_action.message : null],
|
|
903
|
+
["Connection guide", report.next_action?.kind === "connection_guide" ? report.next_action.url : null],
|
|
904
|
+
]));
|
|
649
905
|
}
|
|
650
|
-
return
|
|
906
|
+
return render(lines);
|
|
651
907
|
}
|
|
652
908
|
|
|
653
909
|
export function versionData() {
|
|
@@ -693,35 +949,48 @@ export function doctorMessage(outcome, reason) {
|
|
|
693
949
|
return `${detail.charAt(0).toUpperCase()}${detail.slice(1)}.`;
|
|
694
950
|
}
|
|
695
951
|
|
|
696
|
-
|
|
952
|
+
const CHECK_MARKS = { pass: ["✓", "green"], fail: ["✗", "red"], skipped: ["-", "dim"] };
|
|
953
|
+
|
|
954
|
+
export function doctorText(result, style = PLAIN) {
|
|
697
955
|
const report = result.data;
|
|
698
|
-
const
|
|
956
|
+
const title = result.outcome === "authentication_required"
|
|
957
|
+
? "Service is reachable and requires sign in"
|
|
958
|
+
: result.ok ? "Service is healthy" : "Service check failed";
|
|
959
|
+
const lines = [
|
|
960
|
+
headline(style, toneFor(result.outcome), title, result.outcome),
|
|
961
|
+
"",
|
|
962
|
+
...fields(style, [["Origin", report.origin]]),
|
|
963
|
+
` ${style.dim("Read only, no credentials sent")}`,
|
|
964
|
+
"",
|
|
965
|
+
];
|
|
966
|
+
const width = Math.max(...report.checks.map((check) => CHECK_LABELS[check.name].length));
|
|
699
967
|
for (const check of report.checks) {
|
|
700
|
-
|
|
968
|
+
const [mark, color] = CHECK_MARKS[check.result] ?? ["?", "dim"];
|
|
969
|
+
let line = ` ${style[color](mark)} ${CHECK_LABELS[check.name].padEnd(width)} ${style.dim(check.path)}`;
|
|
701
970
|
if (check.name === "deployment" && report.deployment_profile !== null) {
|
|
702
|
-
line +=
|
|
971
|
+
line += ` ${report.deployment_profile}`;
|
|
703
972
|
} else if (check.reason !== null) {
|
|
704
|
-
line +=
|
|
973
|
+
line += ` ${REASON_TEXT[check.reason] ?? check.reason}`;
|
|
705
974
|
}
|
|
706
975
|
lines.push(line);
|
|
707
976
|
}
|
|
708
|
-
lines.push("",
|
|
709
|
-
if (result.error !== null) lines.push(result.error.message);
|
|
977
|
+
if (result.error !== null) lines.push("", ...paragraph(style, result.error.message));
|
|
710
978
|
if (result.outcome === "authentication_required") {
|
|
711
|
-
lines.push(
|
|
712
|
-
"
|
|
713
|
-
CONNECTION_GUIDE_URL,
|
|
714
|
-
);
|
|
979
|
+
lines.push("", ...steps(style, [
|
|
980
|
+
["Sign in from this machine", "metergraph login --runtime local"],
|
|
981
|
+
["Connect an application", CONNECTION_GUIDE_URL],
|
|
982
|
+
]));
|
|
715
983
|
}
|
|
716
|
-
return
|
|
984
|
+
return render(lines);
|
|
717
985
|
}
|
|
718
986
|
|
|
719
987
|
// Fixed text for every login, logout and session reason. Reasons shared
|
|
720
988
|
// with doctor fall back to its wording.
|
|
721
989
|
const AUTH_MESSAGES = {
|
|
722
990
|
runtime_not_supported:
|
|
723
|
-
"
|
|
724
|
-
"
|
|
991
|
+
"--runtime says where this terminal runs, not which Metergraph service you sign in to. " +
|
|
992
|
+
"A cloud runtime is a remote sandbox, and sign in needs a browser on the same machine as the CLI, " +
|
|
993
|
+
"so it cannot finish there. Nothing was written.",
|
|
725
994
|
ssh_session:
|
|
726
995
|
"This is a remote shell session. A browser on your own machine cannot reach this machine's loopback " +
|
|
727
996
|
"address, so sign in cannot finish here. Run login where your browser runs. Nothing was written.",
|
|
@@ -823,36 +1092,73 @@ export function authMessage(outcome, reason) {
|
|
|
823
1092
|
return AUTH_MESSAGES[reason] ?? doctorMessage(outcome, reason);
|
|
824
1093
|
}
|
|
825
1094
|
|
|
826
|
-
|
|
1095
|
+
// Short headlines for the reasons people meet most. Others use a generic one.
|
|
1096
|
+
const AUTH_TITLES = {
|
|
1097
|
+
runtime_not_supported: "Sign in cannot run from a cloud runtime",
|
|
1098
|
+
ssh_session: "Sign in cannot run over SSH",
|
|
1099
|
+
cloud_workspace: "Sign in cannot run in a cloud development environment",
|
|
1100
|
+
ci_environment: "Sign in cannot run in CI",
|
|
1101
|
+
browser_approval_required: "Waiting for approval in the browser",
|
|
1102
|
+
access_denied: "Sign in was declined in the browser",
|
|
1103
|
+
timeout: "Sign in timed out",
|
|
1104
|
+
cancelled: "Sign in was cancelled",
|
|
1105
|
+
bound_to_other_origin: "This project is bound to a different origin",
|
|
1106
|
+
bound_to_other_workspace: "This project is bound to a different workspace",
|
|
1107
|
+
not_signed_in: "This project is not signed in",
|
|
1108
|
+
revocation_unconfirmed: "Signed out locally; revocation not confirmed",
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
export function authText(result, message, style = PLAIN) {
|
|
827
1112
|
const report = result.data;
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
if (
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
lines.push(
|
|
836
|
-
`Status: ${status}`,
|
|
837
|
-
`Scopes: ${report.scopes.join(" ")}`,
|
|
838
|
-
`Binding: ${report.binding}`,
|
|
839
|
-
`Grant storage: ${report.credential_protection === "dpapi" ? "Windows DPAPI, current user" : "owner-only file"}`,
|
|
840
|
-
);
|
|
841
|
-
if (report.previous_grant_revocation === "unconfirmed") {
|
|
842
|
-
lines.push("The previous grant was removed locally; the service did not confirm its revocation.");
|
|
843
|
-
}
|
|
844
|
-
}
|
|
1113
|
+
const login = result.command === "login";
|
|
1114
|
+
const reason = result.error?.reason ?? null;
|
|
1115
|
+
let title;
|
|
1116
|
+
if (result.ok && login) {
|
|
1117
|
+
title = { signed_in: "Signed in", reused: "Already signed in", reconnected: "Reconnected" }[report.status];
|
|
1118
|
+
} else if (result.ok) {
|
|
1119
|
+
title = report.binding === "none" ? "This project was not signed in" : "Signed out";
|
|
845
1120
|
} else {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
1121
|
+
title = AUTH_TITLES[reason] ?? (login ? "Sign in did not complete" : "Sign out did not complete");
|
|
1122
|
+
}
|
|
1123
|
+
const lines = [headline(style, toneFor(result.outcome), title, result.outcome), ""];
|
|
1124
|
+
const rows = [
|
|
1125
|
+
["Origin", report.origin],
|
|
1126
|
+
["Workspace", report.workspace?.id ?? null],
|
|
1127
|
+
["Profile", report.deployment_profile || null],
|
|
1128
|
+
];
|
|
1129
|
+
if (login && result.ok) {
|
|
1130
|
+
rows.push(
|
|
1131
|
+
["Scopes", report.scopes.join(" ")],
|
|
1132
|
+
["Binding", report.binding],
|
|
1133
|
+
["Grant storage", report.credential_protection === "dpapi" ? "Windows DPAPI, current user" : "owner-only file"],
|
|
1134
|
+
);
|
|
1135
|
+
} else if (!login) {
|
|
1136
|
+
rows.push(
|
|
1137
|
+
["Saved grant", report.local_credentials],
|
|
1138
|
+
["Binding", report.binding],
|
|
1139
|
+
["Server revocation", report.revocation.replace("_", " ")],
|
|
850
1140
|
);
|
|
851
|
-
if (result.ok && report.binding === "none") lines.push("This project was not signed in.");
|
|
852
1141
|
}
|
|
853
|
-
|
|
1142
|
+
lines.push(...fields(style, rows), "");
|
|
1143
|
+
if (login && result.ok && report.previous_grant_revocation === "unconfirmed") {
|
|
1144
|
+
lines.push(...paragraph(style, "The previous grant was removed locally; the service did not confirm its revocation."), "");
|
|
1145
|
+
}
|
|
1146
|
+
if (!result.ok) lines.push(...paragraph(style, message), "");
|
|
854
1147
|
const next = report.next_action;
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1148
|
+
const guide = next?.url ?? null;
|
|
1149
|
+
// A remote handoff names the Keys page where a person creates the
|
|
1150
|
+
// Metadata key the agent then reads from METERGRAPH_AGENT_TOKEN.
|
|
1151
|
+
const keys = next?.kind === "use_agent_key"
|
|
1152
|
+
? next.person_steps.find((step) => step.action === "create_metadata_key")?.url ?? null
|
|
1153
|
+
: null;
|
|
1154
|
+
if (reason === "runtime_not_supported") {
|
|
1155
|
+
lines.push(...steps(style, [
|
|
1156
|
+
["Signing in from this machine?", style.bold("metergraph login --runtime local")],
|
|
1157
|
+
["Connecting a cloud agent?", guide],
|
|
1158
|
+
["Metadata key for the agent", keys],
|
|
1159
|
+
]));
|
|
1160
|
+
} else {
|
|
1161
|
+
lines.push(...steps(style, [["", next?.message ?? null], ["Keys page", keys], ["Connection guide", guide]]));
|
|
1162
|
+
}
|
|
1163
|
+
return render(lines);
|
|
858
1164
|
}
|