metergraph-cli 0.2.0-preview.5 → 0.2.0
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 +34 -23
- package/assets/skill/SKILL.md +10 -1
- package/assets/skill/manifest.json +3 -3
- package/assets/skills/manifest.json +10 -4
- package/assets/skills/metergraph-local-bundle/SKILL.md +283 -0
- package/assets/skills/metergraph-onboarding/SKILL.md +3 -2
- package/bin/metergraph.js +4 -4
- package/package.json +2 -2
- package/src/approval-handoff.js +91 -0
- package/src/args.js +7 -8
- package/src/auth-login.js +27 -13
- package/src/cli.js +54 -6
- package/src/constants.js +5 -0
- package/src/deployment-route.js +1 -1
- package/src/output.js +22 -11
- package/src/setup-deployment.js +103 -7
- package/src/setup.js +33 -16
- package/src/skill-bundle.js +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# metergraph-cli
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/metergraph-cli)
|
|
4
4
|
[](https://github.com/metergraph/cli/actions/workflows/ci.yml)
|
|
5
5
|
[](https://github.com/metergraph/cli/blob/main/LICENSE)
|
|
6
6
|
|
|
@@ -13,11 +13,10 @@ token and route metadata back. Every command has stable `--json` output and exit
|
|
|
13
13
|
so agents can run it as well as people.
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
|
-
npx --yes metergraph-cli
|
|
16
|
+
npx --yes metergraph-cli --help
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
only `doctor` and `skill`. See the [changelog](https://github.com/metergraph/cli/blob/main/CHANGELOG.md).
|
|
19
|
+
Published on npm as `latest`. See the [changelog](https://github.com/metergraph/cli/blob/main/CHANGELOG.md).
|
|
21
20
|
|
|
22
21
|
## Quickstart
|
|
23
22
|
|
|
@@ -27,7 +26,7 @@ writes `METERGRAPH_APP_TOKEN` and `METERGRAPH_INGEST_URL` to a private `.env`, a
|
|
|
27
26
|
installs the skill for your coding agent.
|
|
28
27
|
|
|
29
28
|
```sh
|
|
30
|
-
npx --yes metergraph-cli
|
|
29
|
+
npx --yes metergraph-cli setup --runtime local --client claude
|
|
31
30
|
```
|
|
32
31
|
|
|
33
32
|
Use `--client codex` or `--client cursor` for the other agents.
|
|
@@ -40,20 +39,20 @@ ID or request ID and when the call started and ended.
|
|
|
40
39
|
exact trace is visible in your workspace.
|
|
41
40
|
|
|
42
41
|
```sh
|
|
43
|
-
npx --yes metergraph-cli
|
|
42
|
+
npx --yes metergraph-cli verify --trace-id TRACE_ID \
|
|
44
43
|
--since 2026-01-08T12:00:00Z --until 2026-01-08T12:00:05Z
|
|
45
44
|
```
|
|
46
45
|
|
|
47
46
|
Then read what Metergraph has recorded:
|
|
48
47
|
|
|
49
48
|
```sh
|
|
50
|
-
npx --yes metergraph-cli
|
|
51
|
-
npx --yes metergraph-cli
|
|
49
|
+
npx --yes metergraph-cli usage --days 7 # calls, errors, cost and tokens per route per day
|
|
50
|
+
npx --yes metergraph-cli traces --limit 20 # one page of trace metadata
|
|
52
51
|
```
|
|
53
52
|
|
|
54
|
-
To install it globally instead, run `npm install -g metergraph-cli
|
|
55
|
-
`metergraph`. Pin an exact version, such as `metergraph-cli@0.2.0
|
|
56
|
-
script must not change with the `next` tag.
|
|
53
|
+
To install it globally instead, run `npm install -g metergraph-cli`. The command is
|
|
54
|
+
`metergraph`. Pin an exact version, such as `metergraph-cli@0.2.0`, when a
|
|
55
|
+
script must not change with the `latest` tag. Previews use the `next` tag.
|
|
57
56
|
|
|
58
57
|
## Requirements
|
|
59
58
|
|
|
@@ -80,11 +79,11 @@ The CLI is built to be run by Codex, Claude Code and Cursor as well as by people
|
|
|
80
79
|
network access:
|
|
81
80
|
|
|
82
81
|
```sh
|
|
83
|
-
npx --yes metergraph-cli
|
|
82
|
+
npx --yes metergraph-cli skill install --client codex
|
|
84
83
|
```
|
|
85
84
|
|
|
86
85
|
Python users can run `pip install metergraph-skills` and
|
|
87
|
-
`metergraph-skills install --client codex
|
|
86
|
+
`metergraph-skills install --client codex` instead
|
|
88
87
|
([details](https://github.com/metergraph/cli/blob/main/python/metergraph-skills/README.md)).
|
|
89
88
|
Both write the same files and recognise each other's installs.
|
|
90
89
|
- **Model-swap skills.** `skills install` adds the workflow skills from
|
|
@@ -94,17 +93,26 @@ The CLI is built to be run by Codex, Claude Code and Cursor as well as by people
|
|
|
94
93
|
the `metergraph/skills` plugin marketplace instead.
|
|
95
94
|
|
|
96
95
|
```sh
|
|
97
|
-
npx --yes metergraph-cli
|
|
96
|
+
npx --yes metergraph-cli skills install --client claude
|
|
98
97
|
```
|
|
99
98
|
|
|
99
|
+
`--runtime` defaults to `local`. Pass `--runtime cloud` when the client runs in a
|
|
100
|
+
cloud environment with a checkout of the project.
|
|
101
|
+
|
|
100
102
|
- **Machine output.** Every command takes `--json` and then prints exactly one JSON
|
|
101
103
|
line on stdout and nothing on stderr. All results share one envelope:
|
|
102
104
|
`schema_version`, `command`, `ok`, `outcome`, `exit_code`, `data` and `error`.
|
|
103
105
|
Failures carry a fixed `error.reason` and, where useful, a `data.next_action` that
|
|
104
106
|
tells the agent what to do next.
|
|
105
107
|
- **No hidden prompts.** No command reads stdin or asks a question. `--no-browser`
|
|
106
|
-
prints the sign in URL for a person to open instead of launching a browser
|
|
107
|
-
`--json`, a step that needs approval exits
|
|
108
|
+
prints the sign in URL for a person to open instead of launching a browser. With
|
|
109
|
+
`--json`, a step that needs approval exits within seconds with 18
|
|
110
|
+
`action_required` and `data.next_action` set to
|
|
111
|
+
`{"kind": "open_url", "url": "...", "timeout_seconds": N, "message": "..."}`, so an
|
|
112
|
+
agent can show the URL to the person. A background process waits for the approval
|
|
113
|
+
until `--timeout-ms`; afterwards, run the same command again to continue from the
|
|
114
|
+
saved state
|
|
115
|
+
([details](https://github.com/metergraph/cli/blob/main/docs/reference.md#approval-from-an-agent)).
|
|
108
116
|
- **MCP.** Workspace tools for agents are served by the hosted MCP endpoint, not by this
|
|
109
117
|
CLI. See the [MCP server guide](https://www.metergraph.dev/docs/guides/mcp-server/)
|
|
110
118
|
and the [agent access guide](https://www.metergraph.dev/docs/guides/agent-access/).
|
|
@@ -112,7 +120,7 @@ The CLI is built to be run by Codex, Claude Code and Cursor as well as by people
|
|
|
112
120
|
Suggested `AGENTS.md` or `CLAUDE.md` lines for a project that uses Metergraph:
|
|
113
121
|
|
|
114
122
|
```md
|
|
115
|
-
- Metergraph CLI: `npx --yes metergraph-cli
|
|
123
|
+
- Metergraph CLI: `npx --yes metergraph-cli <command> --json`. Check exit codes;
|
|
116
124
|
follow `data.next_action` on failure.
|
|
117
125
|
- After changing instrumented code, run the app once and confirm the trace with
|
|
118
126
|
`metergraph verify` before reporting that tracing works.
|
|
@@ -141,18 +149,18 @@ writes, and full JSON examples.
|
|
|
141
149
|
|
|
142
150
|
## Exit codes
|
|
143
151
|
|
|
144
|
-
Exit codes are stable. Changing one is a breaking change. Codes 10 to
|
|
152
|
+
Exit codes are stable. Changing one is a breaking change. Codes 10 to 18 are not in
|
|
145
153
|
`0.1.0`.
|
|
146
154
|
|
|
147
155
|
| Code | Outcome | Meaning |
|
|
148
156
|
| --- | --- | --- |
|
|
149
|
-
| 0 | `ok` | Command succeeded. `doctor` does not return this in this
|
|
157
|
+
| 0 | `ok` | Command succeeded. `doctor` does not return this in this release. For `skill`, the file is in place; discovery is still pending. |
|
|
150
158
|
| 1 | `internal_error` | Unexpected failure inside the CLI. |
|
|
151
159
|
| 2 | `invalid_input` | Unknown command or argument, or an invalid option value. No request was made. |
|
|
152
160
|
| 3 | `authentication_required` | Service is reachable, healthy and supported, and requires authentication. No workspace is connected. |
|
|
153
161
|
| 4 | `connection_failed` | The origin could not be reached, the connection failed, or the probe timed out. For `verify`, the deadline expired before any valid answer arrived. A trace that is still pending is exit 11, not 4. |
|
|
154
162
|
| 5 | `unhealthy` | The service answered but reported that it is not healthy, or answered with a server error. |
|
|
155
|
-
| 6 | `unsupported` | The service answered with a response, deployment profile or status this CLI does not support, or the skill client or runtime cannot use project skill files, or sign in cannot run in this environment
|
|
163
|
+
| 6 | `unsupported` | The service answered with a response, deployment profile or status this CLI does not support, or the skill client or runtime cannot use project skill files, or sign in cannot run in this environment. Nothing was written. |
|
|
156
164
|
| 7 | `redirect_rejected` | The service answered with a redirect. Redirects are never followed. |
|
|
157
165
|
| 8 | `conflict` | The skill target is not owned by this CLI, was modified, is unsafe, is locked or needs an explicit update, or the project is bound to a different origin or workspace. Nothing was changed. |
|
|
158
166
|
| 9 | `filesystem_error` | Project or credential files could not be read or written. Partial changes were rolled back unless the message says otherwise. |
|
|
@@ -164,6 +172,7 @@ Exit codes are stable. Changing one is a breaking change. Codes 10 to 17 are not
|
|
|
164
172
|
| 15 | `permission_denied` | The service refused this read for the signed in grant, for example for a missing scope or permission. |
|
|
165
173
|
| 16 | `rate_limited` | The service asked the CLI to slow down. Nothing was retried. Try again later. |
|
|
166
174
|
| 17 | `cancelled` | A read command was interrupted before it finished. Read commands never change workspace configuration or telemetry. |
|
|
175
|
+
| 18 | `action_required` | A person must act before the command can finish; `data.next_action` says what to do. `login` and `setup` with `--json --no-browser` exit with this as soon as an approval URL exists (`next_action.kind: "open_url"`), while a background process waits for the approval until `--timeout-ms`. After approval, run the same command again to continue. |
|
|
167
176
|
|
|
168
177
|
## Security and privacy
|
|
169
178
|
|
|
@@ -194,9 +203,11 @@ report. See [SECURITY.md](https://github.com/metergraph/cli/blob/main/SECURITY.m
|
|
|
194
203
|
|
|
195
204
|
`--url ORIGIN` selects the service; the default is `https://app.metergraph.dev`. It
|
|
196
205
|
accepts a bare `https://` origin, or `http://` on `localhost`, `127.0.0.1` or `[::1]`.
|
|
197
|
-
`setup --deployment`
|
|
198
|
-
`customer-local` bundle, customer-owned cloud (`byoc`)
|
|
199
|
-
(`oss`).
|
|
206
|
+
`setup --deployment` routes to the hosted service (`managed`), the commercial
|
|
207
|
+
`customer-local` bundle, customer-owned cloud (`byoc`) or the open source server
|
|
208
|
+
(`oss`). The hosted service and released customer-local bundle are the release
|
|
209
|
+
targets; `byoc` and `oss` are not yet verified against a live deployment. See
|
|
210
|
+
[self-hosting](https://www.metergraph.dev/docs/self-host/local/) and the
|
|
200
211
|
[setup reference](https://github.com/metergraph/cli/blob/main/docs/reference.md#setup).
|
|
201
212
|
|
|
202
213
|
## Links
|
package/assets/skill/SKILL.md
CHANGED
|
@@ -36,6 +36,15 @@ package does not support. A CLI login or health probe alone does not prove
|
|
|
36
36
|
application traffic. Verification needs an exact trace or request ID from an
|
|
37
37
|
application invocation, its time window and the intended workspace.
|
|
38
38
|
|
|
39
|
+
`login` and `setup` need the person to approve in a browser on this machine. If the
|
|
40
|
+
installed CLI's help lists exit code 18 `action_required`, run them with
|
|
41
|
+
`--json --no-browser`. When a step needs approval, the command exits 18 within
|
|
42
|
+
seconds. Show `data.next_action.url` to the person and ask them to approve; a
|
|
43
|
+
background process waits for the approval. After they approve, run the same command
|
|
44
|
+
again. A new project takes three runs: sign in, ingest approval, then a final check.
|
|
45
|
+
An older CLI exits 6 `no_browser_requires_terminal` instead. Then the person has to
|
|
46
|
+
run setup in their own terminal.
|
|
47
|
+
|
|
39
48
|
Repository identity is optional for capture but keeps traces attributable when
|
|
40
49
|
several repositories share a workspace. If the installed CLI's `setup` help lists
|
|
41
50
|
`--repository`, setup records `owner/name` from the git remote in a committed
|
|
@@ -68,7 +77,7 @@ For hosted Claude Desktop on an individual account, guide the human through Cust
|
|
|
68
77
|
|
|
69
78
|
For a fresh hosted workspace, first ask whether the human already has a Metergraph account and workspace. Direct them to https://app.metergraph.dev/ and the signup, invitation or sign-in flow actually offered. The hosted UI can offer "Get a free API key" for self-service signup, but do not assume that button or a workspace invitation is available to everyone. If access is unavailable, direct them to their workspace owner or Metergraph. Confirm the workspace before creating a coding-agent key.
|
|
70
79
|
|
|
71
|
-
For the commercial customer-local bundle, direct them to https://www.metergraph.dev/docs/self-host/local/#the-customer-local-bundle and the signed public release. They need a separate pull-only registry credential from Metergraph to pull the images; the public bundle download does not grant registry access. Verify the release before installation. Local admin credentials from the bundle's private configuration provide dashboard sign-in; create a coding-agent key in that installation's Keys page. Do not substitute hosted signup or a hosted key. Do not clone the private implementation or run development seed commands.
|
|
80
|
+
For the commercial customer-local bundle, direct them to https://www.metergraph.dev/docs/self-host/local/#the-customer-local-bundle and the signed public release. They need a separate pull-only registry credential from Metergraph to pull the images; the public bundle download does not grant registry access. Verify the release before installation. Local admin credentials from the bundle's private configuration provide dashboard sign-in; create a coding-agent key in that installation's Keys page. Do not substitute hosted signup or a hosted key. Do not clone the private implementation or run development seed commands. Use `metergraph-local-bundle` to stand the bundle up when that skill is installed.
|
|
72
81
|
|
|
73
82
|
For customer-owned AWS, direct them to https://www.metergraph.dev/docs/self-host/aws/. An operator provisions the installation, identity and membership. There is no public signup. Ask for the installation's actual reachable address and use a coding-agent key from its Keys page. Its default internal load balancer is private, and its Agent Access surface excludes trace content and replay.
|
|
74
83
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "metergraph",
|
|
4
4
|
"file": "SKILL.md",
|
|
5
5
|
"source_url": "https://www.metergraph.dev/SKILL.md",
|
|
6
|
-
"sha256": "
|
|
7
|
-
"size":
|
|
8
|
-
"revision": "sha256-
|
|
6
|
+
"sha256": "e7fc4eb7a22eeb16c357bede9d0a080ad3b415aaab1d9c72f164d5350d48c4b1",
|
|
7
|
+
"size": 13664,
|
|
8
|
+
"revision": "sha256-e7fc4eb7a22e"
|
|
9
9
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 1,
|
|
3
3
|
"repository": "metergraph/skills",
|
|
4
|
-
"commit": "
|
|
4
|
+
"commit": "dda4d5f130448f12a7fb2549ed264503d8b51d8d",
|
|
5
5
|
"skills": [
|
|
6
6
|
{
|
|
7
7
|
"name": "metergraph-analyze",
|
|
@@ -33,6 +33,12 @@
|
|
|
33
33
|
"size": 3789,
|
|
34
34
|
"revision": "sha256-9ce5a2cf527e"
|
|
35
35
|
},
|
|
36
|
+
{
|
|
37
|
+
"name": "metergraph-local-bundle",
|
|
38
|
+
"sha256": "692cd8952a8a8896ec2c06091b329a7a01912e261e87d62a9458452172a7c9b0",
|
|
39
|
+
"size": 12245,
|
|
40
|
+
"revision": "sha256-692cd8952a8a"
|
|
41
|
+
},
|
|
36
42
|
{
|
|
37
43
|
"name": "metergraph-model-swap",
|
|
38
44
|
"sha256": "d77ee37f882ab1e50dc4a093e64a2c3661f4cd9bb59f184da6dccbd76fd51a18",
|
|
@@ -41,9 +47,9 @@
|
|
|
41
47
|
},
|
|
42
48
|
{
|
|
43
49
|
"name": "metergraph-onboarding",
|
|
44
|
-
"sha256": "
|
|
45
|
-
"size":
|
|
46
|
-
"revision": "sha256-
|
|
50
|
+
"sha256": "6e6f418933f37b34437035ab3171344d43f37dee3770815b19f97efb62e5f1d1",
|
|
51
|
+
"size": 10267,
|
|
52
|
+
"revision": "sha256-6e6f418933f3"
|
|
47
53
|
},
|
|
48
54
|
{
|
|
49
55
|
"name": "metergraph-report",
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: metergraph-local-bundle
|
|
3
|
+
description: Verify and start the signed Metergraph customer-local bundle on a person's own machine, preserve existing local data and secrets, then hand over to metergraph setup. Use when customer-local setup reports that the bundle_started_verified prerequisite is incomplete or when installing the commercial local bundle from a public release.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Start the signed customer-local bundle
|
|
7
|
+
|
|
8
|
+
Stand up the released bundle only on the person's own machine, then run
|
|
9
|
+
`metergraph setup`. The CLI does not provision it, and
|
|
10
|
+
`--confirm-prerequisites` records confirmation rather than installing anything.
|
|
11
|
+
|
|
12
|
+
Keep registry, admin and database secrets out of chat, command arguments and
|
|
13
|
+
output. Never read or print `.env` values, `~/.docker/config.json`,
|
|
14
|
+
`docker compose config` output or container environment from `docker inspect`.
|
|
15
|
+
Do not configure model-provider keys.
|
|
16
|
+
|
|
17
|
+
## Stop in remote runtimes
|
|
18
|
+
|
|
19
|
+
Before changing anything, determine where the agent executes. Check only
|
|
20
|
+
whether these variables exist, never their values:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
for name in SSH_CONNECTION SSH_CLIENT SSH_TTY CODESPACES GITPOD_WORKSPACE_ID CLOUD_SHELL CI; do
|
|
24
|
+
if printenv "$name" >/dev/null 2>&1; then printf '%s is set\n' "$name"; fi
|
|
25
|
+
done
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
If any is set, or this is otherwise a hosted/cloud runtime, download, verify,
|
|
29
|
+
extract and start nothing. Prefer an installed `metergraph` whose `--help`
|
|
30
|
+
lists `setup`; otherwise state that the fallback fetches the preview CLI and
|
|
31
|
+
use `npx --yes metergraph-cli@next` as the prefix. Run only:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
metergraph setup --runtime cloud --deployment customer-local --url http://localhost:8080 --workspace 11111111-1111-4111-8111-111111111111 --skip-skill --json
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Relay its `data.next_action.kind: "run_on_customer_machine"` handoff and tell
|
|
38
|
+
the person to run this skill on their own machine. The placeholder workspace is
|
|
39
|
+
intentional: the runtime check refuses before making a request or writing a
|
|
40
|
+
file. When an SSH, cloud or CI marker is set, even `--runtime local` is refused
|
|
41
|
+
with reason `run_on_local_machine` and the same handoff. Valid runtimes are
|
|
42
|
+
`local`, `cloud` and `cloud-no-shell`. Never expose localhost to the remote
|
|
43
|
+
session.
|
|
44
|
+
|
|
45
|
+
## Preflight and reuse
|
|
46
|
+
|
|
47
|
+
Ask for the coding client and bundle version. Default to the newest version at
|
|
48
|
+
https://www.metergraph.dev/docs/releases/ and record its `v`-prefixed value.
|
|
49
|
+
Record the application repository directory to return to for setup. Run:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
docker info >/dev/null
|
|
53
|
+
docker compose version
|
|
54
|
+
cosign version
|
|
55
|
+
curl -fsS http://localhost:8080/v1/deployment
|
|
56
|
+
lsof -nP -iTCP:8080 -sTCP:LISTEN
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Docker with Compose must be running and `cosign` installed. If the deployment
|
|
60
|
+
endpoint returns the `local` profile, reuse that stack and download nothing.
|
|
61
|
+
Find its bundle directory using only the Compose working-directory label:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
container_id="$(docker ps --filter publish=8080 --format '{{.ID}}' | head -n 1)"
|
|
65
|
+
bundle_dir="$(docker inspect --format '{{ index .Config.Labels "com.docker.compose.project.working_dir" }}' "$container_id")"
|
|
66
|
+
(cd "$bundle_dir" && ./bin/status)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Do not inspect container environment. Read the workspace ID from the
|
|
70
|
+
`workspace` check and skip to **Run setup**. If the directory cannot be found,
|
|
71
|
+
ask for the workspace ID from Settings. If nothing listens on 8080, continue.
|
|
72
|
+
If anything else owns it, stop and ask; never stop or reconfigure it.
|
|
73
|
+
|
|
74
|
+
Use `~/metergraph/<version>` and set
|
|
75
|
+
`bundle_dir="$HOME/metergraph/$version/local"`. If it contains `.env`, reuse
|
|
76
|
+
that existing installation: never overwrite, delete or read the file, and
|
|
77
|
+
never extract over `local/`. Continue at **Log in and start**. Otherwise check:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
docker volume inspect local_pgdata >/dev/null 2>&1
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Because the extracted directory and Compose project are `local`, an existing
|
|
84
|
+
volume is `local_pgdata`. If it exists without the matching `local/.env`, stop
|
|
85
|
+
and ask for the prior bundle/configuration. Postgres fixed its password when
|
|
86
|
+
the volume was created; a new `.env` can cause `password authentication failed`.
|
|
87
|
+
Never remove the volume, delete `.env` or use `down --volumes`.
|
|
88
|
+
|
|
89
|
+
## Download and verify
|
|
90
|
+
|
|
91
|
+
Require a fresh, empty version directory. Before creating it, tell the person
|
|
92
|
+
the directory and four public files that will be written. No account is needed.
|
|
93
|
+
Set `version` to the recorded version and run:
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
release_dir="$HOME/metergraph/$version"; mkdir -p "$release_dir"; cd "$release_dir"
|
|
97
|
+
archive="metergraph-customer-local-$version.tar.gz"
|
|
98
|
+
base="https://www.metergraph.dev/releases/$version"
|
|
99
|
+
curl -fsSLO "$base/$archive"
|
|
100
|
+
curl -fsSLO "$base/metergraph-byoc-release.json"
|
|
101
|
+
curl -fsSLO "$base/metergraph-byoc-release.json.sig"
|
|
102
|
+
curl -fsSLO "$base/metergraph-byoc-release.json.pem"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Do not list, extract or run archive contents. Verify the manifest signature
|
|
106
|
+
with exactly this command. The issuer and identity are pinned here, not read
|
|
107
|
+
from the release page, so a changed download site cannot change who must have
|
|
108
|
+
signed the release:
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
cosign verify-blob \
|
|
112
|
+
--certificate metergraph-byoc-release.json.pem \
|
|
113
|
+
--signature metergraph-byoc-release.json.sig \
|
|
114
|
+
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
|
|
115
|
+
--certificate-identity-regexp '^https://github\.com/PioneerSquareLabs/metergraph-internal/\.github/workflows/byoc-release\.yml@refs/(heads/main|tags/v.+)$' \
|
|
116
|
+
metergraph-byoc-release.json
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Never loosen or drop the issuer or identity, and never add
|
|
120
|
+
`--insecure-ignore-tlog` or a similar bypass. If the release page publishes a
|
|
121
|
+
different identity, stop and tell the person rather than using it.
|
|
122
|
+
|
|
123
|
+
If cosign fails, stop: "The manifest signature did not verify. Do not extract
|
|
124
|
+
or run this bundle." After it succeeds, check signed fields:
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
python3 - "$version" "$archive" <<'EOF'
|
|
128
|
+
import json, sys
|
|
129
|
+
version, archive = sys.argv[1:]
|
|
130
|
+
m = json.load(open("metergraph-byoc-release.json"))
|
|
131
|
+
required = ("release", "local_bundle", "local_bundle_sha256",
|
|
132
|
+
"local_image_digest", "local_analysis_image_digest")
|
|
133
|
+
missing = [key for key in required if not m.get(key)]
|
|
134
|
+
if missing: sys.exit("Signed manifest is missing: " + ", ".join(missing))
|
|
135
|
+
if m["release"] != version: sys.exit("Signed manifest release mismatch. Do not run it.")
|
|
136
|
+
if m["local_bundle"] != archive: sys.exit("Signed archive name mismatch. Do not run it.")
|
|
137
|
+
print("Signed manifest release, archive, and image digests are present.")
|
|
138
|
+
EOF
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Then run this digest check, still before any `tar` command:
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
python3 - <<'EOF'
|
|
145
|
+
import hashlib, json, sys
|
|
146
|
+
manifest = json.load(open("metergraph-byoc-release.json"))
|
|
147
|
+
name = manifest["local_bundle"]
|
|
148
|
+
actual = hashlib.sha256(open(name, "rb").read()).hexdigest()
|
|
149
|
+
if actual != manifest["local_bundle_sha256"]:
|
|
150
|
+
sys.exit(f"{name} does not match the signed manifest. Do not run it.")
|
|
151
|
+
print(f"{name} matches the signed manifest.")
|
|
152
|
+
EOF
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Stop on failure. After both checks pass, list and validate archive paths:
|
|
156
|
+
|
|
157
|
+
```sh
|
|
158
|
+
tar -tzf "$archive"
|
|
159
|
+
python3 - "$archive" <<'EOF'
|
|
160
|
+
from pathlib import PurePosixPath
|
|
161
|
+
import sys, tarfile
|
|
162
|
+
bad = []
|
|
163
|
+
with tarfile.open(sys.argv[1], "r:gz") as bundle:
|
|
164
|
+
for member in bundle.getmembers():
|
|
165
|
+
path = PurePosixPath(member.name)
|
|
166
|
+
if path.is_absolute() or not path.parts or path.parts[0] != "local" or ".." in path.parts:
|
|
167
|
+
bad.append(member.name)
|
|
168
|
+
if bad: sys.exit("Unsafe archive paths; do not extract: " + ", ".join(bad[:10]))
|
|
169
|
+
print("Every archive entry is beneath local/ with a safe relative path.")
|
|
170
|
+
EOF
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Only now tell the person extraction will create `local/`, then run:
|
|
174
|
+
|
|
175
|
+
```sh
|
|
176
|
+
tar xzf "$archive"
|
|
177
|
+
mv metergraph-byoc-release.json* local/
|
|
178
|
+
cd local
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Configure without exposing passwords
|
|
182
|
+
|
|
183
|
+
Reuse an existing `.env` unchanged. For a new installation, state that a
|
|
184
|
+
mode-0600 file with random admin/database passwords and empty provider keys
|
|
185
|
+
will be created. Confirm `local_pgdata` did not exist, then run:
|
|
186
|
+
|
|
187
|
+
```sh
|
|
188
|
+
umask 077; cp .env.example .env; chmod 600 .env
|
|
189
|
+
python3 - <<'EOF'
|
|
190
|
+
from pathlib import Path
|
|
191
|
+
import secrets
|
|
192
|
+
path = Path(".env")
|
|
193
|
+
keys = ("METERGRAPH_LOCAL_ADMIN_PASSWORD", "METERGRAPH_DB_PASSWORD")
|
|
194
|
+
values = {key: secrets.token_urlsafe(24) for key in keys}
|
|
195
|
+
lines, found = path.read_text().splitlines(), set()
|
|
196
|
+
for i, line in enumerate(lines):
|
|
197
|
+
for key in keys:
|
|
198
|
+
if line.startswith(key + "="):
|
|
199
|
+
lines[i], found = key + "=" + values[key], found | {key}
|
|
200
|
+
if found != set(keys): raise SystemExit("Password keys missing from .env.example")
|
|
201
|
+
path.write_text("\n".join(lines) + "\n")
|
|
202
|
+
print("Set random values for: " + ", ".join(keys))
|
|
203
|
+
EOF
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Do not display, source or parse `.env`. Leave `AI_GATEWAY_API_KEY`,
|
|
207
|
+
`FIREWORKS_API_KEY`, `ANTHROPIC_API_KEY`, `OPENAI_API_KEY` and `GOOGLE_API_KEY`
|
|
208
|
+
empty.
|
|
209
|
+
|
|
210
|
+
## Log in and start
|
|
211
|
+
|
|
212
|
+
Stop for the person to log in to the registry in their own terminal. They use
|
|
213
|
+
the pull-only invitation values, never chat or an agent-visible command:
|
|
214
|
+
|
|
215
|
+
```sh
|
|
216
|
+
echo "$METERGRAPH_PULL_TOKEN" | docker login ghcr.io -u "$METERGRAPH_PULL_USER" --password-stdin
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Wait for confirmation only; never read Docker's credential file. State that
|
|
220
|
+
`./bin/start` will re-verify the manifest and both image digests, pull by digest,
|
|
221
|
+
start containers, and on a new database send one synthetic demo trace labeled
|
|
222
|
+
`environment=local-demo`. Then run:
|
|
223
|
+
|
|
224
|
+
```sh
|
|
225
|
+
./bin/start
|
|
226
|
+
./bin/status
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
An image-pull failure usually means login is missing or expired. On other
|
|
230
|
+
failure, read only the log named by `./bin/start` and `./bin/status`; never read
|
|
231
|
+
rendered Compose configuration or container environment. On password
|
|
232
|
+
authentication failure with `local_pgdata`, stop and ask for its matching
|
|
233
|
+
`.env`. Never delete data. Read the workspace ID and suggested command from the
|
|
234
|
+
status JSON's `workspace` check. The service listens only on localhost:8080.
|
|
235
|
+
|
|
236
|
+
## Run setup
|
|
237
|
+
|
|
238
|
+
Return to the application repository. Prefer installed `metergraph` only when
|
|
239
|
+
its help lists `setup`; otherwise state that `npx --yes metergraph-cli@next`
|
|
240
|
+
fetches the preview CLI and use it as the prefix. Run with `--json`:
|
|
241
|
+
|
|
242
|
+
```sh
|
|
243
|
+
metergraph setup --runtime local --deployment customer-local --url http://localhost:8080 --workspace 11111111-1111-4111-8111-111111111111 --confirm-prerequisites --client <client> --json
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Replace the example UUID and client only. Setup writes an ingest key to the
|
|
247
|
+
application `.env`, installs the skill and requests Metadata access only; never
|
|
248
|
+
print that file. Setup opens the deployment's browser consent page and waits;
|
|
249
|
+
the person signs in as the local admin and completes two approvals. If the
|
|
250
|
+
browser cannot open, follow the `metergraph` skill's guidance for handing the
|
|
251
|
+
person the approval URL. Continue until `data.status` is `ok`. For
|
|
252
|
+
`operator_handoff`, relay `data.next_action.kind` (`complete_prerequisite`,
|
|
253
|
+
`run_on_customer_machine`, `connection_guide`, or other); never report a
|
|
254
|
+
handoff as success.
|
|
255
|
+
|
|
256
|
+
The agent never runs a command that reads `.env`. To see the admin login without
|
|
257
|
+
printing it, the person runs these no-output pipelines themselves, one at a
|
|
258
|
+
time, in their own terminal or in Claude Code with the `!` prefix. Fill in the
|
|
259
|
+
absolute bundle directory for them; their shell does not have `bundle_dir`:
|
|
260
|
+
|
|
261
|
+
```sh
|
|
262
|
+
sed -n 's/^METERGRAPH_LOCAL_ADMIN_EMAIL=//p' ~/metergraph/<version>/local/.env | pbcopy
|
|
263
|
+
sed -n 's/^METERGRAPH_LOCAL_ADMIN_PASSWORD=//p' ~/metergraph/<version>/local/.env | pbcopy
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
They may instead use their own editor. Never use `cat`, `grep` or any other
|
|
267
|
+
command that prints `.env`, particularly with `!`, because `!` output lands in
|
|
268
|
+
the transcript. A new configuration keeps `admin@example.com` as the email.
|
|
269
|
+
|
|
270
|
+
## Recovery and stopping
|
|
271
|
+
|
|
272
|
+
| State | Action |
|
|
273
|
+
| --- | --- |
|
|
274
|
+
| Signature, manifest, digest or path check fails | Stop; extract and run nothing |
|
|
275
|
+
| Port 8080 belongs to something else | Stop and ask; do not alter it |
|
|
276
|
+
| Registry pull is denied | Person repeats private login; never request the token |
|
|
277
|
+
| Existing `.env` | Reuse unchanged; never regenerate passwords |
|
|
278
|
+
| `local_pgdata` lacks its matching `.env` | Stop and find prior configuration; never remove it |
|
|
279
|
+
| Startup fails | Use status and only the named log; preserve secrets and data |
|
|
280
|
+
| Setup returns a handoff | Relay exact status/next action; do not report success |
|
|
281
|
+
|
|
282
|
+
To stop later, follow the bundle README's `stop`. Prefer it over `down`, which
|
|
283
|
+
loses raw payloads in local object storage. Never use `down --volumes`.
|
|
@@ -34,8 +34,9 @@ skill installation; a later installed version may offer `login`, `setup` and
|
|
|
34
34
|
https://www.metergraph.dev/docs/start/first-trace/ and its manual setup steps.
|
|
35
35
|
Do not infer that a server endpoint or source-only CLI command has shipped.
|
|
36
36
|
|
|
37
|
-
For customer-local MeterGraph, use
|
|
38
|
-
|
|
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
|
|
39
40
|
pull-only registry credential; registry access is not yet universally self-serve.
|
|
40
41
|
Use password-stdin and the local secret store for registry login. Verify the
|
|
41
42
|
archive before running its scripts. Configure the bundle's local settings,
|
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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metergraph-cli",
|
|
3
|
-
"version": "0.2.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "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": {
|
|
7
7
|
"metergraph": "bin/metergraph.js"
|
|
@@ -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
|
+
}
|