metergraph-cli 0.2.0-preview.4 → 0.2.0-preview.6
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 +36 -12
- package/assets/skill/SKILL.md +20 -1
- package/assets/skill/manifest.json +3 -3
- package/assets/skills/manifest.json +67 -0
- package/assets/skills/metergraph-analyze/SKILL.md +159 -0
- package/assets/skills/metergraph-candidates/SKILL.md +84 -0
- package/assets/skills/metergraph-evals/SKILL.md +99 -0
- package/assets/skills/metergraph-investigate/SKILL.md +105 -0
- package/assets/skills/metergraph-iterate/SKILL.md +73 -0
- package/assets/skills/metergraph-local-bundle/SKILL.md +283 -0
- package/assets/skills/metergraph-model-swap/SKILL.md +81 -0
- package/assets/skills/metergraph-onboarding/SKILL.md +156 -0
- package/assets/skills/metergraph-report/SKILL.md +91 -0
- package/assets/skills/metergraph-workloads/SKILL.md +94 -0
- package/bin/metergraph.js +4 -4
- package/package.json +3 -1
- package/src/approval-handoff.js +91 -0
- package/src/args.js +50 -11
- package/src/auth-login.js +27 -13
- package/src/cli.js +70 -7
- package/src/constants.js +9 -0
- package/src/deployment-route.js +1 -1
- package/src/output.js +138 -9
- package/src/setup-deployment.js +103 -7
- package/src/setup.js +33 -16
- package/src/skill-bundle.js +68 -1
- package/src/skill.js +175 -14
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: metergraph-onboarding
|
|
3
|
+
description: Guide a coding agent from an empty local or hosted MeterGraph workspace to a verified application trace, then hand off to workload and eval selection.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Set up MeterGraph through the first usable trace
|
|
7
|
+
|
|
8
|
+
Confirm the user's deployment choice, application repository, target workspace,
|
|
9
|
+
coding client and whether they want SDK instrumentation or an existing supported
|
|
10
|
+
import. Use the actual MeterGraph origin the user selected. Do not guess an
|
|
11
|
+
origin from a credential. Record client/version, application revision, setup
|
|
12
|
+
start time and fixture source. Keep demo, synthetic, import and application
|
|
13
|
+
traffic distinct.
|
|
14
|
+
|
|
15
|
+
## Establish the deployment and workspace
|
|
16
|
+
|
|
17
|
+
For hosted MeterGraph, connect with OAuth. No operator-issued key is needed.
|
|
18
|
+
Add the hosted endpoint as an HTTP MCP server with no credential or headers,
|
|
19
|
+
for example `claude mcp add --transport http metergraph
|
|
20
|
+
https://app.metergraph.dev/v1/agent/mcp`. Then use the client's own
|
|
21
|
+
authentication step (in Claude Code, `/mcp`, choose `metergraph`, then
|
|
22
|
+
Authenticate). The human completes browser sign-in and consent in the browser,
|
|
23
|
+
in the workspace they intend to use, and confirms the workspace shown on the
|
|
24
|
+
consent page. The consent page names the requested role; a client following
|
|
25
|
+
the server's challenge requests Metadata. If the client cannot
|
|
26
|
+
complete OAuth, or the user needs retained content or replay, describe the
|
|
27
|
+
Keys-page scoped connection as a manual alternative and obtain the user's
|
|
28
|
+
choice. Never silently fall back to a key.
|
|
29
|
+
|
|
30
|
+
For the first application trace, check the installed Metergraph CLI's version
|
|
31
|
+
and help before naming commands. `metergraph-cli@0.1.0` has `doctor` and project
|
|
32
|
+
skill installation; a later installed version may offer `login`, `setup` and
|
|
33
|
+
`verify`. Use only commands present in that installation. Otherwise follow
|
|
34
|
+
https://www.metergraph.dev/docs/start/first-trace/ and its manual setup steps.
|
|
35
|
+
Do not infer that a server endpoint or source-only CLI command has shipped.
|
|
36
|
+
|
|
37
|
+
For customer-local MeterGraph, use `metergraph-local-bundle` to stand up the
|
|
38
|
+
released bundle, then continue here. Use its included README, signed manifest
|
|
39
|
+
and verification files. The human obtains the current
|
|
40
|
+
pull-only registry credential; registry access is not yet universally self-serve.
|
|
41
|
+
Use password-stdin and the local secret store for registry login. Verify the
|
|
42
|
+
archive before running its scripts. Configure the bundle's local settings,
|
|
43
|
+
start with `./bin/start`, and inspect `./bin/status` and the reported readiness.
|
|
44
|
+
Use the bundle's printed loopback URL. Record bundle version and image digests.
|
|
45
|
+
The human sets their local admin/database passwords and signs in. Reuse the
|
|
46
|
+
bundle's startup/recovery instructions; do not replace them with an ad-hoc
|
|
47
|
+
Compose stack. First boot may ingest a demo trace, which is synthetic evidence.
|
|
48
|
+
|
|
49
|
+
## Connect the coding client
|
|
50
|
+
|
|
51
|
+
For hosted OAuth, the connection is the URL-only server added above. For a
|
|
52
|
+
keyed or customer-local connection, reuse the Keys page's client-specific
|
|
53
|
+
instructions, documented in
|
|
54
|
+
https://www.metergraph.dev/docs/guides/agent-access/. The endpoint is
|
|
55
|
+
`<origin>/v1/agent/mcp`; the stdio bridge is `metergraph-mcp` with
|
|
56
|
+
`METERGRAPH_URL` and `METERGRAPH_AGENT_TOKEN` in its environment. Verify
|
|
57
|
+
`tools/list`, then `metergraph_get_workspace_context` and
|
|
58
|
+
`metergraph_get_capabilities`. Record the selected origin separately: `managed` identifies the hosted profile
|
|
59
|
+
and does not distinguish staging from production. Match the returned workspace ID and deployment
|
|
60
|
+
to the user's selection before reading traces. Capabilities and required scopes
|
|
61
|
+
from this connection are authoritative. Do not infer write authority from
|
|
62
|
+
`agent:read` or provider authority from a healthy MCP connection.
|
|
63
|
+
|
|
64
|
+
Keep these credentials separate:
|
|
65
|
+
|
|
66
|
+
| Credential | Destination and purpose | Human step |
|
|
67
|
+
| --- | --- | --- |
|
|
68
|
+
| Registry pull credential | Image registry only | Obtain/refresh through the supported distribution channel |
|
|
69
|
+
| Ingest credential | Application SDK or supported import connector | Create an ingest key for the selected workspace |
|
|
70
|
+
| Agent Access credential | Coding client's supported OAuth/secret configuration | Sign in/consent or create a scoped connection |
|
|
71
|
+
| Model-provider credential | Deployment's supported runner/provider configuration | Set the provider key and approve paid execution |
|
|
72
|
+
|
|
73
|
+
Never ask for secrets in chat, commit them, put them in a shared MCP file or
|
|
74
|
+
save them in verification evidence. A read connection is sufficient for this
|
|
75
|
+
onboarding verification. Replay, eval previews and analyses make separate
|
|
76
|
+
authorization and cost decisions.
|
|
77
|
+
|
|
78
|
+
## Instrument or import application traffic
|
|
79
|
+
|
|
80
|
+
Reuse the appropriate language/provider instructions and the repository prompt
|
|
81
|
+
at https://www.metergraph.dev/docs/agents/instrument-a-repo/#verify-one-application-call, or a supported
|
|
82
|
+
connector under https://www.metergraph.dev/docs/import/overview/. Inspect the real
|
|
83
|
+
application entry points first. Exclude tests, fixtures, mock libraries, demos
|
|
84
|
+
and validation jobs from production traffic claims. Keep provider base URLs,
|
|
85
|
+
gateway credentials and application behavior intact. Review instrumentation
|
|
86
|
+
changes with the user, including calls that could not be captured.
|
|
87
|
+
|
|
88
|
+
Use the selected deployment's ingestion destination and the ingest credential,
|
|
89
|
+
not its MCP URL or Agent Access key. Use that deployment's SDK/connector setup
|
|
90
|
+
instructions to choose the complete ingest URL. Set capture/retention as the
|
|
91
|
+
workspace owner intends; metadata-only telemetry does not prove retained
|
|
92
|
+
inputs/outputs needed for eval replay. Do not silently enable content capture.
|
|
93
|
+
Avoid treating received HTTP acceptance as worker processing completion.
|
|
94
|
+
|
|
95
|
+
With user authorization, trigger one normal application request/job and record
|
|
96
|
+
its application-owned trace ID and time. For an import, record the supported
|
|
97
|
+
connector, source trace identity and imported time window. Do not create an LLM
|
|
98
|
+
connection, paid eval rule or provider invocation just to manufacture proof.
|
|
99
|
+
|
|
100
|
+
## Verify and hand off
|
|
101
|
+
|
|
102
|
+
1. Read bounded `metergraph_get_ingestion_health` if available. Preserve pending,
|
|
103
|
+
processed and failed states and receive/process timestamps. Counts apply to
|
|
104
|
+
a workspace window; they do not identify the batch that contains one trace.
|
|
105
|
+
2. Read bounded `metergraph_query_traces` in the same workspace/time window and
|
|
106
|
+
locate the exact application/import trace ID. Continue bounded pagination
|
|
107
|
+
when advertised. An empty page is a setup state, not proof of no ingestion.
|
|
108
|
+
3. If the user permits a content-class read and `trace_content` is available,
|
|
109
|
+
inspect one `metergraph_get_trace` for retained usable input/output and capture
|
|
110
|
+
warnings. Record availability and omissions, never the content in setup logs.
|
|
111
|
+
4. When advertised, use classified-workload and model-readiness tools to explain
|
|
112
|
+
available cohorts, capture eligibility, supported checks and missing provider
|
|
113
|
+
credentials. A configured credential is not proof of a successful provider
|
|
114
|
+
call. Missing capability is an explicit handoff blocker.
|
|
115
|
+
5. Return workspace/profile, bundle/app/client revisions, source label, exact
|
|
116
|
+
trace ID/time window, ingestion and capture evidence, warnings, human steps
|
|
117
|
+
still needed and the next eval selection action. A synthetic demonstration
|
|
118
|
+
does not meet real application or hosted pilot acceptance.
|
|
119
|
+
|
|
120
|
+
Verify the named trace through the connected client's advertised MCP tools,
|
|
121
|
+
with an explicit source label and the same workspace/deployment before and
|
|
122
|
+
after any permitted content read. The older trace-debug contract has no
|
|
123
|
+
workspace envelope, so confirm its tenant-bound connection separately. Trace
|
|
124
|
+
discovery is not analysis readiness. Keep retained content out of setup reports.
|
|
125
|
+
|
|
126
|
+
## Recovery
|
|
127
|
+
|
|
128
|
+
| State | Next action |
|
|
129
|
+
| --- | --- |
|
|
130
|
+
| Signup, sign-in or OAuth consent required | Hand the browser step to the human, then reconnect and verify workspace identity |
|
|
131
|
+
| Registry access expired/denied | Refresh the pull credential through the supported distribution channel, repeat registry login, retain local data |
|
|
132
|
+
| Local startup or bootstrap failure | Inspect `bin/status` and the bundle's named logs/readiness checks; follow its recovery instructions |
|
|
133
|
+
| Runner unavailable | Use the bundle's runner health instructions; do not claim MCP health proves analysis readiness |
|
|
134
|
+
| Empty data | Check application environment, correct ingest destination/key, flush/import window and worker status; exclude demos/mocks |
|
|
135
|
+
| Received but pending/failed | Inspect ingestion failure classes and the existing retry/recovery flow before changing instrumentation |
|
|
136
|
+
| Missing/retained content unavailable | Explain capture policy, retention and omission; collect a new permitted application trace |
|
|
137
|
+
| Agent credential revoked or insufficient scope | Reconnect/re-authorize through the workspace owner's supported path; do not use an ingest key |
|
|
138
|
+
| Provider key absent or candidate unsupported | Complete the runner/provider setup or choose an available candidate; defer paid execution |
|
|
139
|
+
| API capability unavailable | State the unsupported deployment boundary and the specific supported next step |
|
|
140
|
+
|
|
141
|
+
Do not run destructive `down --volumes` as recovery. The bundle warns that raw
|
|
142
|
+
payload persistence differs from database persistence; prefer its stop/start
|
|
143
|
+
procedure and preserve existing data. Refine and rerun evals only through the
|
|
144
|
+
separately authorized workflow. Packaging this skill belongs to the distribution
|
|
145
|
+
workflow; this source does not assert that plugin OAuth/configuration or hosted
|
|
146
|
+
onboarding has already shipped.
|
|
147
|
+
|
|
148
|
+
Use the public skill's current client/runtime/deployment table at
|
|
149
|
+
https://www.metergraph.dev/SKILL.md. An unimplemented cloud/OAuth client path
|
|
150
|
+
is blocked even if server code exists. Query an exact SDK trace ID, or resolve a
|
|
151
|
+
provider request ID only when that argument is advertised. Ambiguous request IDs
|
|
152
|
+
need confirmation from the application; never select the first match. Preserve
|
|
153
|
+
checkpoint source run, pattern and pattern-set identities in workload queries and
|
|
154
|
+
cursor continuation. Missing/fallback IDs and expired records leave incomplete
|
|
155
|
+
evidence. Read classification availability separately from failed analysis and
|
|
156
|
+
report availability. Discovery must not launch or spend on classification or analysis.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: metergraph-report
|
|
3
|
+
description: Summarize a finished Metergraph analysis in a few lines with the link to its report in the app, say whether the evidence supports a switch, no switch or more testing, and tell the person exactly what to inspect in the app. Use after an analysis completes, when someone asks "what did the analysis find", "should we switch", "give me the report link", or stage 5 of the metergraph-model-swap loop.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Summarize the report and hand off to the app
|
|
7
|
+
|
|
8
|
+
The coding agent gives the answer and the link. The app is where the person
|
|
9
|
+
reads the full report and inspects case-level evidence.
|
|
10
|
+
|
|
11
|
+
## 0. Confirm the connection
|
|
12
|
+
|
|
13
|
+
If this conversation has not already confirmed the workspace, call
|
|
14
|
+
`metergraph_get_workspace_context`, then `metergraph_get_capabilities`, before
|
|
15
|
+
anything else. Say which workspace you are connected to in one line, and plan
|
|
16
|
+
only with the tools the capabilities advertise.
|
|
17
|
+
|
|
18
|
+
## 1. Read it
|
|
19
|
+
|
|
20
|
+
1. `metergraph_get_analysis_run` with the run ID. Continue only when the status
|
|
21
|
+
is final. If `report.available` is false on a completed run, the report is
|
|
22
|
+
missing: say so and give the run's page (below).
|
|
23
|
+
2. `metergraph_get_report` with `report.analysis_id`. Read the `outcome`, each
|
|
24
|
+
workload's `state`, `sample_count` and `reason`, each `model_swap`
|
|
25
|
+
opportunity (`recommendation_status`, `quality_vs_reference`,
|
|
26
|
+
`safe_switch_rate`, `cost_ratio`, `quality_grade_counts`,
|
|
27
|
+
`evaluation_gate`, `savings_usd`), `spend_projection`,
|
|
28
|
+
`analysis_generation_cost` and every warning.
|
|
29
|
+
3. Read evidence with `metergraph_get_report_evidence` only if the person asks
|
|
30
|
+
about specific cases and the key has the Debug role. It returns captured
|
|
31
|
+
content.
|
|
32
|
+
|
|
33
|
+
## 2. Write the summary
|
|
34
|
+
|
|
35
|
+
At most eight lines, in this order:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
<Switch | Don't switch | Test more>: <workload> from <current model> to <candidate>.
|
|
39
|
+
Quality: <quality_vs_reference>, <safe_switch_rate> of cases safe to switch, graded on <n> samples against <eval revision or "default graders">.
|
|
40
|
+
Cost: <cost_ratio>× per call; about $<savings_usd>/month projected from <observed_days> days of traffic.
|
|
41
|
+
Runner-up: <candidate> — <one reason it lost>.
|
|
42
|
+
Caveats: <sample size, short window, partial coverage, failed candidates, no eval bound>.
|
|
43
|
+
Report: <link>
|
|
44
|
+
Inspect: <the one thing to look at in the app>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Rules:
|
|
48
|
+
|
|
49
|
+
- Every number comes from the report. Say "projected" for `spend_projection`
|
|
50
|
+
figures; a projection from a few days of traffic is not observed savings.
|
|
51
|
+
- Keep quality failures, evaluator or execution failures and missing coverage
|
|
52
|
+
apart. A candidate with missing results has not passed.
|
|
53
|
+
- If no eval was bound, say the grades came from default graders, not from the
|
|
54
|
+
person's own quality bar, and point to `metergraph-evals`.
|
|
55
|
+
- `no_opportunities` is a real answer: the candidates did not beat the current
|
|
56
|
+
model within the bar. `no_eligible_workloads` with `insufficient_samples`
|
|
57
|
+
needs more traffic, not a rerun.
|
|
58
|
+
- If the person's code shows where this workload's model is set, name the file
|
|
59
|
+
and line and suggest a canary experiment. Do not change code without a
|
|
60
|
+
separate instruction.
|
|
61
|
+
|
|
62
|
+
## 3. Links
|
|
63
|
+
|
|
64
|
+
Use a link a tool returned (`metergraph_links`, `links`) exactly as given.
|
|
65
|
+
Otherwise build it from the origin of the Metergraph server the agent is
|
|
66
|
+
connected to (hosted is `https://app.metergraph.dev`; the customer-local bundle
|
|
67
|
+
is normally `http://127.0.0.1:8080`) and these app routes:
|
|
68
|
+
|
|
69
|
+
| Page | Route |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| Report | `<origin>/#analysis?report=<analysis_id>` |
|
|
72
|
+
| One workload's results in a report | `<origin>/#analysis/<analysis_id>/workloads/<workload_id>/model_evaluation` |
|
|
73
|
+
| Workload evidence | `<origin>/#workloads?pattern=<workload_id>&report=<analysis_id>` |
|
|
74
|
+
| Eval definition used | `<origin>/#analysis/<analysis_id>/workloads/<workload_id>/eval_definition` |
|
|
75
|
+
| All analyses and runs | `<origin>/#opportunities` |
|
|
76
|
+
|
|
77
|
+
Say that a built link was built, not returned, in a few words. If the person
|
|
78
|
+
says it does not open, give the Opportunities page instead.
|
|
79
|
+
|
|
80
|
+
## 4. What to inspect in the app
|
|
81
|
+
|
|
82
|
+
Name one or two concrete things, from what the report shows:
|
|
83
|
+
|
|
84
|
+
- cases where the candidate was graded worse than the current model, to judge
|
|
85
|
+
whether the grader or the candidate is wrong;
|
|
86
|
+
- a check with a surprising pass rate, which may be a bad check;
|
|
87
|
+
- the cost of the cases that drive most of the spend.
|
|
88
|
+
|
|
89
|
+
Then ask the person to come back with what they found: grades they disagree
|
|
90
|
+
with, cases that should be added, or a candidate to drop. That is the input to
|
|
91
|
+
`metergraph-iterate`.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: metergraph-workloads
|
|
3
|
+
description: Choose which Metergraph workload to analyze - list classified workloads, rank them by spend with the source of each number stated, flag anomalies and suspect data, check readiness, and hand off when classification has not run. Use for "what is my largest workload", "list my workloads", "which workload should we optimize", or stage 1 of the metergraph-model-swap loop.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Choose a workload
|
|
7
|
+
|
|
8
|
+
The goal is one classified workload, its exact `selection_reference`, and a
|
|
9
|
+
one-sentence reason it was chosen.
|
|
10
|
+
|
|
11
|
+
## 0. Confirm the connection
|
|
12
|
+
|
|
13
|
+
If this conversation has not already confirmed the workspace, call
|
|
14
|
+
`metergraph_get_workspace_context`, then `metergraph_get_capabilities`, before
|
|
15
|
+
anything else. Say which workspace you are connected to in one line, and plan
|
|
16
|
+
only with the tools the capabilities advertise.
|
|
17
|
+
|
|
18
|
+
## 1. Read what exists
|
|
19
|
+
|
|
20
|
+
1. `metergraph_list_classified_workloads` with a small limit (10). Each item
|
|
21
|
+
has a `display_name`, `classified_sample_count` and a `selection_reference`
|
|
22
|
+
of `source_run_id`, `pattern_id` and `pattern_set_version`. Keep the
|
|
23
|
+
reference exactly as returned. The name is a label.
|
|
24
|
+
2. `metergraph_list_routes` and `metergraph_get_usage` with `days: 30`. These
|
|
25
|
+
give cost, calls, tokens, latency and errors per route per UTC day, plus
|
|
26
|
+
each route's `constraints` and `replay_eligible_calls`.
|
|
27
|
+
3. `metergraph_list_reports` with a small limit. If a recent report exists,
|
|
28
|
+
`metergraph_get_report` gives each workload's `spend_projection`.
|
|
29
|
+
|
|
30
|
+
## 2. Rank by spend, and say where each number came from
|
|
31
|
+
|
|
32
|
+
There are two kinds of spend number. Do not mix them in one column.
|
|
33
|
+
|
|
34
|
+
| Number | Source | Meaning |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| Observed cost | `metergraph_get_usage` | What was ingested in the window. Per route, not per workload. |
|
|
37
|
+
| Projected spend | a report's `spend_projection` (`daily_rate_usd`, `projected_monthly_cost_usd`, `population`) | An extrapolation the analysis made. Per workload. |
|
|
38
|
+
|
|
39
|
+
The app's pages can show projected numbers. If the person quotes figures from
|
|
40
|
+
the app that do not match what you read, say which source each one is before
|
|
41
|
+
ranking, and ask which one they mean. Never rank on classified sample counts:
|
|
42
|
+
they are the sample, not the traffic.
|
|
43
|
+
|
|
44
|
+
Rank on a typical day, not the total. Compute each route's median daily cost
|
|
45
|
+
over the window. If one day or one hour dominates a route's total, call it out
|
|
46
|
+
as a spike and investigate it with `metergraph_list_incidents` and
|
|
47
|
+
`metergraph_query_traces` (status and error counts) before treating that route
|
|
48
|
+
as large. A runaway loop or retry storm is a bug to fix, not a model to swap.
|
|
49
|
+
|
|
50
|
+
Present at most the top five, one line each: workload, current model(s), typical
|
|
51
|
+
daily cost, share of typical spend, and one reason it is or is not a good
|
|
52
|
+
candidate. Opus-class models on extraction, classification or short-output
|
|
53
|
+
work are the usual best candidates. Workloads already on small models, or
|
|
54
|
+
under about 5% of spend, rarely repay an analysis.
|
|
55
|
+
|
|
56
|
+
## 3. Check the data is real
|
|
57
|
+
|
|
58
|
+
Say so before planning when any of these hold, with the numbers:
|
|
59
|
+
|
|
60
|
+
- the workspace or all its data is only hours old, or arrived in one ingest
|
|
61
|
+
batch (`metergraph_get_ingestion_health`);
|
|
62
|
+
- incidents or usage disagree with each other for the same day;
|
|
63
|
+
- traces are labeled synthetic or demo, or a route mixes staging and
|
|
64
|
+
production. Ask which environment the analysis should use.
|
|
65
|
+
|
|
66
|
+
An analysis of seeded data proves the pipeline works and says nothing about
|
|
67
|
+
real costs.
|
|
68
|
+
|
|
69
|
+
## 4. Check readiness of the chosen workload
|
|
70
|
+
|
|
71
|
+
`metergraph_get_workload_readiness` with the selection reference and the
|
|
72
|
+
chosen environment. Report `blocking_reasons`, the counts and the
|
|
73
|
+
`retained_window` in one or two lines. An analysis needs at least **10
|
|
74
|
+
samples**. Below that it completes with `no_eligible_workloads`; the fix is
|
|
75
|
+
more traffic, not a rerun. `stale_checkpoint` means the classification is
|
|
76
|
+
older than the current window.
|
|
77
|
+
|
|
78
|
+
## 5. When nothing is classified yet
|
|
79
|
+
|
|
80
|
+
An empty list with `classification_pending` or `no_traces` means classification
|
|
81
|
+
has not run. No agent tool starts classification on its own. Tell the person:
|
|
82
|
+
|
|
83
|
+
1. Open the **Opportunities** page in the Metergraph app and choose **Find
|
|
84
|
+
opportunities**.
|
|
85
|
+
2. Choose to pick a specific workload rather than the automatic option. That
|
|
86
|
+
run classifies the traffic and then waits for them to choose a workload,
|
|
87
|
+
before any candidate model is called.
|
|
88
|
+
3. The automatic option classifies and then immediately analyzes the largest
|
|
89
|
+
workloads with the saved candidate models, which sends captured prompts to
|
|
90
|
+
those providers and spends money. Point this out before they click.
|
|
91
|
+
|
|
92
|
+
When they are back, list classified workloads again and continue. Do not click
|
|
93
|
+
through the app yourself unless the person asks you to and has approved what
|
|
94
|
+
the button will do.
|
package/bin/metergraph.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { handOff, handsOff } from "../src/approval-handoff.js";
|
|
2
3
|
import { main } from "../src/cli.js";
|
|
3
4
|
|
|
4
5
|
// A closed pipe on stdout (for example "metergraph --help | head -1") is not
|
|
5
6
|
// a CLI failure.
|
|
6
7
|
process.stdout.on("error", () => {});
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
});
|
|
9
|
+
const argv = process.argv.slice(2);
|
|
10
|
+
const io = { stdout: process.stdout, stderr: process.stderr };
|
|
11
|
+
process.exitCode = handsOff(argv) ? await handOff(argv, io) : await main(argv, io);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metergraph-cli",
|
|
3
|
-
"version": "0.2.0-preview.
|
|
3
|
+
"version": "0.2.0-preview.6",
|
|
4
4
|
"description": "Preview Metergraph command line tool with a read-only connection probe, a project skill installer, Metadata-only sign in and bounded workspace Metadata reads",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
"src/*.js",
|
|
15
15
|
"assets/skill/SKILL.md",
|
|
16
16
|
"assets/skill/manifest.json",
|
|
17
|
+
"assets/skills/manifest.json",
|
|
18
|
+
"assets/skills/*/SKILL.md",
|
|
17
19
|
"LICENSE",
|
|
18
20
|
"README.md"
|
|
19
21
|
],
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import childProcess from "node:child_process";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
4
|
+
import { parseArgs } from "./args.js";
|
|
5
|
+
import { envelope, toJsonLine } from "./output.js";
|
|
6
|
+
|
|
7
|
+
// login and setup with --json --no-browser may need a person to approve in a
|
|
8
|
+
// browser. The approval returns to a loopback listener, so something must
|
|
9
|
+
// keep listening after the agent has the URL. Agent shells usually show a
|
|
10
|
+
// command's output only after it exits, so that cannot be this process.
|
|
11
|
+
//
|
|
12
|
+
// This process runs the same command as a detached waiter and relays the
|
|
13
|
+
// waiter's one JSON line. When the line is action_required, it exits with it
|
|
14
|
+
// at once and leaves the waiter listening until approval or --timeout-ms; the
|
|
15
|
+
// waiter saves what the approval completes and prints nothing more. Any other
|
|
16
|
+
// line is the command's result, and this process waits for the waiter to
|
|
17
|
+
// finish. The arguments carry no credential, and the waiter's environment is
|
|
18
|
+
// this one plus a marker.
|
|
19
|
+
const BIN = fileURLToPath(new URL("../bin/metergraph.js", import.meta.url));
|
|
20
|
+
const WAITER = "METERGRAPH_APPROVAL_WAITER";
|
|
21
|
+
|
|
22
|
+
export function handsOff(argv, env = process.env) {
|
|
23
|
+
if (env[WAITER] === "1") return false;
|
|
24
|
+
const parsed = parseArgs(argv);
|
|
25
|
+
return parsed.ok && (parsed.command === "login" || parsed.command === "setup") && parsed.json && parsed.noBrowser;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function handOff(argv, { stdout }) {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
let child;
|
|
31
|
+
try {
|
|
32
|
+
child = childProcess.spawn(process.execPath, [...process.execArgv, BIN, ...argv], {
|
|
33
|
+
env: { ...process.env, [WAITER]: "1" },
|
|
34
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
35
|
+
detached: true,
|
|
36
|
+
windowsHide: true,
|
|
37
|
+
});
|
|
38
|
+
} catch {
|
|
39
|
+
resolve(failed(argv, stdout));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
let text = "";
|
|
43
|
+
let settled = false;
|
|
44
|
+
// Ctrl+C before the line arrives cancels the waiter, which reports it.
|
|
45
|
+
const forward = (signal) => child.kill(signal);
|
|
46
|
+
const signals = ["SIGINT", "SIGTERM"];
|
|
47
|
+
for (const signal of signals) process.on(signal, forward);
|
|
48
|
+
const finish = (code) => {
|
|
49
|
+
if (settled) return;
|
|
50
|
+
settled = true;
|
|
51
|
+
for (const signal of signals) process.removeListener(signal, forward);
|
|
52
|
+
resolve(code);
|
|
53
|
+
};
|
|
54
|
+
child.once("error", () => finish(failed(argv, stdout)));
|
|
55
|
+
child.stdout.setEncoding("utf8");
|
|
56
|
+
child.stdout.on("data", (chunk) => {
|
|
57
|
+
if (settled) return;
|
|
58
|
+
text += chunk;
|
|
59
|
+
const end = text.indexOf("\n");
|
|
60
|
+
if (end === -1) return;
|
|
61
|
+
let line;
|
|
62
|
+
try { line = JSON.parse(text.slice(0, end)); } catch { line = null; }
|
|
63
|
+
if (line?.outcome !== "action_required") return;
|
|
64
|
+
stdout.write(text.slice(0, end + 1));
|
|
65
|
+
child.stdout.destroy();
|
|
66
|
+
child.unref();
|
|
67
|
+
finish(line.exit_code);
|
|
68
|
+
});
|
|
69
|
+
child.once("close", (code) => {
|
|
70
|
+
if (settled) return;
|
|
71
|
+
if (text.endsWith("\n") && text.indexOf("\n") === text.length - 1) {
|
|
72
|
+
stdout.write(text);
|
|
73
|
+
finish(code ?? 1);
|
|
74
|
+
} else {
|
|
75
|
+
finish(failed(argv, stdout));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function failed(argv, stdout) {
|
|
82
|
+
const parsed = parseArgs(argv);
|
|
83
|
+
const result = envelope({
|
|
84
|
+
command: parsed.command ?? null,
|
|
85
|
+
outcome: "internal_error",
|
|
86
|
+
reason: "unexpected_failure",
|
|
87
|
+
message: "The CLI failed unexpectedly. No details are shown to avoid exposing sensitive input.",
|
|
88
|
+
});
|
|
89
|
+
stdout.write(toJsonLine(result));
|
|
90
|
+
return result.exit_code;
|
|
91
|
+
}
|
package/src/args.js
CHANGED
|
@@ -26,13 +26,15 @@ import { parseOrigin } from "./origin.js";
|
|
|
26
26
|
import { isCursor, isSafeFilter } from "./read-contract.js";
|
|
27
27
|
|
|
28
28
|
export const READ_COMMANDS = Object.freeze(["status", "context", "capabilities", "usage", "routes", "traces"]);
|
|
29
|
-
const COMMANDS = new Set(["doctor", "help", "skill", "login", "logout", "setup", "verify", ...READ_COMMANDS]);
|
|
30
|
-
const HELP_TOPICS = new Set(["doctor", "skill", "login", "logout", "setup", "verify", ...READ_COMMANDS]);
|
|
29
|
+
const COMMANDS = new Set(["doctor", "help", "skill", "skills", "login", "logout", "setup", "verify", ...READ_COMMANDS]);
|
|
30
|
+
const HELP_TOPICS = new Set(["doctor", "skill", "skills", "login", "logout", "setup", "verify", ...READ_COMMANDS]);
|
|
31
31
|
const SKILL_ACTIONS = new Set(["install", "update"]);
|
|
32
|
+
const SKILLS_ACTIONS = new Set(["install", "update", "list"]);
|
|
32
33
|
const READ_BASE = ["--project", "--config-dir", "--timeout-ms"];
|
|
33
34
|
const OPTIONS = {
|
|
34
35
|
doctor: new Set(["--url", "--timeout-ms"]),
|
|
35
36
|
skill: new Set(["--client", "--runtime", "--project"]),
|
|
37
|
+
skills: new Set(["--client", "--runtime", "--project"]),
|
|
36
38
|
login: new Set(["--runtime", "--url", "--workspace", "--project", "--config-dir", "--timeout-ms"]),
|
|
37
39
|
setup: new Set(["--runtime", "--url", "--workspace", "--project", "--config-dir", "--env-file", "--client", "--timeout-ms", "--deployment", "--agent-token-file", "--repository"]),
|
|
38
40
|
logout: new Set(["--project", "--config-dir"]),
|
|
@@ -86,6 +88,8 @@ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
|
86
88
|
// { ok: true, command: "version", json }
|
|
87
89
|
// { ok: true, command: "doctor", origin, timeoutMs, json }
|
|
88
90
|
// { ok: true, command: "skill", action, client, runtime, project, json }
|
|
91
|
+
// { ok: true, command: "skills", action, client, runtime, project, json }
|
|
92
|
+
// (client and runtime are null for list)
|
|
89
93
|
// { ok: true, command: "login", runtime, origin, workspace, project,
|
|
90
94
|
// configDir, timeoutMs, signup, noBrowser, reconnect, json }
|
|
91
95
|
// { ok: true, command: "logout", project, configDir, json }
|
|
@@ -113,7 +117,9 @@ export function parseArgs(argv) {
|
|
|
113
117
|
|
|
114
118
|
const fail = (code, message, outcome = "invalid_input") => ({
|
|
115
119
|
ok: false,
|
|
116
|
-
command: command === "skill"
|
|
120
|
+
command: (command === "skill" || command === "skills") && action !== null
|
|
121
|
+
? `${command} ${action}`
|
|
122
|
+
: command ?? (version ? "version" : null),
|
|
117
123
|
json,
|
|
118
124
|
code,
|
|
119
125
|
message,
|
|
@@ -142,6 +148,10 @@ export function parseArgs(argv) {
|
|
|
142
148
|
action = arg;
|
|
143
149
|
continue;
|
|
144
150
|
}
|
|
151
|
+
if (command === "skills" && action === null && SKILLS_ACTIONS.has(arg)) {
|
|
152
|
+
action = arg;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
145
155
|
|
|
146
156
|
if (READ_COMMANDS.includes(command)) {
|
|
147
157
|
const equals = arg.indexOf("=");
|
|
@@ -192,6 +202,12 @@ export function parseArgs(argv) {
|
|
|
192
202
|
`Unknown skill subcommand at argument ${position}. Use install or update.`,
|
|
193
203
|
);
|
|
194
204
|
}
|
|
205
|
+
if (command === "skills" && action === null && !arg.startsWith("-")) {
|
|
206
|
+
return fail(
|
|
207
|
+
"unknown_subcommand",
|
|
208
|
+
`Unknown skills subcommand at argument ${position}. Use install, update or list.`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
195
211
|
return fail(
|
|
196
212
|
"unknown_argument",
|
|
197
213
|
`Unrecognized argument at position ${position}. Run "metergraph --help" for usage.`,
|
|
@@ -203,6 +219,7 @@ export function parseArgs(argv) {
|
|
|
203
219
|
return { ok: true, command: "help", topic, json };
|
|
204
220
|
}
|
|
205
221
|
if (command === "skill") return parseSkill(action, values, json, fail);
|
|
222
|
+
if (command === "skills") return parseSkills(action, values, json, fail);
|
|
206
223
|
if (command === "login") return parseLogin(values, flags, json, fail);
|
|
207
224
|
if (command === "setup") return parseSetup(values, flags, json, fail);
|
|
208
225
|
if (command === "verify") return parseVerify(values, flags, json, fail);
|
|
@@ -262,9 +279,10 @@ function parseVerify(values, flags, json, fail) {
|
|
|
262
279
|
open: flags.has("--open"), noBrowser: flags.has("--no-browser"), json };
|
|
263
280
|
}
|
|
264
281
|
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
282
|
+
// The client is required, so a script states which client loads the skill.
|
|
283
|
+
// The runtime defaults to local, the runtime setup installs the skill for;
|
|
284
|
+
// pass --runtime cloud for a cloud checkout. Values for clients and runtimes
|
|
285
|
+
// that cannot use project skill files are accepted here and handed off later.
|
|
268
286
|
function parseSkill(action, values, json, fail) {
|
|
269
287
|
if (action === null) {
|
|
270
288
|
return fail(
|
|
@@ -279,10 +297,7 @@ function parseSkill(action, values, json, fail) {
|
|
|
279
297
|
if (!Object.hasOwn(SKILL_CLIENTS, client) && !Object.hasOwn(HANDOFF_SKILL_CLIENTS, client)) {
|
|
280
298
|
return fail("invalid_client", "--client must be codex, claude or cursor.");
|
|
281
299
|
}
|
|
282
|
-
const runtime = values["--runtime"];
|
|
283
|
-
if (runtime === undefined) {
|
|
284
|
-
return fail("missing_runtime", "--runtime is required. Use local or cloud.");
|
|
285
|
-
}
|
|
300
|
+
const runtime = values["--runtime"] ?? "local";
|
|
286
301
|
if (!SKILL_RUNTIMES.includes(runtime) && !HANDOFF_SKILL_RUNTIMES.includes(runtime)) {
|
|
287
302
|
return fail("invalid_runtime", "--runtime must be local or cloud.");
|
|
288
303
|
}
|
|
@@ -293,6 +308,29 @@ function parseSkill(action, values, json, fail) {
|
|
|
293
308
|
return { ok: true, command: "skill", action, client, runtime, project: project ?? null, json };
|
|
294
309
|
}
|
|
295
310
|
|
|
311
|
+
// skills install and update take the same options as skill. list is read
|
|
312
|
+
// only and takes just --project.
|
|
313
|
+
function parseSkills(action, values, json, fail) {
|
|
314
|
+
if (action === null) {
|
|
315
|
+
return fail(
|
|
316
|
+
"missing_subcommand",
|
|
317
|
+
'skills requires a subcommand: install, update or list. Run "metergraph help skills" for usage.',
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
if (action === "list") {
|
|
321
|
+
if (values["--client"] !== undefined || values["--runtime"] !== undefined) {
|
|
322
|
+
return fail("unexpected_option", "skills list takes only --project. It reports every client.");
|
|
323
|
+
}
|
|
324
|
+
const project = values["--project"];
|
|
325
|
+
if (project !== undefined && (project === "" || project.includes("\0"))) {
|
|
326
|
+
return fail("invalid_project", "--project must name an existing directory.");
|
|
327
|
+
}
|
|
328
|
+
return { ok: true, command: "skills", action, client: null, runtime: null, project: project ?? null, json };
|
|
329
|
+
}
|
|
330
|
+
const parsed = parseSkill(action, values, json, fail);
|
|
331
|
+
return parsed.ok ? { ...parsed, command: "skills" } : parsed;
|
|
332
|
+
}
|
|
333
|
+
|
|
296
334
|
const INVALID_URL =
|
|
297
335
|
"--url must be a bare https origin such as https://metergraph.example.com, " +
|
|
298
336
|
"or an http origin on localhost, 127.0.0.1 or [::1]. " +
|
|
@@ -402,7 +440,8 @@ function parseSetup(values, flags, json, fail) {
|
|
|
402
440
|
parseTimeout(rawTimeout, LOGIN_MIN_TIMEOUT_MS, LOGIN_MAX_TIMEOUT_MS);
|
|
403
441
|
if (timeoutMs === null) return fail("invalid_timeout", `--timeout-ms must be a whole number from ${LOGIN_MIN_TIMEOUT_MS} to ${LOGIN_MAX_TIMEOUT_MS}.`);
|
|
404
442
|
return { ok: true, command: "setup", runtime, origin, originExplicit: values["--url"] !== undefined,
|
|
405
|
-
deployment,
|
|
443
|
+
deployment, deploymentExplicit: values["--deployment"] !== undefined,
|
|
444
|
+
confirmPrerequisites: flags.has("--confirm-prerequisites"), agentTokenFile,
|
|
406
445
|
workspace: rawWorkspace === undefined ? null : rawWorkspace.toLowerCase(),
|
|
407
446
|
project: paths.project, configDir: paths.configDir, envFile, client, skipSkill: flags.has("--skip-skill"),
|
|
408
447
|
timeoutMs, noBrowser: flags.has("--no-browser"), repair: flags.has("--repair"),
|