metergraph-cli 0.2.0-preview.0 → 0.2.0-preview.2
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 +64 -50
- package/package.json +1 -1
- package/src/auth-login.js +9 -6
- package/src/output.js +2 -2
- package/src/read-contract.js +11 -3
- package/src/setup-env-parse.js +7 -7
- package/src/setup.js +14 -3
- package/src/trace-open.js +7 -2
- package/src/verify.js +4 -1
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.2` 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,45 @@ 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.2`
|
|
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
|
+
- `metergraph-cli@0.2.0-preview.2` makes `verify` exit 11 `verification_failed`
|
|
28
|
+
when its deadline expires after the service reported the trace as pending, lets
|
|
29
|
+
`login` and `setup` reruns that need no approval succeed with `--json
|
|
30
|
+
--no-browser`, and reports why `verify --open` did not launch a browser.
|
|
31
|
+
- Sign in needs a Metergraph service that offers Metadata-only CLI grants and grant
|
|
25
32
|
revocation. A service without them is reported as unsupported, and the CLI never
|
|
26
33
|
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.
|
|
34
|
+
- The read commands need a project signed in with `login`. `setup` also needs the
|
|
35
|
+
deployment's ingest bootstrap API and browser approval by a workspace member.
|
|
36
|
+
`verify` checks one exact trace identity in an explicit invocation window using
|
|
37
|
+
Metadata access. It never sends application data.
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
The preview includes:
|
|
37
40
|
|
|
38
41
|
- `doctor` checks whether a Metergraph service is reachable, healthy and supported.
|
|
39
42
|
- `skill install` and `skill update` copy the Metergraph agent skill bundled with the
|
|
40
43
|
CLI into one coding agent's project skill directory.
|
|
41
|
-
- `login` and `logout`
|
|
44
|
+
- `login` and `logout` sign a project in to one workspace through your
|
|
42
45
|
browser with a delegated, Metadata-only grant, and sign it out again.
|
|
43
|
-
- The read commands
|
|
46
|
+
- The read commands use that grant to read bounded workspace Metadata:
|
|
44
47
|
connection status, workspace context, capabilities, daily usage, routes and one page
|
|
45
48
|
of trace metadata.
|
|
46
|
-
- `setup`
|
|
49
|
+
- `setup` guides browser sign in and workspace choice, asks for
|
|
47
50
|
ingest-only approval, writes a private project env file, confirms delivery, and
|
|
48
51
|
installs the selected client skill.
|
|
49
|
-
- `verify`
|
|
52
|
+
- `verify` polls for one exact processed trace in a bounded window.
|
|
50
53
|
It does not infer application provenance from a Metadata match.
|
|
51
54
|
|
|
52
55
|
It does not read retained content, replay traces, call model providers or send
|
|
@@ -101,7 +104,16 @@ sends a test event.
|
|
|
101
104
|
`--open` launches only a server-provided link carrying the exact trace and the
|
|
102
105
|
verified workspace ID. The dashboard must check that ID against its signed-in
|
|
103
106
|
workspace before displaying traces. Older links without a workspace remain a
|
|
104
|
-
manual handoff; a conflicting workspace or unsafe link is refused.
|
|
107
|
+
manual handoff; a conflicting workspace or unsafe link is refused. The result's
|
|
108
|
+
`browser` field is `launcher_started`, `launcher_unavailable`, `not_requested`
|
|
109
|
+
(no `--open`), or `suppressed_by_json` / `suppressed_by_no_browser` when `--open`
|
|
110
|
+
was given but that flag returned the link instead of launching it.
|
|
111
|
+
|
|
112
|
+
If `--timeout-ms` expires after the service has answered at least once with a
|
|
113
|
+
pending result, `verify` exits 11 `verification_failed` with reason
|
|
114
|
+
`verification_timeout`: the origin is reachable and the trace is not visible yet.
|
|
115
|
+
Exit 4 `connection_failed` with that reason means no valid answer arrived before
|
|
116
|
+
the deadline.
|
|
105
117
|
|
|
106
118
|
### doctor
|
|
107
119
|
|
|
@@ -205,7 +217,7 @@ does not match. It never downloads the skill or runs a remote script. A new skil
|
|
|
205
217
|
revision ships only in a new CLI release; `skill update` then upgrades projects that
|
|
206
218
|
hold an unchanged earlier revision.
|
|
207
219
|
|
|
208
|
-
### login and logout
|
|
220
|
+
### login and logout
|
|
209
221
|
|
|
210
222
|
`login` binds a project directory to one Metergraph workspace. Your browser does the
|
|
211
223
|
sign in, sign up, invitation and workspace consent on the service's own pages and
|
|
@@ -226,7 +238,7 @@ metergraph logout
|
|
|
226
238
|
| `--config-dir DIR` | see below | Private per-user directory for the saved grant. |
|
|
227
239
|
| `--timeout-ms N` | `300000` | How long to wait for the browser, 1000 to 900000. |
|
|
228
240
|
| `--signup` | off | Start at the hosted sign up page, which returns to the same authorization request. Managed service only; other profiles exit 6. |
|
|
229
|
-
| `--no-browser` | off | Print the authorization URL on stderr for you to open on this machine, then wait.
|
|
241
|
+
| `--no-browser` | off | Print the authorization URL on stderr for you to open on this machine, then wait. With `--json`, a rerun that needs no approval succeeds; one that needs approval exits 6 `no_browser_requires_terminal`. |
|
|
230
242
|
| `--reconnect` | off | Allow replacing a binding to a different origin or workspace. |
|
|
231
243
|
| `--json` | off | Print exactly one JSON line on stdout and nothing on stderr. |
|
|
232
244
|
|
|
@@ -296,7 +308,7 @@ revocation request. If it does not answer `200`, local sign out still happens an
|
|
|
296
308
|
command exits 13 with `revocation: "unconfirmed"`. A project that is not signed in
|
|
297
309
|
exits 0 without any request.
|
|
298
310
|
|
|
299
|
-
### setup
|
|
311
|
+
### setup
|
|
300
312
|
|
|
301
313
|
Run setup once from a project directory, choosing the coding client that will use
|
|
302
314
|
the skill:
|
|
@@ -343,6 +355,11 @@ page shows the workspace and the consequence of approval. A signed-in browser
|
|
|
343
355
|
on another workspace must switch in Metergraph and rerun; the CLI does not
|
|
344
356
|
switch it automatically.
|
|
345
357
|
|
|
358
|
+
`METERGRAPH_INGEST_URL` is the deployment's service root. The Python SDK
|
|
359
|
+
appends `/v1/ingest` itself. A setup rerun with a verified
|
|
360
|
+
key repairs the endpoint value written by the first preview without minting a
|
|
361
|
+
new key.
|
|
362
|
+
|
|
346
363
|
The env file must be a project-relative `.env`, `.env.<name>` or `<name>.env`
|
|
347
364
|
(`--env-file` selects another). The writer refuses tracked files, links,
|
|
348
365
|
ambiguous dotenv syntax and unsafe paths. It adds a project `.gitignore` rule
|
|
@@ -367,11 +384,11 @@ conflict leaves the delivered key in place and reports `credential_ready_skill_p
|
|
|
367
384
|
resolve the skill file conflict and rerun setup without another approval.
|
|
368
385
|
Success means the key was delivered and the project is ready to instrument.
|
|
369
386
|
It does **not** mean application traffic has arrived. Run your application and
|
|
370
|
-
verify one exact trace afterward.
|
|
371
|
-
|
|
372
|
-
|
|
387
|
+
verify one exact trace afterward. The hosted service advertises the setup
|
|
388
|
+
contract, but each non-hosted deployment must be checked at its own origin.
|
|
389
|
+
Local protocol tests do not prove browser approval or real application traffic.
|
|
373
390
|
|
|
374
|
-
### Read commands
|
|
391
|
+
### Read commands
|
|
375
392
|
|
|
376
393
|
The read commands use the grant `login` saved for this project. They never open a
|
|
377
394
|
browser, never sign in on their own and never request another scope. Each one:
|
|
@@ -512,8 +529,8 @@ before the failure, and `notices` lists fixed tokens such as `rows_truncated`,
|
|
|
512
529
|
|
|
513
530
|
## Exit codes
|
|
514
531
|
|
|
515
|
-
Exit codes are stable. Changing one is a breaking change. Codes 10 to 17
|
|
516
|
-
|
|
532
|
+
Exit codes are stable. Changing one is a breaking change. Codes 10 to 17 were
|
|
533
|
+
added in `0.2.0-preview.0` and are unavailable in `0.1.0`.
|
|
517
534
|
|
|
518
535
|
| Code | Outcome | Meaning |
|
|
519
536
|
| --- | --- | --- |
|
|
@@ -618,7 +635,7 @@ A successful `skill install`:
|
|
|
618
635
|
`receipt_invalid`, `locked`, `invalid_project`, `client_not_supported`,
|
|
619
636
|
`write_failed` or `bundled_skill_invalid`.
|
|
620
637
|
|
|
621
|
-
A successful `login
|
|
638
|
+
A successful `login`:
|
|
622
639
|
|
|
623
640
|
```json
|
|
624
641
|
{
|
|
@@ -759,7 +776,7 @@ To try a packed artifact without publishing:
|
|
|
759
776
|
|
|
760
777
|
```sh
|
|
761
778
|
npm pack --pack-destination "$(mktemp -d)"
|
|
762
|
-
npx --yes --package=/path/to/metergraph-cli-0.2.0-preview.
|
|
779
|
+
npx --yes --package=/path/to/metergraph-cli-0.2.0-preview.2.tgz -- metergraph --version
|
|
763
780
|
```
|
|
764
781
|
|
|
765
782
|
Do not commit tarballs or other generated files.
|
|
@@ -768,10 +785,9 @@ Do not commit tarballs or other generated files.
|
|
|
768
785
|
|
|
769
786
|
The source of truth is the public repository
|
|
770
787
|
[github.com/metergraph/cli](https://github.com/metergraph/cli), licensed Apache-2.0.
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
below before publication.
|
|
788
|
+
`0.2.0-preview.2` is published on the `next` npm tag; `latest` remains on
|
|
789
|
+
`0.1.0`. The hosted service supports sign in, Metadata reads and ingest bootstrap.
|
|
790
|
+
Subsequent releases must pass the checks below before publication.
|
|
775
791
|
|
|
776
792
|
Releases are manual. The `Release CLI` workflow (`.github/workflows/release.yml`) runs
|
|
777
793
|
only when a maintainer starts it from `main`. It does not run on tags, pushes or a
|
|
@@ -803,12 +819,12 @@ commit:
|
|
|
803
819
|
- If `main` moves after you copy the SHA, the run fails. Start a new run with the new
|
|
804
820
|
head. To release an older state, land it on `main` first.
|
|
805
821
|
|
|
806
|
-
### First package bootstrap
|
|
822
|
+
### First package bootstrap (completed for 0.1.0)
|
|
807
823
|
|
|
808
824
|
npm trusted publishing is configured on a package that already exists, so the very
|
|
809
|
-
first version
|
|
810
|
-
|
|
811
|
-
|
|
825
|
+
first version could not come from this workflow. The steps below describe the
|
|
826
|
+
completed bootstrap of `0.1.0`; they are not part of subsequent releases. No npm
|
|
827
|
+
token or secret is stored here.
|
|
812
828
|
|
|
813
829
|
1. Confirm the intended npm maintainer accounts and that `metergraph-cli` is
|
|
814
830
|
available. The first approved publish establishes package ownership.
|
|
@@ -837,15 +853,13 @@ Trusted publishing requires npm 11.5.1 or newer. The publish job checks this bef
|
|
|
837
853
|
publishes. After the trusted publisher works, consider restricting the package to
|
|
838
854
|
trusted publishing so that long lived tokens cannot publish it.
|
|
839
855
|
|
|
840
|
-
###
|
|
856
|
+
### Release configuration
|
|
841
857
|
|
|
842
|
-
|
|
843
|
-
maintainer still has to:
|
|
858
|
+
Before an automated publish, confirm the following settings are still in place:
|
|
844
859
|
|
|
845
|
-
-
|
|
846
|
-
-
|
|
847
|
-
-
|
|
848
|
-
- set the repository variable `METERGRAPH_CLI_PUBLISH_ENABLED` to `true`.
|
|
860
|
+
- the [trusted publisher](#trusted-publishing) matches this repository and workflow;
|
|
861
|
+
- the `npm-release` environment has required reviewers;
|
|
862
|
+
- the repository variable `METERGRAPH_CLI_PUBLISH_ENABLED` is `true`.
|
|
849
863
|
|
|
850
864
|
Until all of these are done, leave `publish` false.
|
|
851
865
|
|
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.2",
|
|
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/auth-login.js
CHANGED
|
@@ -58,12 +58,13 @@ export async function runLogin(options, progress) {
|
|
|
58
58
|
if (options.runtime !== "local") return end("unsupported", "runtime_not_supported", GUIDE);
|
|
59
59
|
const remote = detectRemoteSession();
|
|
60
60
|
if (remote !== null) return end("unsupported", remote, GUIDE);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
61
|
+
// --json cannot show a person the --no-browser URL. A rerun with a saved,
|
|
62
|
+
// working grant needs no URL, so only refuse once approval is required.
|
|
63
|
+
const noBrowserHandoff = () => end("unsupported", "no_browser_requires_terminal", {
|
|
64
|
+
kind: "run_in_terminal",
|
|
65
|
+
message: "Run the same login command in a terminal without --json to see the sign in URL.",
|
|
66
|
+
});
|
|
67
|
+
const urlUnshowable = options.json && options.noBrowser;
|
|
67
68
|
|
|
68
69
|
const trap = trapSignals();
|
|
69
70
|
try {
|
|
@@ -71,6 +72,7 @@ export async function runLogin(options, progress) {
|
|
|
71
72
|
const configDir = resolveConfigDir(options.configDir);
|
|
72
73
|
const existing = readBinding(root);
|
|
73
74
|
const bound = existing?.binding ?? null;
|
|
75
|
+
if (urlUnshowable && (bound === null || options.reconnect)) return noBrowserHandoff();
|
|
74
76
|
// Setup pins a deployment model before sign in. A stale binding for a
|
|
75
77
|
// different profile must never be silently reused or reconnected.
|
|
76
78
|
if (options.expectedProfile && bound !== null && !options.reconnect &&
|
|
@@ -100,6 +102,7 @@ export async function runLogin(options, progress) {
|
|
|
100
102
|
}
|
|
101
103
|
if (session.outcome !== "login_required") return end(session.outcome, session.reason);
|
|
102
104
|
}
|
|
105
|
+
if (urlUnshowable) return noBrowserHandoff();
|
|
103
106
|
|
|
104
107
|
const flow = await authorize(options, ctx, trap.signal, progress);
|
|
105
108
|
if (!flow.ok) return end(flow.outcome, flow.reason, flow.next ?? null);
|
package/src/output.js
CHANGED
|
@@ -599,8 +599,8 @@ const AUTH_MESSAGES = {
|
|
|
599
599
|
ci_environment:
|
|
600
600
|
"This is a CI environment, where no person can approve sign in in a browser. Nothing was written.",
|
|
601
601
|
no_browser_requires_terminal:
|
|
602
|
-
"--no-browser prints
|
|
603
|
-
"Run
|
|
602
|
+
"Approval is needed, and --no-browser prints its URL for a person to open, which --json cannot do. " +
|
|
603
|
+
"Run the same command without --json in a terminal. Nothing was done.",
|
|
604
604
|
bound_to_other_origin:
|
|
605
605
|
"This project is bound to a different origin. Nothing was changed. Use --reconnect to switch it.",
|
|
606
606
|
bound_to_other_workspace:
|
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
|
@@ -31,7 +31,6 @@ const fail = (outcome, reason, status = "not_ready", receipt = null) => ({ outco
|
|
|
31
31
|
// response can supply it. Every returned string is fixed and non-secret.
|
|
32
32
|
export async function runSetup(options, progress = () => {}) {
|
|
33
33
|
if (options.runtime !== "local" || detectRemoteSession() !== null) return fail("unsupported", "run_on_local_machine");
|
|
34
|
-
if (options.json && options.noBrowser) return fail("unsupported", "no_browser_requires_terminal");
|
|
35
34
|
const trap = trapSignals();
|
|
36
35
|
let receipt = null;
|
|
37
36
|
let loginVerified = false;
|
|
@@ -111,7 +110,9 @@ export async function runSetup(options, progress = () => {}) {
|
|
|
111
110
|
if (previous && existing.token !== null) {
|
|
112
111
|
const checked = await checkCredential(origin, existing.token, previous.value, profile, trap.signal);
|
|
113
112
|
if (checked.ok) {
|
|
114
|
-
if (existing.ingestUrl !== `${origin}/v1/ingest`)
|
|
113
|
+
if (existing.ingestUrl !== origin && existing.ingestUrl !== `${origin}/v1/ingest`) {
|
|
114
|
+
return stop("conflict", "ingest_url_mismatch");
|
|
115
|
+
}
|
|
115
116
|
if (previous.value.key_id !== null && previous.value.key_id !== checked.keyId) {
|
|
116
117
|
return stop("conflict", "setup_key_changed");
|
|
117
118
|
}
|
|
@@ -121,6 +122,13 @@ export async function runSetup(options, progress = () => {}) {
|
|
|
121
122
|
if (previous.value.phase !== "delivered" || previous.value.key_id === null) {
|
|
122
123
|
writeSetupState(root, withSetupState(previous.value, { key_id: checked.keyId, phase: "delivered" }), previous);
|
|
123
124
|
}
|
|
125
|
+
// The first preview wrote the endpoint, which the SDK appends again.
|
|
126
|
+
// Repair only after verifying this family's saved key, without issuing
|
|
127
|
+
// another key or changing the workspace binding.
|
|
128
|
+
if (existing.ingestUrl !== origin) {
|
|
129
|
+
const repaired = await commitEnv(plan, { ingestUrl: origin, signal: trap.signal });
|
|
130
|
+
return finishSkill(root, options, repaired.receipt.file);
|
|
131
|
+
}
|
|
124
132
|
return finishSkill(root, options, "unchanged");
|
|
125
133
|
}
|
|
126
134
|
if (!options.repair && previous.value.phase !== "redeem_attempted") return stop("verification_failed", "saved_key_unverified");
|
|
@@ -129,6 +137,9 @@ export async function runSetup(options, progress = () => {}) {
|
|
|
129
137
|
return stop("conflict", "repair_requires_saved_key");
|
|
130
138
|
}
|
|
131
139
|
|
|
140
|
+
// A verified rerun returned above without approval. From here a person
|
|
141
|
+
// must open an approval URL, which --json cannot show with --no-browser.
|
|
142
|
+
if (options.json && options.noBrowser) return stop("unsupported", "no_browser_requires_terminal");
|
|
132
143
|
const setup = await discoverSetup(origin, profile, trap.signal);
|
|
133
144
|
if (!setup.ok) return stop(setup.outcome, setup.reason);
|
|
134
145
|
|
|
@@ -188,7 +199,7 @@ export async function runSetup(options, progress = () => {}) {
|
|
|
188
199
|
redirect_uri: listener.redirectUri, code_verifier: pkce.verifier,
|
|
189
200
|
family_id: state.family_id, workspace_id: workspaceId }, profile, trap.signal);
|
|
190
201
|
if (!redeemed.ok) return stop(redeemed.outcome, redeemed.reason, "delivery_pending");
|
|
191
|
-
const written = await commitEnv(plan, { token: redeemed.token, ingestUrl:
|
|
202
|
+
const written = await commitEnv(plan, { token: redeemed.token, ingestUrl: origin, signal: trap.signal });
|
|
192
203
|
const checked = await checkCredential(origin, redeemed.token, state, profile, trap.signal);
|
|
193
204
|
if (!checked.ok || checked.keyId !== redeemed.keyId) return stop("verification_failed", "issued_key_unverified", "delivery_pending");
|
|
194
205
|
const ack = await acknowledge(origin, redeemed.token, state, profile, redeemed.keyId, trap.signal);
|
package/src/trace-open.js
CHANGED
|
@@ -4,7 +4,8 @@ import { normalizeUuid } from "./auth-oauth.js";
|
|
|
4
4
|
import { parseOrigin } from "./origin.js";
|
|
5
5
|
|
|
6
6
|
// Operates only on an already verified, provenance-bound receipt. JSON and
|
|
7
|
-
// no-browser mode return its URL without starting a launcher
|
|
7
|
+
// no-browser mode return its URL without starting a launcher, and say which
|
|
8
|
+
// one suppressed an --open request. "opened" means
|
|
8
9
|
// the OS launcher started; it does not assert a browser rendered the page.
|
|
9
10
|
export async function openTrace(receipt, options = {}, launch = openBrowser) {
|
|
10
11
|
if (receipt?.readiness?.metadata_available !== true || receipt?.readiness?.processed !== true ||
|
|
@@ -20,7 +21,11 @@ export async function openTrace(receipt, options = {}, launch = openBrowser) {
|
|
|
20
21
|
if (link.workspaceBound !== true || receipt.link_workspace_bound !== true || receipt.link_status !== "available") {
|
|
21
22
|
return { outcome: "unsupported", reason: "link_workspace_binding_unavailable", data: { ...receipt, browser: "not_requested" } };
|
|
22
23
|
}
|
|
23
|
-
if (
|
|
24
|
+
if (!options.open) return { outcome: "ok", reason: null, data: { ...receipt, browser: "not_requested" } };
|
|
25
|
+
if (options.json || options.noBrowser) {
|
|
26
|
+
const browser = options.json ? "suppressed_by_json" : "suppressed_by_no_browser";
|
|
27
|
+
return { outcome: "ok", reason: null, data: { ...receipt, browser } };
|
|
28
|
+
}
|
|
24
29
|
if (options.signal?.aborted) return { outcome: "cancelled", reason: "cancelled", data: receipt };
|
|
25
30
|
const launched = await launchWithinSignal(link.value, launch, options.signal);
|
|
26
31
|
if (options.signal?.aborted) return { outcome: "cancelled", reason: "cancelled", data: receipt };
|
package/src/verify.js
CHANGED
|
@@ -33,7 +33,10 @@ export async function runVerify(options, dependencies = {}) {
|
|
|
33
33
|
if (holdsKnown(data, known)) return { outcome: "verification_failed", reason: "credential_in_metadata_response", data: null };
|
|
34
34
|
return { outcome, reason, data: data === null ? null : { ...data, attempts } };
|
|
35
35
|
};
|
|
36
|
-
|
|
36
|
+
// Once the origin has answered with a valid pending receipt, a deadline
|
|
37
|
+
// means the trace is not visible yet, not that the origin is unreachable.
|
|
38
|
+
const interrupted = () => !limit.timedOut() ? end("cancelled", "cancelled")
|
|
39
|
+
: end(receipt === null ? "connection_failed" : "verification_failed", "verification_timeout");
|
|
37
40
|
try {
|
|
38
41
|
const sessionResult = await (dependencies.session ?? verifiedSession)({ project: options.project, configDir: options.configDir, cancel: limit.signal });
|
|
39
42
|
if (limit.signal.aborted) return interrupted();
|