metergraph-cli 0.2.0-preview.0 → 0.2.0-preview.1
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 +49 -48
- package/package.json +1 -1
- package/src/read-contract.js +11 -3
- package/src/setup-env-parse.js +7 -7
- package/src/setup.js +11 -2
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# metergraph-cli
|
|
2
2
|
|
|
3
|
-
The Metergraph command line tool.
|
|
4
|
-
`
|
|
3
|
+
The Metergraph command line tool. Version `0.2.0-preview.1` is published on the
|
|
4
|
+
`next` npm tag. The `latest` tag remains on `0.1.0`.
|
|
5
5
|
|
|
6
6
|
Install the released preview channel with npm or run it directly:
|
|
7
7
|
|
|
@@ -11,42 +11,41 @@ npx --yes metergraph-cli@next doctor --json
|
|
|
11
11
|
npm install -g metergraph-cli@next
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
The installed command is `metergraph`. Pin `metergraph-cli@0.
|
|
14
|
+
The installed command is `metergraph`. Pin `metergraph-cli@0.2.0-preview.1`
|
|
15
|
+
when you need this exact preview rather than whichever version `next` names later.
|
|
15
16
|
|
|
16
17
|
**Availability:**
|
|
17
18
|
|
|
18
|
-
-
|
|
19
|
-
`skill
|
|
20
|
-
- `
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
19
|
+
- `metergraph-cli@0.1.0` contains only `doctor` and `skill install` /
|
|
20
|
+
`skill update`. It has no sign in commands.
|
|
21
|
+
- `metergraph-cli@0.2.0-preview.0` adds `login`, `logout`, `setup`, `verify`,
|
|
22
|
+
`status`, `context`, `capabilities`, `usage`, `routes` and `traces`.
|
|
23
|
+
- `metergraph-cli@0.2.0-preview.1` makes `setup` write the SDK service root as
|
|
24
|
+
`METERGRAPH_INGEST_URL` and repairs the full ingest endpoint that
|
|
25
|
+
`0.2.0-preview.0` wrote, after checking the saved key. It also keeps validated,
|
|
26
|
+
workspace-bound trace links in Metadata reads.
|
|
27
|
+
- Sign in needs a Metergraph service that offers Metadata-only CLI grants and grant
|
|
25
28
|
revocation. A service without them is reported as unsupported, and the CLI never
|
|
26
29
|
falls back to broader access.
|
|
27
|
-
- The read commands
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
then requires the deployment's separate ingest bootstrap API and browser approval
|
|
32
|
-
by a member of that workspace.
|
|
33
|
-
- `verify` also exists only in this checkout. It checks one exact trace identity in an
|
|
34
|
-
explicit invocation window using Metadata access. It never sends application data.
|
|
30
|
+
- The read commands need a project signed in with `login`. `setup` also needs the
|
|
31
|
+
deployment's ingest bootstrap API and browser approval by a workspace member.
|
|
32
|
+
`verify` checks one exact trace identity in an explicit invocation window using
|
|
33
|
+
Metadata access. It never sends application data.
|
|
35
34
|
|
|
36
|
-
|
|
35
|
+
The preview includes:
|
|
37
36
|
|
|
38
37
|
- `doctor` checks whether a Metergraph service is reachable, healthy and supported.
|
|
39
38
|
- `skill install` and `skill update` copy the Metergraph agent skill bundled with the
|
|
40
39
|
CLI into one coding agent's project skill directory.
|
|
41
|
-
- `login` and `logout`
|
|
40
|
+
- `login` and `logout` sign a project in to one workspace through your
|
|
42
41
|
browser with a delegated, Metadata-only grant, and sign it out again.
|
|
43
|
-
- The read commands
|
|
42
|
+
- The read commands use that grant to read bounded workspace Metadata:
|
|
44
43
|
connection status, workspace context, capabilities, daily usage, routes and one page
|
|
45
44
|
of trace metadata.
|
|
46
|
-
- `setup`
|
|
45
|
+
- `setup` guides browser sign in and workspace choice, asks for
|
|
47
46
|
ingest-only approval, writes a private project env file, confirms delivery, and
|
|
48
47
|
installs the selected client skill.
|
|
49
|
-
- `verify`
|
|
48
|
+
- `verify` polls for one exact processed trace in a bounded window.
|
|
50
49
|
It does not infer application provenance from a Metadata match.
|
|
51
50
|
|
|
52
51
|
It does not read retained content, replay traces, call model providers or send
|
|
@@ -205,7 +204,7 @@ does not match. It never downloads the skill or runs a remote script. A new skil
|
|
|
205
204
|
revision ships only in a new CLI release; `skill update` then upgrades projects that
|
|
206
205
|
hold an unchanged earlier revision.
|
|
207
206
|
|
|
208
|
-
### login and logout
|
|
207
|
+
### login and logout
|
|
209
208
|
|
|
210
209
|
`login` binds a project directory to one Metergraph workspace. Your browser does the
|
|
211
210
|
sign in, sign up, invitation and workspace consent on the service's own pages and
|
|
@@ -296,7 +295,7 @@ revocation request. If it does not answer `200`, local sign out still happens an
|
|
|
296
295
|
command exits 13 with `revocation: "unconfirmed"`. A project that is not signed in
|
|
297
296
|
exits 0 without any request.
|
|
298
297
|
|
|
299
|
-
### setup
|
|
298
|
+
### setup
|
|
300
299
|
|
|
301
300
|
Run setup once from a project directory, choosing the coding client that will use
|
|
302
301
|
the skill:
|
|
@@ -343,6 +342,11 @@ page shows the workspace and the consequence of approval. A signed-in browser
|
|
|
343
342
|
on another workspace must switch in Metergraph and rerun; the CLI does not
|
|
344
343
|
switch it automatically.
|
|
345
344
|
|
|
345
|
+
`METERGRAPH_INGEST_URL` is the deployment's service root. The Python SDK
|
|
346
|
+
appends `/v1/ingest` itself. A setup rerun with a verified
|
|
347
|
+
key repairs the endpoint value written by the first preview without minting a
|
|
348
|
+
new key.
|
|
349
|
+
|
|
346
350
|
The env file must be a project-relative `.env`, `.env.<name>` or `<name>.env`
|
|
347
351
|
(`--env-file` selects another). The writer refuses tracked files, links,
|
|
348
352
|
ambiguous dotenv syntax and unsafe paths. It adds a project `.gitignore` rule
|
|
@@ -367,11 +371,11 @@ conflict leaves the delivered key in place and reports `credential_ready_skill_p
|
|
|
367
371
|
resolve the skill file conflict and rerun setup without another approval.
|
|
368
372
|
Success means the key was delivered and the project is ready to instrument.
|
|
369
373
|
It does **not** mean application traffic has arrived. Run your application and
|
|
370
|
-
verify one exact trace afterward.
|
|
371
|
-
|
|
372
|
-
|
|
374
|
+
verify one exact trace afterward. The hosted service advertises the setup
|
|
375
|
+
contract, but each non-hosted deployment must be checked at its own origin.
|
|
376
|
+
Local protocol tests do not prove browser approval or real application traffic.
|
|
373
377
|
|
|
374
|
-
### Read commands
|
|
378
|
+
### Read commands
|
|
375
379
|
|
|
376
380
|
The read commands use the grant `login` saved for this project. They never open a
|
|
377
381
|
browser, never sign in on their own and never request another scope. Each one:
|
|
@@ -512,8 +516,8 @@ before the failure, and `notices` lists fixed tokens such as `rows_truncated`,
|
|
|
512
516
|
|
|
513
517
|
## Exit codes
|
|
514
518
|
|
|
515
|
-
Exit codes are stable. Changing one is a breaking change. Codes 10 to 17
|
|
516
|
-
|
|
519
|
+
Exit codes are stable. Changing one is a breaking change. Codes 10 to 17 were
|
|
520
|
+
added in `0.2.0-preview.0` and are unavailable in `0.1.0`.
|
|
517
521
|
|
|
518
522
|
| Code | Outcome | Meaning |
|
|
519
523
|
| --- | --- | --- |
|
|
@@ -618,7 +622,7 @@ A successful `skill install`:
|
|
|
618
622
|
`receipt_invalid`, `locked`, `invalid_project`, `client_not_supported`,
|
|
619
623
|
`write_failed` or `bundled_skill_invalid`.
|
|
620
624
|
|
|
621
|
-
A successful `login
|
|
625
|
+
A successful `login`:
|
|
622
626
|
|
|
623
627
|
```json
|
|
624
628
|
{
|
|
@@ -759,7 +763,7 @@ To try a packed artifact without publishing:
|
|
|
759
763
|
|
|
760
764
|
```sh
|
|
761
765
|
npm pack --pack-destination "$(mktemp -d)"
|
|
762
|
-
npx --yes --package=/path/to/metergraph-cli-0.2.0-preview.
|
|
766
|
+
npx --yes --package=/path/to/metergraph-cli-0.2.0-preview.1.tgz -- metergraph --version
|
|
763
767
|
```
|
|
764
768
|
|
|
765
769
|
Do not commit tarballs or other generated files.
|
|
@@ -768,10 +772,9 @@ Do not commit tarballs or other generated files.
|
|
|
768
772
|
|
|
769
773
|
The source of truth is the public repository
|
|
770
774
|
[github.com/metergraph/cli](https://github.com/metergraph/cli), licensed Apache-2.0.
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
below before publication.
|
|
775
|
+
`0.2.0-preview.1` is published on the `next` npm tag; `latest` remains on
|
|
776
|
+
`0.1.0`. The hosted service supports sign in, Metadata reads and ingest bootstrap.
|
|
777
|
+
Subsequent releases must pass the checks below before publication.
|
|
775
778
|
|
|
776
779
|
Releases are manual. The `Release CLI` workflow (`.github/workflows/release.yml`) runs
|
|
777
780
|
only when a maintainer starts it from `main`. It does not run on tags, pushes or a
|
|
@@ -803,12 +806,12 @@ commit:
|
|
|
803
806
|
- If `main` moves after you copy the SHA, the run fails. Start a new run with the new
|
|
804
807
|
head. To release an older state, land it on `main` first.
|
|
805
808
|
|
|
806
|
-
### First package bootstrap
|
|
809
|
+
### First package bootstrap (completed for 0.1.0)
|
|
807
810
|
|
|
808
811
|
npm trusted publishing is configured on a package that already exists, so the very
|
|
809
|
-
first version
|
|
810
|
-
|
|
811
|
-
|
|
812
|
+
first version could not come from this workflow. The steps below describe the
|
|
813
|
+
completed bootstrap of `0.1.0`; they are not part of subsequent releases. No npm
|
|
814
|
+
token or secret is stored here.
|
|
812
815
|
|
|
813
816
|
1. Confirm the intended npm maintainer accounts and that `metergraph-cli` is
|
|
814
817
|
available. The first approved publish establishes package ownership.
|
|
@@ -837,15 +840,13 @@ Trusted publishing requires npm 11.5.1 or newer. The publish job checks this bef
|
|
|
837
840
|
publishes. After the trusted publisher works, consider restricting the package to
|
|
838
841
|
trusted publishing so that long lived tokens cannot publish it.
|
|
839
842
|
|
|
840
|
-
###
|
|
843
|
+
### Release configuration
|
|
841
844
|
|
|
842
|
-
|
|
843
|
-
maintainer still has to:
|
|
845
|
+
Before an automated publish, confirm the following settings are still in place:
|
|
844
846
|
|
|
845
|
-
-
|
|
846
|
-
-
|
|
847
|
-
-
|
|
848
|
-
- set the repository variable `METERGRAPH_CLI_PUBLISH_ENABLED` to `true`.
|
|
847
|
+
- the [trusted publisher](#trusted-publishing) matches this repository and workflow;
|
|
848
|
+
- the `npm-release` environment has required reviewers;
|
|
849
|
+
- the repository variable `METERGRAPH_CLI_PUBLISH_ENABLED` is `true`.
|
|
849
850
|
|
|
850
851
|
Until all of these are done, leave `publish` false.
|
|
851
852
|
|
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.1",
|
|
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": {
|
package/src/read-contract.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { normalizeUuid } from "./auth-oauth.js";
|
|
2
2
|
import { AGENT_CONTRACT_VERSION, MAX_CURSOR_LENGTH, METADATA_SCOPE } from "./constants.js";
|
|
3
|
+
import { traceLink } from "./trace-contract.js";
|
|
3
4
|
|
|
4
5
|
// Validators for the service's Metadata read documents. Each one checks the
|
|
5
6
|
// document against the agent access contract, the bound workspace and the
|
|
@@ -530,6 +531,8 @@ export function tracesReport(body, ctx, request, notices) {
|
|
|
530
531
|
) {
|
|
531
532
|
throw new Invalid("verification_failed", reason);
|
|
532
533
|
}
|
|
534
|
+
const link = traceLink(row.metergraph_links?.trace ?? null, ctx.origin, row, ctx.workspaceId);
|
|
535
|
+
if (!link.ok) throw new Invalid(link.outcome, link.reason);
|
|
533
536
|
return {
|
|
534
537
|
id: row.id,
|
|
535
538
|
trace_id: row.trace_id,
|
|
@@ -546,12 +549,16 @@ export function tracesReport(body, ctx, request, notices) {
|
|
|
546
549
|
routes: nameList(row.routes, notices, reason),
|
|
547
550
|
providers: nameList(row.providers, notices, reason),
|
|
548
551
|
models: nameList(row.models, notices, reason),
|
|
549
|
-
link:
|
|
552
|
+
link: link.value,
|
|
553
|
+
link_workspace_bound: link.workspaceBound === true,
|
|
550
554
|
};
|
|
551
555
|
});
|
|
552
556
|
if (!proof.complete) notices.add("evidence_incomplete");
|
|
553
557
|
if (page.truncated) notices.add("more_pages");
|
|
554
|
-
|
|
558
|
+
const missingLinks = traces.length === 0 || traces.some((trace) => trace.link === null);
|
|
559
|
+
if (missingLinks) notices.add("trace_links_unavailable");
|
|
560
|
+
const unboundLinks = traces.some((trace) => trace.link !== null && !trace.link_workspace_bound);
|
|
561
|
+
if (unboundLinks) notices.add("trace_workspace_binding_unavailable");
|
|
555
562
|
return {
|
|
556
563
|
provenance: origin,
|
|
557
564
|
window: span,
|
|
@@ -570,7 +577,8 @@ export function tracesReport(body, ctx, request, notices) {
|
|
|
570
577
|
next_cursor: page.next_cursor,
|
|
571
578
|
complete: proof.complete && !page.truncated,
|
|
572
579
|
empty: traces.length === 0,
|
|
573
|
-
link_status: "server_link_unavailable"
|
|
580
|
+
link_status: missingLinks ? "server_link_unavailable" :
|
|
581
|
+
unboundLinks ? "workspace_binding_unavailable" : "available",
|
|
574
582
|
traces,
|
|
575
583
|
};
|
|
576
584
|
});
|
package/src/setup-env-parse.js
CHANGED
|
@@ -13,14 +13,14 @@ export const ENV_NAMES = Object.freeze({
|
|
|
13
13
|
});
|
|
14
14
|
const NAMES = Object.values(ENV_NAMES);
|
|
15
15
|
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
export const INGEST_PATHS = Object.freeze(["/v1/ingest"]);
|
|
16
|
+
// The SDK expects a service root and appends /v1/ingest itself. Accept the
|
|
17
|
+
// endpoint form only while reading setup files written by the first preview.
|
|
18
|
+
export const INGEST_PATHS = Object.freeze(["", "/v1/ingest"]);
|
|
19
19
|
|
|
20
20
|
// Server-generated application tokens are RFC 3986 unreserved characters
|
|
21
21
|
// only, so a value can never carry a quote, newline, comment or space.
|
|
22
22
|
const TOKEN = /^[A-Za-z0-9._~-]{16,1024}$/;
|
|
23
|
-
const URL_TEXT = /^(https?:\/\/[^/?#@\\]+)(\/[A-Za-z0-9/_.-]*)
|
|
23
|
+
const URL_TEXT = /^(https?:\/\/[^/?#@\\]+)(\/[A-Za-z0-9/_.-]*)?$/i;
|
|
24
24
|
const SAFE_URL = /^[A-Za-z0-9.:/[\]_-]+$/;
|
|
25
25
|
|
|
26
26
|
const ASSIGNMENT = /^([ \t]*)(export[ \t]+)?([A-Za-z_][A-Za-z0-9_.-]*)([ \t]*=[ \t]*)(.*)$/;
|
|
@@ -35,15 +35,15 @@ export function isAppToken(value) {
|
|
|
35
35
|
return typeof value === "string" && TOKEN.test(value);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// Returns the normalized
|
|
38
|
+
// Returns the normalized service root or legacy ingest endpoint, or null.
|
|
39
39
|
// The caller must not echo the raw input when this returns null.
|
|
40
40
|
export function parseIngestUrl(raw) {
|
|
41
41
|
if (typeof raw !== "string" || raw.length > 2048) return null;
|
|
42
42
|
const match = URL_TEXT.exec(raw);
|
|
43
|
-
if (match === null || !INGEST_PATHS.includes(match[2])) return null;
|
|
43
|
+
if (match === null || !INGEST_PATHS.includes(match[2] ?? "")) return null;
|
|
44
44
|
const origin = parseOrigin(match[1]);
|
|
45
45
|
if (origin === null) return null;
|
|
46
|
-
const url = `${origin}${match[2]}`;
|
|
46
|
+
const url = `${origin}${match[2] ?? ""}`;
|
|
47
47
|
return SAFE_URL.test(url) ? url : null;
|
|
48
48
|
}
|
|
49
49
|
|
package/src/setup.js
CHANGED
|
@@ -111,7 +111,9 @@ export async function runSetup(options, progress = () => {}) {
|
|
|
111
111
|
if (previous && existing.token !== null) {
|
|
112
112
|
const checked = await checkCredential(origin, existing.token, previous.value, profile, trap.signal);
|
|
113
113
|
if (checked.ok) {
|
|
114
|
-
if (existing.ingestUrl !== `${origin}/v1/ingest`)
|
|
114
|
+
if (existing.ingestUrl !== origin && existing.ingestUrl !== `${origin}/v1/ingest`) {
|
|
115
|
+
return stop("conflict", "ingest_url_mismatch");
|
|
116
|
+
}
|
|
115
117
|
if (previous.value.key_id !== null && previous.value.key_id !== checked.keyId) {
|
|
116
118
|
return stop("conflict", "setup_key_changed");
|
|
117
119
|
}
|
|
@@ -121,6 +123,13 @@ export async function runSetup(options, progress = () => {}) {
|
|
|
121
123
|
if (previous.value.phase !== "delivered" || previous.value.key_id === null) {
|
|
122
124
|
writeSetupState(root, withSetupState(previous.value, { key_id: checked.keyId, phase: "delivered" }), previous);
|
|
123
125
|
}
|
|
126
|
+
// The first preview wrote the endpoint, which the SDK appends again.
|
|
127
|
+
// Repair only after verifying this family's saved key, without issuing
|
|
128
|
+
// another key or changing the workspace binding.
|
|
129
|
+
if (existing.ingestUrl !== origin) {
|
|
130
|
+
const repaired = await commitEnv(plan, { ingestUrl: origin, signal: trap.signal });
|
|
131
|
+
return finishSkill(root, options, repaired.receipt.file);
|
|
132
|
+
}
|
|
124
133
|
return finishSkill(root, options, "unchanged");
|
|
125
134
|
}
|
|
126
135
|
if (!options.repair && previous.value.phase !== "redeem_attempted") return stop("verification_failed", "saved_key_unverified");
|
|
@@ -188,7 +197,7 @@ export async function runSetup(options, progress = () => {}) {
|
|
|
188
197
|
redirect_uri: listener.redirectUri, code_verifier: pkce.verifier,
|
|
189
198
|
family_id: state.family_id, workspace_id: workspaceId }, profile, trap.signal);
|
|
190
199
|
if (!redeemed.ok) return stop(redeemed.outcome, redeemed.reason, "delivery_pending");
|
|
191
|
-
const written = await commitEnv(plan, { token: redeemed.token, ingestUrl:
|
|
200
|
+
const written = await commitEnv(plan, { token: redeemed.token, ingestUrl: origin, signal: trap.signal });
|
|
192
201
|
const checked = await checkCredential(origin, redeemed.token, state, profile, trap.signal);
|
|
193
202
|
if (!checked.ok || checked.keyId !== redeemed.keyId) return stop("verification_failed", "issued_key_unverified", "delivery_pending");
|
|
194
203
|
const ack = await acknowledge(origin, redeemed.token, state, profile, redeemed.keyId, trap.signal);
|