run402 4.93.2 → 4.95.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 +61 -59
- package/cli.mjs +40 -47
- package/core-dist/config.js +52 -18
- package/core-dist/control-plane-session.js +31 -6
- package/core-dist/keystore.js +7 -7
- package/core-dist/profiles.js +9 -7
- package/core-dist/{allowance-auth.js → wallet-auth.js} +15 -15
- package/{sdk/core-dist/allowance.js → core-dist/wallet.js} +20 -20
- package/{sdk/core-dist/write-auth-session.js → core-dist/write-approvals.js} +19 -19
- package/gitvault-surface.json +2 -2
- package/lib/admin.mjs +6 -6
- package/lib/agent.mjs +20 -18
- package/lib/ai.mjs +5 -5
- package/lib/approve.mjs +81 -0
- package/lib/apps.mjs +12 -12
- package/lib/archives.mjs +371 -8
- package/lib/argparse.mjs +5 -5
- package/lib/assets.mjs +14 -14
- package/lib/auth.mjs +34 -34
- package/lib/billing.mjs +6 -6
- package/lib/branches.mjs +7 -7
- package/lib/buzz-doctor.mjs +2 -2
- package/lib/buzz-notifications.mjs +5 -5
- package/lib/buzz.mjs +3 -3
- package/lib/buzz.test.mjs +1 -1
- package/lib/cache.mjs +1 -1
- package/lib/cdn.mjs +2 -2
- package/lib/ci.mjs +12 -12
- package/lib/claims.mjs +2 -2
- package/lib/cold-start.mjs +21 -21
- package/lib/command-manifest.mjs +55 -70
- package/lib/config.mjs +28 -28
- package/lib/contacts.mjs +16 -16
- package/lib/contracts.mjs +12 -12
- package/lib/credentials.mjs +33 -33
- package/lib/daemon-path.mjs +1 -1
- package/lib/delegates.mjs +14 -14
- package/lib/deliveries.mjs +6 -6
- package/lib/deploy-rehearse-discovery.test.mjs +7 -7
- package/lib/deploy-v2.mjs +241 -193
- package/lib/deploy.mjs +21 -53
- package/lib/dev.mjs +3 -3
- package/lib/doctor-source-scan.mjs +1 -1
- package/lib/doctor.mjs +63 -63
- package/lib/domains.mjs +27 -27
- package/lib/email.mjs +38 -38
- package/lib/errors.mjs +40 -24
- package/lib/escalations.mjs +5 -5
- package/lib/events.mjs +32 -25
- package/lib/feedback.mjs +12 -12
- package/lib/functions.mjs +34 -34
- package/lib/gitvault-daemon.mjs +1 -1
- package/lib/gitvault-scaffold.mjs +2 -2
- package/lib/gitvault.mjs +2 -2
- package/lib/grants.mjs +7 -7
- package/lib/image.mjs +10 -10
- package/lib/init.mjs +69 -69
- package/lib/jobs.mjs +15 -15
- package/lib/lightning-wallet.mjs +28 -28
- package/lib/live.mjs +1 -1
- package/lib/login.mjs +207 -0
- package/lib/logout.mjs +53 -0
- package/lib/logs.mjs +8 -8
- package/lib/messages.mjs +5 -5
- package/lib/next-actions.mjs +16 -16
- package/lib/org-context.mjs +13 -13
- package/lib/{org.mjs → orgs.mjs} +215 -167
- package/lib/pay.mjs +1 -1
- package/lib/projects.mjs +70 -84
- package/lib/redeem.mjs +7 -8
- package/lib/remote-helper-session.mjs +12 -12
- package/lib/repos.mjs +83 -83
- package/lib/rooms.mjs +105 -69
- package/lib/sdk-errors.mjs +10 -5
- package/lib/sdk.mjs +3 -3
- package/lib/secrets.mjs +13 -13
- package/lib/service.mjs +5 -5
- package/lib/session-proofs.mjs +21 -0
- package/lib/sign-in.mjs +296 -0
- package/lib/sites.mjs +22 -22
- package/lib/snapshots.mjs +10 -10
- package/lib/stats.mjs +2 -2
- package/lib/status.mjs +25 -25
- package/lib/subdomains.mjs +30 -30
- package/lib/subscriptions.mjs +10 -10
- package/lib/tier.mjs +29 -22
- package/lib/transfer.mjs +59 -86
- package/lib/up.mjs +14 -14
- package/lib/wallet-context.mjs +4 -4
- package/lib/wallet-context.test.mjs +3 -3
- package/lib/wallets.mjs +197 -32
- package/lib/webhook-secret.mjs +6 -6
- package/lib/webhooks.mjs +8 -8
- package/lib/whoami.mjs +102 -0
- package/package.json +1 -1
- package/sdk/core-dist/config.js +52 -18
- package/sdk/core-dist/control-plane-session.js +31 -6
- package/sdk/core-dist/keystore.js +7 -7
- package/sdk/core-dist/profiles.js +9 -7
- package/sdk/core-dist/{allowance-auth.js → wallet-auth.js} +15 -15
- package/{core-dist/allowance.js → sdk/core-dist/wallet.js} +20 -20
- package/{core-dist/write-auth-session.js → sdk/core-dist/write-approvals.js} +19 -19
- package/sdk/dist/actions.d.ts +4 -4
- package/sdk/dist/actions.d.ts.map +1 -1
- package/sdk/dist/app-up.d.ts +2 -2
- package/sdk/dist/app-up.d.ts.map +1 -1
- package/sdk/dist/config.d.ts +1 -2
- package/sdk/dist/config.d.ts.map +1 -1
- package/sdk/dist/config.js.map +1 -1
- package/sdk/dist/control-plane-credentials.d.ts +8 -8
- package/sdk/dist/control-plane-credentials.js +7 -7
- package/sdk/dist/credentials.d.ts +20 -20
- package/sdk/dist/credentials.d.ts.map +1 -1
- package/sdk/dist/credentials.js +1 -1
- package/sdk/dist/errors.d.ts +26 -28
- package/sdk/dist/errors.d.ts.map +1 -1
- package/sdk/dist/errors.js +35 -38
- package/sdk/dist/errors.js.map +1 -1
- package/sdk/dist/index.d.ts +29 -16
- package/sdk/dist/index.d.ts.map +1 -1
- package/sdk/dist/index.js +30 -17
- package/sdk/dist/index.js.map +1 -1
- package/sdk/dist/kernel.d.ts +1 -1
- package/sdk/dist/kernel.d.ts.map +1 -1
- package/sdk/dist/kernel.js +10 -9
- package/sdk/dist/kernel.js.map +1 -1
- package/sdk/dist/namespaces/admin.d.ts +52 -121
- package/sdk/dist/namespaces/admin.d.ts.map +1 -1
- package/sdk/dist/namespaces/admin.js +41 -45
- package/sdk/dist/namespaces/admin.js.map +1 -1
- package/sdk/dist/namespaces/agent.d.ts +2 -2
- package/sdk/dist/namespaces/agent.d.ts.map +1 -1
- package/sdk/dist/namespaces/agent.js +2 -2
- package/sdk/dist/namespaces/agent.js.map +1 -1
- package/sdk/dist/namespaces/ai.d.ts +2 -2
- package/sdk/dist/namespaces/ai.js +3 -3
- package/sdk/dist/namespaces/ai.js.map +1 -1
- package/sdk/dist/namespaces/assets.js +1 -1
- package/sdk/dist/namespaces/assets.js.map +1 -1
- package/sdk/dist/namespaces/billing.d.ts +6 -3
- package/sdk/dist/namespaces/billing.d.ts.map +1 -1
- package/sdk/dist/namespaces/billing.js.map +1 -1
- package/sdk/dist/namespaces/buzz-notifications.types.d.ts +1 -1
- package/sdk/dist/namespaces/buzz.d.ts +2 -2
- package/sdk/dist/namespaces/buzz.d.ts.map +1 -1
- package/sdk/dist/namespaces/buzz.js +2 -2
- package/sdk/dist/namespaces/buzz.js.map +1 -1
- package/sdk/dist/namespaces/buzz.types.d.ts +2 -2
- package/sdk/dist/namespaces/buzz.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/ci.js +3 -3
- package/sdk/dist/namespaces/ci.js.map +1 -1
- package/sdk/dist/namespaces/credentials.types.d.ts +1 -1
- package/sdk/dist/namespaces/deploy.d.ts +1 -1
- package/sdk/dist/namespaces/deploy.d.ts.map +1 -1
- package/sdk/dist/namespaces/deploy.js +31 -32
- package/sdk/dist/namespaces/deploy.js.map +1 -1
- package/sdk/dist/namespaces/deploy.types.d.ts +6 -6
- package/sdk/dist/namespaces/deploy.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/domains.d.ts +10 -2
- package/sdk/dist/namespaces/domains.d.ts.map +1 -1
- package/sdk/dist/namespaces/domains.js +10 -5
- package/sdk/dist/namespaces/domains.js.map +1 -1
- package/sdk/dist/namespaces/errors.js +1 -1
- package/sdk/dist/namespaces/escalations.d.ts +1 -1
- package/sdk/dist/namespaces/escalations.js +1 -1
- package/sdk/dist/namespaces/escalations.types.d.ts +2 -2
- package/sdk/dist/namespaces/escalations.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/events.types.d.ts +1 -1
- package/sdk/dist/namespaces/functions.d.ts +1 -1
- package/sdk/dist/namespaces/functions.js +2 -2
- package/sdk/dist/namespaces/functions.js.map +1 -1
- package/sdk/dist/namespaces/gitvault.crypto.d.ts +1 -1
- package/sdk/dist/namespaces/gitvault.d.ts +26 -26
- package/sdk/dist/namespaces/gitvault.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.js +113 -113
- package/sdk/dist/namespaces/gitvault.js.map +1 -1
- package/sdk/dist/namespaces/me.d.ts +120 -0
- package/sdk/dist/namespaces/me.d.ts.map +1 -0
- package/sdk/dist/namespaces/me.js +34 -0
- package/sdk/dist/namespaces/me.js.map +1 -0
- package/sdk/dist/namespaces/org.d.ts +108 -6
- package/sdk/dist/namespaces/org.d.ts.map +1 -1
- package/sdk/dist/namespaces/org.js +63 -7
- package/sdk/dist/namespaces/org.js.map +1 -1
- package/sdk/dist/namespaces/org.types.d.ts +29 -9
- package/sdk/dist/namespaces/org.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/pay.js +3 -3
- package/sdk/dist/namespaces/pay.js.map +1 -1
- package/sdk/dist/namespaces/projects.d.ts +9 -9
- package/sdk/dist/namespaces/projects.js +15 -15
- package/sdk/dist/namespaces/projects.js.map +1 -1
- package/sdk/dist/namespaces/projects.types.d.ts +28 -12
- package/sdk/dist/namespaces/projects.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/rooms.d.ts +7 -7
- package/sdk/dist/namespaces/rooms.js +17 -17
- package/sdk/dist/namespaces/rooms.js.map +1 -1
- package/sdk/dist/namespaces/rooms.types.d.ts +9 -9
- package/sdk/dist/namespaces/rooms.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/service.d.ts +1 -1
- package/sdk/dist/namespaces/service.js +1 -1
- package/sdk/dist/namespaces/{operator-session.d.ts → session.d.ts} +135 -51
- package/sdk/dist/namespaces/session.d.ts.map +1 -0
- package/sdk/dist/namespaces/{operator-session.js → session.js} +118 -30
- package/sdk/dist/namespaces/session.js.map +1 -0
- package/sdk/dist/namespaces/subdomains.d.ts +5 -5
- package/sdk/dist/namespaces/subdomains.d.ts.map +1 -1
- package/sdk/dist/namespaces/subdomains.js +6 -6
- package/sdk/dist/namespaces/subdomains.js.map +1 -1
- package/sdk/dist/namespaces/tier.d.ts +44 -21
- package/sdk/dist/namespaces/tier.d.ts.map +1 -1
- package/sdk/dist/namespaces/tier.js +7 -7
- package/sdk/dist/namespaces/tier.js.map +1 -1
- package/sdk/dist/namespaces/transfers.d.ts +119 -85
- package/sdk/dist/namespaces/transfers.d.ts.map +1 -1
- package/sdk/dist/namespaces/transfers.js +31 -38
- package/sdk/dist/namespaces/transfers.js.map +1 -1
- package/sdk/dist/namespaces/vouchers.d.ts +9 -10
- package/sdk/dist/namespaces/vouchers.d.ts.map +1 -1
- package/sdk/dist/namespaces/vouchers.js +4 -5
- package/sdk/dist/namespaces/vouchers.js.map +1 -1
- package/sdk/dist/namespaces/wallets.d.ts +72 -3
- package/sdk/dist/namespaces/wallets.d.ts.map +1 -1
- package/sdk/dist/namespaces/wallets.js +141 -3
- package/sdk/dist/namespaces/wallets.js.map +1 -1
- package/sdk/dist/namespaces/write-approval.d.ts +101 -0
- package/sdk/dist/namespaces/write-approval.d.ts.map +1 -0
- package/sdk/dist/namespaces/write-approval.js +62 -0
- package/sdk/dist/namespaces/write-approval.js.map +1 -0
- package/sdk/dist/node/actions-node.d.ts +3 -3
- package/sdk/dist/node/actions-node.d.ts.map +1 -1
- package/sdk/dist/node/actions-node.js +44 -44
- package/sdk/dist/node/actions-node.js.map +1 -1
- package/sdk/dist/node/assets-node.js +6 -6
- package/sdk/dist/node/assets-node.js.map +1 -1
- package/sdk/dist/node/{bearer-claim-key.d.ts → bearer-redeem-key.d.ts} +12 -12
- package/sdk/dist/node/bearer-redeem-key.d.ts.map +1 -0
- package/sdk/dist/node/{bearer-claim-key.js → bearer-redeem-key.js} +21 -21
- package/sdk/dist/node/bearer-redeem-key.js.map +1 -0
- package/sdk/dist/node/ci.d.ts +1 -1
- package/sdk/dist/node/ci.d.ts.map +1 -1
- package/sdk/dist/node/ci.js +6 -6
- package/sdk/dist/node/ci.js.map +1 -1
- package/sdk/dist/node/credentials.d.ts +18 -17
- package/sdk/dist/node/credentials.d.ts.map +1 -1
- package/sdk/dist/node/credentials.js +29 -28
- package/sdk/dist/node/credentials.js.map +1 -1
- package/sdk/dist/node/deploy-manifest.d.ts +3 -5
- package/sdk/dist/node/deploy-manifest.d.ts.map +1 -1
- package/sdk/dist/node/deploy-manifest.js +3 -12
- package/sdk/dist/node/deploy-manifest.js.map +1 -1
- package/sdk/dist/node/funding-recovery.d.ts +1 -1
- package/sdk/dist/node/funding-recovery.d.ts.map +1 -1
- package/sdk/dist/node/funding-recovery.js +1 -1
- package/sdk/dist/node/funding-recovery.js.map +1 -1
- package/sdk/dist/node/gitvault-apply.d.ts +1 -1
- package/sdk/dist/node/gitvault-apply.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-apply.js +1 -1
- package/sdk/dist/node/gitvault-apply.js.map +1 -1
- package/sdk/dist/node/gitvault-handoff.d.ts +22 -22
- package/sdk/dist/node/gitvault-handoff.d.ts.map +1 -1
- package/sdk/dist/node/gitvault-handoff.js +45 -45
- package/sdk/dist/node/gitvault-handoff.js.map +1 -1
- package/sdk/dist/node/gitvault-prewarm.js +7 -7
- package/sdk/dist/node/gitvault-prewarm.js.map +1 -1
- package/sdk/dist/node/gitvault-restore.d.ts +1 -1
- package/sdk/dist/node/gitvault-restore.js +1 -1
- package/sdk/dist/node/index.d.ts +16 -16
- package/sdk/dist/node/index.d.ts.map +1 -1
- package/sdk/dist/node/index.js +15 -15
- package/sdk/dist/node/index.js.map +1 -1
- package/sdk/dist/node/manifest-export.js +2 -2
- package/sdk/dist/node/manifest-export.js.map +1 -1
- package/sdk/dist/node/org-adopt.d.ts +49 -0
- package/sdk/dist/node/org-adopt.d.ts.map +1 -0
- package/sdk/dist/node/org-adopt.js +77 -0
- package/sdk/dist/node/org-adopt.js.map +1 -0
- package/sdk/dist/node/paid-fetch.d.ts +7 -7
- package/sdk/dist/node/paid-fetch.d.ts.map +1 -1
- package/sdk/dist/node/paid-fetch.js +54 -54
- package/sdk/dist/node/paid-fetch.js.map +1 -1
- package/sdk/dist/node/preflight.d.ts +1 -1
- package/sdk/dist/node/preflight.d.ts.map +1 -1
- package/sdk/dist/node/preflight.js +1 -1
- package/sdk/dist/node/preflight.js.map +1 -1
- package/sdk/dist/node/sites-node.js +1 -1
- package/sdk/dist/node/sites-node.js.map +1 -1
- package/sdk/dist/node/source-scan.d.ts +1 -1
- package/sdk/dist/node/source-scan.js +2 -2
- package/sdk/dist/node/source-scan.js.map +1 -1
- package/sdk/dist/scoped.d.ts +11 -11
- package/sdk/dist/scoped.d.ts.map +1 -1
- package/sdk/dist/scoped.js +5 -5
- package/sdk/dist/scoped.js.map +1 -1
- package/core-dist/operator-session.js +0 -154
- package/lib/allowance.mjs +0 -355
- package/lib/cloud.mjs +0 -256
- package/lib/core.mjs +0 -123
- package/lib/operator-proofs.mjs +0 -22
- package/lib/operator.mjs +0 -771
- package/sdk/core-dist/operator-session.js +0 -154
- package/sdk/dist/namespaces/allowance.d.ts +0 -79
- package/sdk/dist/namespaces/allowance.d.ts.map +0 -1
- package/sdk/dist/namespaces/allowance.js +0 -149
- package/sdk/dist/namespaces/allowance.js.map +0 -1
- package/sdk/dist/namespaces/operator-session.d.ts.map +0 -1
- package/sdk/dist/namespaces/operator-session.js.map +0 -1
- package/sdk/dist/namespaces/operator.d.ts +0 -359
- package/sdk/dist/namespaces/operator.d.ts.map +0 -1
- package/sdk/dist/namespaces/operator.js +0 -272
- package/sdk/dist/namespaces/operator.js.map +0 -1
- package/sdk/dist/node/bearer-claim-key.d.ts.map +0 -1
- package/sdk/dist/node/bearer-claim-key.js.map +0 -1
- package/sdk/dist/node/operator-claim.d.ts +0 -49
- package/sdk/dist/node/operator-claim.d.ts.map +0 -1
- package/sdk/dist/node/operator-claim.js +0 -77
- package/sdk/dist/node/operator-claim.js.map +0 -1
package/lib/sign-in.mjs
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared machinery for a person's command-line sign-in: `run402 login`,
|
|
3
|
+
* `run402 logout`, `run402 whoami`, and `run402 approve`.
|
|
4
|
+
*
|
|
5
|
+
* One sign-in session (wire token class `control_plane_session`), bound to the
|
|
6
|
+
* principal and graded by how it was minted:
|
|
7
|
+
* - `run402 login` — loopback PKCE (RFC 8252): the browser runs the passkey
|
|
8
|
+
* ceremony and redirects to a 127.0.0.1 listener. Grade `loopback`: full and
|
|
9
|
+
* step-up-able.
|
|
10
|
+
* - `run402 login --device` — RFC 8628 device authorization for a terminal
|
|
11
|
+
* with no browser. Grade `device`: read-only (every mutation answers
|
|
12
|
+
* `SESSION_READ_ONLY`).
|
|
13
|
+
*
|
|
14
|
+
* Provisioning, deploying, and writing secrets without a wallet additionally
|
|
15
|
+
* need a **write approval** — passkey-signed, scoped to one (action, target),
|
|
16
|
+
* minted by `run402 approve` and sent as `X-Run402-Write-Approval`. A write
|
|
17
|
+
* approval never satisfies step-up.
|
|
18
|
+
*
|
|
19
|
+
* The session and approvals are cached at the BASE config dir (principal-
|
|
20
|
+
* scoped, shared across named wallets), mode 0600. Agent-first: JSON to stdout;
|
|
21
|
+
* the URL and code a person must open go to stderr.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { spawn } from "node:child_process";
|
|
25
|
+
import { createServer } from "node:http";
|
|
26
|
+
import { randomBytes, createHash } from "node:crypto";
|
|
27
|
+
import { fail, reportSdkError } from "./sdk-errors.mjs";
|
|
28
|
+
import { getSdk } from "./sdk.mjs";
|
|
29
|
+
import { isWriteApprovalRequired } from "#sdk/node";
|
|
30
|
+
import { loadLiveControlPlaneSession } from "../core-dist/control-plane-session.js";
|
|
31
|
+
import {
|
|
32
|
+
saveApproval,
|
|
33
|
+
hashControlPlaneSession,
|
|
34
|
+
approvalFromTokenResponse,
|
|
35
|
+
} from "../core-dist/write-approvals.js";
|
|
36
|
+
import { getApiBase } from "../core-dist/config.js";
|
|
37
|
+
|
|
38
|
+
/** Gateway write-approval capabilities → required target scope. */
|
|
39
|
+
export const APPROVAL_ACTIONS = {
|
|
40
|
+
"org.project.create": "org",
|
|
41
|
+
"project.deploy": "project",
|
|
42
|
+
"project.secret.write": "project",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** True when `url` is same-origin as the API base (confirm_url validation). */
|
|
46
|
+
function sameApiOrigin(url) {
|
|
47
|
+
try {
|
|
48
|
+
return new URL(url).origin === new URL(getApiBase()).origin;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Best-effort, cross-platform browser open. Never throws. */
|
|
55
|
+
export function openBrowser(url) {
|
|
56
|
+
try {
|
|
57
|
+
let cmd;
|
|
58
|
+
let cmdArgs;
|
|
59
|
+
if (process.platform === "darwin") {
|
|
60
|
+
cmd = "open";
|
|
61
|
+
cmdArgs = [url];
|
|
62
|
+
} else if (process.platform === "win32") {
|
|
63
|
+
cmd = "cmd";
|
|
64
|
+
cmdArgs = ["/c", "start", "", url];
|
|
65
|
+
} else {
|
|
66
|
+
cmd = "xdg-open";
|
|
67
|
+
cmdArgs = [url];
|
|
68
|
+
}
|
|
69
|
+
const child = spawn(cmd, cmdArgs, { stdio: "ignore", detached: true });
|
|
70
|
+
child.on("error", () => {}); // ignore: the URL is also printed to stderr
|
|
71
|
+
child.unref();
|
|
72
|
+
} catch {
|
|
73
|
+
// Best-effort only — the person can always copy the printed URL.
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Output shape for a cached sign-in session. NEVER includes the token. */
|
|
78
|
+
export function sessionView(session, nowMs = Date.now()) {
|
|
79
|
+
return {
|
|
80
|
+
logged_in: true,
|
|
81
|
+
grade: session.grade,
|
|
82
|
+
read_only: session.grade === "device",
|
|
83
|
+
principal_id: session.principal_id || null,
|
|
84
|
+
amr: session.amr,
|
|
85
|
+
expires_at: new Date(session.expires_at).toISOString(),
|
|
86
|
+
expires_in_seconds: Math.max(0, Math.round((session.expires_at - nowMs) / 1000)),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const base64url = (buf) => buf.toString("base64url");
|
|
91
|
+
|
|
92
|
+
/** Generate PKCE (S256) + CSRF state + replay nonce for the loopback flow. */
|
|
93
|
+
export function pkce() {
|
|
94
|
+
const codeVerifier = base64url(randomBytes(32));
|
|
95
|
+
const codeChallenge = base64url(createHash("sha256").update(codeVerifier).digest());
|
|
96
|
+
return { codeVerifier, codeChallenge, state: base64url(randomBytes(16)), nonce: base64url(randomBytes(16)) };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Start a 127.0.0.1 loopback server (RFC 8252) that captures exactly one
|
|
101
|
+
* redirect. Returns the bound port (via `ready`), a promise for the auth code,
|
|
102
|
+
* and a `close()`. State is validated here to reject CSRF before exchange.
|
|
103
|
+
*/
|
|
104
|
+
export function startLoopbackServer({ expectedState, timeoutMs }) {
|
|
105
|
+
let resolveCode;
|
|
106
|
+
let rejectCode;
|
|
107
|
+
const codePromise = new Promise((res, rej) => {
|
|
108
|
+
resolveCode = res;
|
|
109
|
+
rejectCode = rej;
|
|
110
|
+
});
|
|
111
|
+
let timer;
|
|
112
|
+
// Track live sockets: server.close() alone stops NEW connections but leaves
|
|
113
|
+
// the browser's keep-alive socket open, which keeps Node's event loop alive
|
|
114
|
+
// and hangs the CLI after a successful login. close() must destroy them.
|
|
115
|
+
const sockets = new Set();
|
|
116
|
+
const server = createServer((req, res) => {
|
|
117
|
+
let u;
|
|
118
|
+
try {
|
|
119
|
+
u = new URL(req.url, "http://127.0.0.1");
|
|
120
|
+
} catch {
|
|
121
|
+
res.writeHead(400).end("bad request");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (u.pathname !== "/callback") {
|
|
125
|
+
res.writeHead(404).end("not found");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const code = u.searchParams.get("code");
|
|
129
|
+
const gotState = u.searchParams.get("state");
|
|
130
|
+
const errParam = u.searchParams.get("error");
|
|
131
|
+
// `connection: close` so the browser does not keep the socket alive.
|
|
132
|
+
res.writeHead(200, { "content-type": "text/html", connection: "close" });
|
|
133
|
+
res.end(
|
|
134
|
+
"<!doctype html><html><body style=\"font-family:system-ui;padding:3rem\">" +
|
|
135
|
+
"<h2>run402 - you're signed in.</h2><p>You can close this window and return to your terminal.</p></body></html>",
|
|
136
|
+
);
|
|
137
|
+
// Do NOT tear down here — let the response flush. The caller calls close()
|
|
138
|
+
// once it has the code (or on any failure path).
|
|
139
|
+
if (errParam) rejectCode(new Error(`authorization error: ${errParam}`));
|
|
140
|
+
else if (!code) rejectCode(new Error("no authorization code on the loopback redirect"));
|
|
141
|
+
else if (gotState !== expectedState) rejectCode(new Error("state mismatch on the loopback redirect (possible CSRF) - aborted"));
|
|
142
|
+
else resolveCode(code);
|
|
143
|
+
});
|
|
144
|
+
server.on("connection", (s) => {
|
|
145
|
+
sockets.add(s);
|
|
146
|
+
s.once("close", () => sockets.delete(s));
|
|
147
|
+
});
|
|
148
|
+
function close() {
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
for (const s of sockets) {
|
|
151
|
+
try {
|
|
152
|
+
s.destroy();
|
|
153
|
+
} catch {
|
|
154
|
+
// already gone
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
server.close();
|
|
159
|
+
} catch {
|
|
160
|
+
// already closing
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
timer = setTimeout(() => {
|
|
164
|
+
close();
|
|
165
|
+
rejectCode(new Error("timed out waiting for browser approval"));
|
|
166
|
+
}, timeoutMs);
|
|
167
|
+
server.on("error", (e) => {
|
|
168
|
+
close();
|
|
169
|
+
rejectCode(e);
|
|
170
|
+
});
|
|
171
|
+
const ready = new Promise((res, rej) => {
|
|
172
|
+
server.once("error", rej);
|
|
173
|
+
server.listen(0, "127.0.0.1", () => res(server.address().port));
|
|
174
|
+
});
|
|
175
|
+
return { ready, codePromise, close };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Run the write-approval ceremony (loopback + passkey) and cache the minted
|
|
180
|
+
* approval. Requires a live, write-capable sign-in session. Returns the cached
|
|
181
|
+
* approval (or exits non-zero via fail/reportSdkError). Reused by
|
|
182
|
+
* `run402 approve` AND the TTY auto-approve path in provision/deploy.
|
|
183
|
+
*/
|
|
184
|
+
export async function mintApproval({ action, orgId, projectId, noOpen = false }) {
|
|
185
|
+
const cp = loadLiveControlPlaneSession();
|
|
186
|
+
if (!cp) {
|
|
187
|
+
return fail({
|
|
188
|
+
code: "LOGIN_REQUIRED",
|
|
189
|
+
message: "No sign-in session. Run 'run402 login' first, then approve.",
|
|
190
|
+
hint: "A write approval is minted on top of your sign-in session.",
|
|
191
|
+
next_actions: [{ type: "authenticate", command: "run402 login" }],
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
if (cp.grade === "device") {
|
|
195
|
+
return fail({
|
|
196
|
+
code: "SESSION_READ_ONLY",
|
|
197
|
+
message: "This sign-in session came from 'run402 login --device' and can only read; it cannot mint a write approval.",
|
|
198
|
+
hint: "Run 'run402 login' (the browser passkey sign-in), then approve.",
|
|
199
|
+
next_actions: [{ type: "authenticate", command: "run402 login" }],
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
const sdk = getSdk();
|
|
203
|
+
const { codeVerifier, codeChallenge, state } = pkce();
|
|
204
|
+
const { ready, codePromise, close } = startLoopbackServer({ expectedState: state, timeoutMs: 300_000 });
|
|
205
|
+
|
|
206
|
+
let port;
|
|
207
|
+
try {
|
|
208
|
+
port = await ready;
|
|
209
|
+
} catch (err) {
|
|
210
|
+
close();
|
|
211
|
+
return fail({ code: "APPROVE_FAILED", message: `Could not start the loopback server: ${err.message}` });
|
|
212
|
+
}
|
|
213
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
214
|
+
|
|
215
|
+
let challenge;
|
|
216
|
+
try {
|
|
217
|
+
challenge = await sdk.writeApproval.requestChallenge({
|
|
218
|
+
action,
|
|
219
|
+
orgId: orgId ?? undefined,
|
|
220
|
+
projectId: projectId ?? undefined,
|
|
221
|
+
cliRedirectUri: redirectUri,
|
|
222
|
+
codeChallenge,
|
|
223
|
+
state,
|
|
224
|
+
token: cp.control_plane_session_token,
|
|
225
|
+
});
|
|
226
|
+
} catch (err) {
|
|
227
|
+
close();
|
|
228
|
+
return reportSdkError(err);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const confirmUrl = challenge?.confirm_url;
|
|
232
|
+
if (!confirmUrl || !sameApiOrigin(confirmUrl)) {
|
|
233
|
+
close();
|
|
234
|
+
return fail({
|
|
235
|
+
code: "APPROVE_BAD_CONFIRM_URL",
|
|
236
|
+
message: "The gateway returned a confirm_url that is not same-origin as the API base; aborting.",
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
const targetLabel = orgId ? `org ${orgId}` : projectId ? `project ${projectId}` : "(no target)";
|
|
240
|
+
process.stderr.write(
|
|
241
|
+
`\nTo approve '${action}' for ${targetLabel}, open and approve with your passkey:\n ${confirmUrl}\n\n`,
|
|
242
|
+
);
|
|
243
|
+
if (!noOpen && process.stderr.isTTY) {
|
|
244
|
+
openBrowser(confirmUrl);
|
|
245
|
+
process.stderr.write("(opening your browser…)\n\n");
|
|
246
|
+
}
|
|
247
|
+
process.stderr.write("Waiting for passkey approval…\n");
|
|
248
|
+
|
|
249
|
+
let code;
|
|
250
|
+
try {
|
|
251
|
+
code = await codePromise;
|
|
252
|
+
} catch (err) {
|
|
253
|
+
close();
|
|
254
|
+
return fail({ code: "APPROVE_FAILED", message: err.message, hint: "Run 'run402 approve' to try again." });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let token;
|
|
258
|
+
try {
|
|
259
|
+
token = await sdk.writeApproval.exchangeClaimCode({ code, codeVerifier, state });
|
|
260
|
+
} catch (err) {
|
|
261
|
+
close();
|
|
262
|
+
return reportSdkError(err);
|
|
263
|
+
}
|
|
264
|
+
close();
|
|
265
|
+
|
|
266
|
+
const approval = approvalFromTokenResponse(token, {
|
|
267
|
+
action,
|
|
268
|
+
target: { org_id: orgId ?? undefined, project_id: projectId ?? undefined },
|
|
269
|
+
apiOrigin: new URL(getApiBase()).origin,
|
|
270
|
+
controlPlaneSessionHash: hashControlPlaneSession(cp.control_plane_session_token),
|
|
271
|
+
controlPlanePrincipalId: cp.principal_id || "",
|
|
272
|
+
});
|
|
273
|
+
saveApproval(approval);
|
|
274
|
+
process.stderr.write(`\nApproved '${action}' for ${targetLabel}.\n`);
|
|
275
|
+
return approval;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Run a write action; on `WriteApprovalRequiredError` AND an interactive TTY
|
|
280
|
+
* (CLI), run the scoped approval ceremony and retry once. In MCP / CI /
|
|
281
|
+
* non-TTY the typed error is rethrown unchanged — the agent relays the resolved
|
|
282
|
+
* `run402 approve …` command rather than a browser opening unexpectedly.
|
|
283
|
+
*/
|
|
284
|
+
export async function withAutoApprove(fn) {
|
|
285
|
+
try {
|
|
286
|
+
return await fn();
|
|
287
|
+
} catch (err) {
|
|
288
|
+
if (!isWriteApprovalRequired(err) || !process.stderr.isTTY) throw err;
|
|
289
|
+
const action = err.capability;
|
|
290
|
+
const target = err.target || {};
|
|
291
|
+
if (!action) throw err;
|
|
292
|
+
process.stderr.write(`\nThis '${action}' needs a one-time write approval. Opening the browser to approve…\n`);
|
|
293
|
+
await mintApproval({ action, orgId: target.org_id ?? undefined, projectId: target.project_id ?? undefined });
|
|
294
|
+
return await fn(); // retry once with the now-cached approval
|
|
295
|
+
}
|
|
296
|
+
}
|
package/lib/sites.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs"
|
|
|
2
2
|
import { tmpdir } from "os";
|
|
3
3
|
import { dirname, join, resolve } from "path";
|
|
4
4
|
import { fileSetFromDir, delegateTokenFromEnv } from "#sdk/node";
|
|
5
|
-
import {
|
|
5
|
+
import { walletAuthHeaders, resolveProjectId, updateProject } from "./config.mjs";
|
|
6
6
|
import { resolveFilePathsInManifest } from "./manifest.mjs";
|
|
7
7
|
import { getSdk } from "./sdk.mjs";
|
|
8
8
|
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
@@ -13,8 +13,8 @@ const SMALL_DIR_THRESHOLD = 5;
|
|
|
13
13
|
const HELP = `run402 sites - Deploy and manage static sites
|
|
14
14
|
|
|
15
15
|
Usage:
|
|
16
|
-
run402 sites deploy --manifest <file> [--project <
|
|
17
|
-
run402 sites deploy-dir <path> --project <
|
|
16
|
+
run402 sites deploy --manifest <file> [--project <project_id>]
|
|
17
|
+
run402 sites deploy-dir <path> --project <project_id>
|
|
18
18
|
cat manifest.json | run402 sites deploy
|
|
19
19
|
|
|
20
20
|
Subcommands:
|
|
@@ -23,12 +23,12 @@ Subcommands:
|
|
|
23
23
|
|
|
24
24
|
Options (deploy):
|
|
25
25
|
--manifest <file> Path to manifest JSON file (or read from stdin)
|
|
26
|
-
--project <
|
|
26
|
+
--project <project_id> Project ID (defaults to active project)
|
|
27
27
|
--help, -h Show this help message
|
|
28
28
|
|
|
29
29
|
Options (deploy-dir):
|
|
30
30
|
<path> Positional: local directory to deploy
|
|
31
|
-
--project <
|
|
31
|
+
--project <project_id> Project ID (defaults to active project)
|
|
32
32
|
--quiet Suppress progress events on stderr
|
|
33
33
|
--dry-run Plan-only: print the diff envelope and exit
|
|
34
34
|
--confirm-prune Required when <path> has fewer files than the
|
|
@@ -58,25 +58,25 @@ Notes:
|
|
|
58
58
|
(CAS-backed): only bytes the gateway doesn't already have are uploaded.
|
|
59
59
|
Re-deploys of an unchanged tree make no S3 PUTs.
|
|
60
60
|
- To check status of an in-flight deploy, use 'run402 deploy events <op>'
|
|
61
|
-
or 'run402 deploy list --project <
|
|
61
|
+
or 'run402 deploy list --project <project_id>'.
|
|
62
62
|
- deploy-dir walks the directory, skips .git / node_modules / .DS_Store,
|
|
63
63
|
and auto-detects binary files. Symlinks are rejected.
|
|
64
64
|
- Progress events are emitted as JSON-line objects on stderr by default
|
|
65
65
|
(one object per line). Final result envelope goes to stdout. Pass --quiet
|
|
66
66
|
to silence stderr.
|
|
67
|
-
- Free with active tier - requires
|
|
67
|
+
- Free with active tier - requires SIWX auth
|
|
68
68
|
`;
|
|
69
69
|
|
|
70
70
|
const SUB_HELP = {
|
|
71
71
|
deploy: `run402 sites deploy - Deploy a static site from a manifest
|
|
72
72
|
|
|
73
73
|
Usage:
|
|
74
|
-
run402 sites deploy --manifest <file> [--project <
|
|
75
|
-
cat manifest.json | run402 sites deploy [--project <
|
|
74
|
+
run402 sites deploy --manifest <file> [--project <project_id>]
|
|
75
|
+
cat manifest.json | run402 sites deploy [--project <project_id>]
|
|
76
76
|
|
|
77
77
|
Options:
|
|
78
78
|
--manifest <file> Path to manifest JSON file (or read from stdin)
|
|
79
|
-
--project <
|
|
79
|
+
--project <project_id> Project ID (defaults to the active project)
|
|
80
80
|
|
|
81
81
|
Manifest format (JSON):
|
|
82
82
|
{
|
|
@@ -90,7 +90,7 @@ Manifest format (JSON):
|
|
|
90
90
|
|
|
91
91
|
Notes:
|
|
92
92
|
- Must include at least index.html in the files array
|
|
93
|
-
- Free with active tier - requires
|
|
93
|
+
- Free with active tier - requires SIWX auth
|
|
94
94
|
|
|
95
95
|
Examples:
|
|
96
96
|
run402 sites deploy --manifest site.json
|
|
@@ -99,14 +99,14 @@ Examples:
|
|
|
99
99
|
"deploy-dir": `run402 sites deploy-dir - Deploy a static site from a local directory
|
|
100
100
|
|
|
101
101
|
Usage:
|
|
102
|
-
run402 sites deploy-dir <path> [--project <
|
|
102
|
+
run402 sites deploy-dir <path> [--project <project_id>] [--quiet]
|
|
103
103
|
[--dry-run] [--confirm-prune]
|
|
104
104
|
|
|
105
105
|
Arguments:
|
|
106
106
|
<path> Local directory to deploy (positional, required)
|
|
107
107
|
|
|
108
108
|
Options:
|
|
109
|
-
--project <
|
|
109
|
+
--project <project_id> Project ID (defaults to the active project)
|
|
110
110
|
--quiet Suppress progress events on stderr (events are on by
|
|
111
111
|
default — see Progress events below)
|
|
112
112
|
--dry-run Plan the deploy and print a JSON envelope describing
|
|
@@ -139,7 +139,7 @@ Progress events:
|
|
|
139
139
|
Notes:
|
|
140
140
|
- Re-deploying an unchanged tree makes no S3 PUTs (returns immediately
|
|
141
141
|
with bytes_uploaded: 0)
|
|
142
|
-
- Free with active tier - requires
|
|
142
|
+
- Free with active tier - requires SIWX auth
|
|
143
143
|
|
|
144
144
|
Examples:
|
|
145
145
|
run402 sites deploy-dir ./dist --project prj_abc
|
|
@@ -196,7 +196,7 @@ async function deploy(args) {
|
|
|
196
196
|
fail({
|
|
197
197
|
code: "BAD_USAGE",
|
|
198
198
|
message: `Unexpected argument for sites deploy: ${extra[0]}`,
|
|
199
|
-
hint: "Use `run402 sites deploy --manifest <file> [--project <
|
|
199
|
+
hint: "Use `run402 sites deploy --manifest <file> [--project <project_id>]`.",
|
|
200
200
|
});
|
|
201
201
|
}
|
|
202
202
|
const opts = { manifest: null, project: undefined, target: undefined, quiet: false };
|
|
@@ -219,10 +219,10 @@ async function deploy(args) {
|
|
|
219
219
|
const manifest = JSON.parse(raw);
|
|
220
220
|
if (opts.manifest) resolveFilePathsInManifest(manifest, dirname(resolve(opts.manifest)));
|
|
221
221
|
|
|
222
|
-
// Preserve the aggressive early exit when no
|
|
222
|
+
// Preserve the aggressive early exit when no local wallet is configured.
|
|
223
223
|
// A delegate holds `deploy` and /apply/v1/plans accepts it; refusing locally
|
|
224
224
|
// would tell a wallet-less holder to run `run402 init` (see 4.11.2).
|
|
225
|
-
if (!delegateTokenFromEnv())
|
|
225
|
+
if (!delegateTokenFromEnv()) walletAuthHeaders("/apply/v1/plans");
|
|
226
226
|
|
|
227
227
|
const stage = stageFilesToTempDir(manifest.files || []);
|
|
228
228
|
try {
|
|
@@ -252,7 +252,7 @@ async function deployDir(args) {
|
|
|
252
252
|
fail({
|
|
253
253
|
code: "BAD_USAGE",
|
|
254
254
|
message: `Unexpected argument for sites deploy-dir: ${positionals[1]}`,
|
|
255
|
-
hint: "Use `run402 sites deploy-dir <path> --project <
|
|
255
|
+
hint: "Use `run402 sites deploy-dir <path> --project <project_id>`.",
|
|
256
256
|
});
|
|
257
257
|
}
|
|
258
258
|
const opts = {
|
|
@@ -282,16 +282,16 @@ async function deployDir(args) {
|
|
|
282
282
|
fail({
|
|
283
283
|
code: "BAD_USAGE",
|
|
284
284
|
message: "Missing <path>.",
|
|
285
|
-
hint: "run402 sites deploy-dir <path> --project <
|
|
285
|
+
hint: "run402 sites deploy-dir <path> --project <project_id>",
|
|
286
286
|
});
|
|
287
287
|
}
|
|
288
288
|
const projectId = resolveProjectId(opts.project);
|
|
289
289
|
if (opts.target !== undefined) failUnsupportedTarget();
|
|
290
290
|
|
|
291
|
-
// Preserve the aggressive early exit when no
|
|
291
|
+
// Preserve the aggressive early exit when no local wallet is configured.
|
|
292
292
|
// A delegate holds `deploy` and /apply/v1/plans accepts it; refusing locally
|
|
293
293
|
// would tell a wallet-less holder to run `run402 init` (see 4.11.2).
|
|
294
|
-
if (!delegateTokenFromEnv())
|
|
294
|
+
if (!delegateTokenFromEnv()) walletAuthHeaders("/apply/v1/plans");
|
|
295
295
|
|
|
296
296
|
let fileSet;
|
|
297
297
|
try {
|
|
@@ -325,7 +325,7 @@ async function deployDir(args) {
|
|
|
325
325
|
if (opts.dryRun) {
|
|
326
326
|
try {
|
|
327
327
|
const { plan } = await getSdk()._applyEngine.plan({
|
|
328
|
-
|
|
328
|
+
project_id: projectId,
|
|
329
329
|
site: { replace: fileSet },
|
|
330
330
|
}, { dryRun: true });
|
|
331
331
|
console.log(JSON.stringify({
|
package/lib/snapshots.mjs
CHANGED
|
@@ -15,14 +15,14 @@ import {
|
|
|
15
15
|
const HELP = `run402 snapshots — Project database restore points
|
|
16
16
|
|
|
17
17
|
Usage:
|
|
18
|
-
run402 snapshots create [--project <
|
|
19
|
-
run402 snapshots list [--project <
|
|
20
|
-
run402 snapshots get <
|
|
21
|
-
run402 snapshots restore <
|
|
22
|
-
run402 snapshots delete <
|
|
18
|
+
run402 snapshots create [--project <project_id>] [--json]
|
|
19
|
+
run402 snapshots list [--project <project_id>] [--kind <kind>] [--limit <n>] [--after <cursor>] [--json]
|
|
20
|
+
run402 snapshots get <snapshot_id> [--project <project_id>] [--json]
|
|
21
|
+
run402 snapshots restore <snapshot_id> [--project <project_id>] [--include-auth] [--confirm <token>] [--json]
|
|
22
|
+
run402 snapshots delete <snapshot_id> [--project <project_id>] [--json]
|
|
23
23
|
|
|
24
24
|
Legacy (still supported): a leading prj_... positional selects the project,
|
|
25
|
-
e.g. run402 snapshots restore prj_abc123 <
|
|
25
|
+
e.g. run402 snapshots restore prj_abc123 <snapshot_id>. --project defaults to
|
|
26
26
|
the active project.
|
|
27
27
|
|
|
28
28
|
Restore is a two-step handshake. First call without --confirm to get a
|
|
@@ -82,7 +82,7 @@ async function list(args) {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
async function get(args) {
|
|
85
|
-
const { projectId, snapshotId } = resolveProjectAndSnapshot(args, "run402 snapshots get [
|
|
85
|
+
const { projectId, snapshotId } = resolveProjectAndSnapshot(args, "run402 snapshots get [project_id] <snapshot_id>");
|
|
86
86
|
try {
|
|
87
87
|
const snapshot = await getSdk().snapshots.get(projectId, snapshotId);
|
|
88
88
|
console.log(JSON.stringify({ snapshot }, null, 2));
|
|
@@ -92,7 +92,7 @@ async function get(args) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
async function restore(args) {
|
|
95
|
-
const { projectId, snapshotId } = resolveProjectAndSnapshot(args, "run402 snapshots restore [
|
|
95
|
+
const { projectId, snapshotId } = resolveProjectAndSnapshot(args, "run402 snapshots restore [project_id] <snapshot_id> [--confirm <token>]");
|
|
96
96
|
const includeAuth = args.includes("--include-auth");
|
|
97
97
|
const confirm = flagValue(args, "--confirm");
|
|
98
98
|
try {
|
|
@@ -115,7 +115,7 @@ async function restore(args) {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
async function deleteSnapshot(args) {
|
|
118
|
-
const { projectId, snapshotId } = resolveProjectAndSnapshot(args, "run402 snapshots delete [
|
|
118
|
+
const { projectId, snapshotId } = resolveProjectAndSnapshot(args, "run402 snapshots delete [project_id] <snapshot_id>");
|
|
119
119
|
try {
|
|
120
120
|
await getSdk().snapshots.delete(projectId, snapshotId);
|
|
121
121
|
console.log(JSON.stringify({ ok: true, project_id: projectId, snapshot_id: snapshotId, deleted: true }, null, 2));
|
|
@@ -125,7 +125,7 @@ async function deleteSnapshot(args) {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
function resolveProjectAndSnapshot(args, usage) {
|
|
128
|
-
// Canonical: `<
|
|
128
|
+
// Canonical: `<snapshot_id> [--project <project_id>]`; legacy `<prj_id> <snapshot_id>`
|
|
129
129
|
// still works (requireRestPositional keeps a lone snapshot id that happens to
|
|
130
130
|
// start with prj_ from being eaten as the project selector).
|
|
131
131
|
const { projectId, rest } = resolveProjectSelector(args, { valueFlags: FLAG_VALUES, requireRestPositional: true });
|
package/lib/stats.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* line per request to stderr, automatically, for every SDK caller (CLI, MCP,
|
|
9
9
|
* `git-remote-run402`). This module is the thin CLI-edge reader of that
|
|
10
10
|
* per-instance accumulator: it turns `sdk.stats()` into the `stats` envelope
|
|
11
|
-
* field every `repos` verb's result and `deploy
|
|
11
|
+
* field every `repos` verb's result and `deploy`'s final result carry
|
|
12
12
|
* (always on), plus the `-v`/`--verbose` stderr summary line.
|
|
13
13
|
*
|
|
14
14
|
* `sdk.stats()` reflects only calls made through the ONE `Run402` instance
|
|
@@ -47,7 +47,7 @@ function writeVerboseStatsLine(sdk) {
|
|
|
47
47
|
/**
|
|
48
48
|
* One stderr summary line, only when verbose was requested. Coexists with
|
|
49
49
|
* `--human`. `verbose` is either normalized argv (checked for `-v`/
|
|
50
|
-
* `--verbose`) or a plain boolean, for callers (e.g. `deploy
|
|
50
|
+
* `--verbose`) or a plain boolean, for callers (e.g. `deploy`) that
|
|
51
51
|
* already parsed their own flags into an options object.
|
|
52
52
|
*/
|
|
53
53
|
export function printVerboseStats(verbose, sdk) {
|
package/lib/status.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readRemoteStatus } from "#sdk/node";
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
readWallet,
|
|
4
4
|
loadKeyStore,
|
|
5
5
|
getActiveProjectId,
|
|
6
6
|
apiBase,
|
|
@@ -21,8 +21,8 @@ Usage:
|
|
|
21
21
|
Displays:
|
|
22
22
|
- Wallet identity (local_label, server_label, address)
|
|
23
23
|
- Payment rail (x402 | mpp)
|
|
24
|
-
- Balances (on_chain_usd_micros + on_chain_token,
|
|
25
|
-
- Tier
|
|
24
|
+
- Balances (on_chain_usd_micros + on_chain_token, allowance_usd_micros, held_usd_micros)
|
|
25
|
+
- Tier and lease (name, status, expiry)
|
|
26
26
|
- Projects (from server, with fallback to local keystore)
|
|
27
27
|
- Active project ID
|
|
28
28
|
- Active API target
|
|
@@ -31,11 +31,11 @@ Output is JSON by default. --json is accepted as a compatibility no-op.
|
|
|
31
31
|
--human renders a compact human summary instead (wallet, API target, tier,
|
|
32
32
|
active project, balance, and the next action); it cannot be combined with
|
|
33
33
|
--json.
|
|
34
|
-
Run402 Cloud status requires
|
|
34
|
+
Run402 Cloud status requires a wallet; Core target status can still report
|
|
35
35
|
local project state without one.
|
|
36
36
|
`;
|
|
37
37
|
|
|
38
|
-
// USDC / pathUSD constants (match
|
|
38
|
+
// USDC / pathUSD constants (match wallet.mjs)
|
|
39
39
|
const USDC_ABI = [{ name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }] }];
|
|
40
40
|
const USDC_MAINNET = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
41
41
|
const USDC_SEPOLIA = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
|
|
@@ -105,13 +105,13 @@ export async function run(args = []) {
|
|
|
105
105
|
hint: "JSON is the default; drop --json to keep it, or keep --human alone for the rendered view.",
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
|
-
const
|
|
108
|
+
const localWallet = readWallet();
|
|
109
109
|
const target = {
|
|
110
110
|
api_base: apiBase(),
|
|
111
111
|
api_base_source: apiBaseSource(),
|
|
112
112
|
kind: apiTargetKind(),
|
|
113
113
|
};
|
|
114
|
-
if (!
|
|
114
|
+
if (!localWallet) {
|
|
115
115
|
const store = loadKeyStore();
|
|
116
116
|
const bare = {
|
|
117
117
|
wallet: null,
|
|
@@ -128,16 +128,16 @@ export async function run(args = []) {
|
|
|
128
128
|
return;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
const wallet =
|
|
132
|
-
const rail =
|
|
131
|
+
const wallet = localWallet.address.toLowerCase();
|
|
132
|
+
const rail = localWallet.rail || "x402";
|
|
133
133
|
|
|
134
|
-
// Parallel API calls: tier +
|
|
134
|
+
// Parallel API calls: tier + allowance + server-side projects + on-chain wallet balance.
|
|
135
135
|
// projects.list() is the membership-scoped named inventory (project-findability);
|
|
136
|
-
// SIWX wallet auth is signed from the
|
|
137
|
-
//
|
|
136
|
+
// SIWX wallet auth is signed from the wallet. Best-effort — a missing
|
|
137
|
+
// wallet yields null and we fall back to the local keystore below.
|
|
138
138
|
const [status, walletBalance] = await Promise.all([
|
|
139
139
|
readRemoteStatus(getSdk(), wallet),
|
|
140
|
-
readWalletBalanceUsdMicros(rail,
|
|
140
|
+
readWalletBalanceUsdMicros(rail, localWallet.address),
|
|
141
141
|
]);
|
|
142
142
|
const { tier, billing, remote } = status;
|
|
143
143
|
|
|
@@ -155,18 +155,18 @@ export async function run(args = []) {
|
|
|
155
155
|
const walletName = getActiveProfile();
|
|
156
156
|
const walletMeta = readMeta(walletName);
|
|
157
157
|
|
|
158
|
-
// Balances are grouped under one object so the on-chain and
|
|
159
|
-
//
|
|
158
|
+
// Balances are grouped under one object so the on-chain (wallet) and
|
|
159
|
+
// allowance numbers are unambiguous and rail-legible.
|
|
160
160
|
// - on_chain_usd_micros / on_chain_token: on-chain USDC (x402) or pathUSD
|
|
161
|
-
// (mpp), null if the RPC read failed
|
|
162
|
-
// -
|
|
163
|
-
// rail-independent, null when no organization exists
|
|
161
|
+
// (mpp) in the wallet, null if the RPC read failed
|
|
162
|
+
// - allowance_usd_micros / held_usd_micros: the organization's Run402-held
|
|
163
|
+
// allowance, rail-independent, null when no organization exists
|
|
164
164
|
const hasBilling = billing && billing.exists !== false;
|
|
165
165
|
const result = {
|
|
166
166
|
wallet: {
|
|
167
167
|
local_label: walletName,
|
|
168
168
|
server_label: walletMeta?.label ?? null,
|
|
169
|
-
address:
|
|
169
|
+
address: localWallet.address,
|
|
170
170
|
},
|
|
171
171
|
rail,
|
|
172
172
|
remote_status: status.availability,
|
|
@@ -175,7 +175,7 @@ export async function run(args = []) {
|
|
|
175
175
|
balances: {
|
|
176
176
|
on_chain_usd_micros: walletBalance,
|
|
177
177
|
on_chain_token: rail === "mpp" ? "pathUSD" : "USDC",
|
|
178
|
-
|
|
178
|
+
allowance_usd_micros: hasBilling ? billing.allowance_usd_micros : null,
|
|
179
179
|
held_usd_micros: hasBilling ? (billing.held_usd_micros ?? 0) : null,
|
|
180
180
|
},
|
|
181
181
|
tier: tier && tier.tier
|
|
@@ -205,7 +205,7 @@ function usdFromMicros(micros) {
|
|
|
205
205
|
|
|
206
206
|
/**
|
|
207
207
|
* The one next action for the human view, derived from the same state the
|
|
208
|
-
* JSON carries. Mirrors `init`: `run402 up -y`
|
|
208
|
+
* JSON carries. Mirrors `init`: `run402 up -y` sets the prototype tier
|
|
209
209
|
* itself as part of the first deploy, so a tier-less account gets ONE command.
|
|
210
210
|
*/
|
|
211
211
|
function statusNextAction(result) {
|
|
@@ -214,7 +214,7 @@ function statusNextAction(result) {
|
|
|
214
214
|
}
|
|
215
215
|
if (result.remote_status?.tier?.state === "unavailable") return "Check connectivity/authentication and retry run402 status; tier state is unavailable.";
|
|
216
216
|
if (!result.tier) {
|
|
217
|
-
return "run402 up -y (
|
|
217
|
+
return "run402 up -y (sets the prototype tier, free on testnet, as part of the first deploy; or: run402 tier set prototype)";
|
|
218
218
|
}
|
|
219
219
|
if (!result.active_project) {
|
|
220
220
|
return "run402 up --name <name> -y (or select an existing project: run402 projects use <project_id>)";
|
|
@@ -230,7 +230,7 @@ function statusNextAction(result) {
|
|
|
230
230
|
export function formatStatusHuman(result) {
|
|
231
231
|
const lines = [];
|
|
232
232
|
if (!result.wallet) {
|
|
233
|
-
lines.push("Wallet: none (no
|
|
233
|
+
lines.push("Wallet: none (no local wallet on this machine)");
|
|
234
234
|
} else {
|
|
235
235
|
const label = result.wallet.server_label
|
|
236
236
|
? `${result.wallet.local_label} (${result.wallet.server_label})`
|
|
@@ -262,8 +262,8 @@ export function formatStatusHuman(result) {
|
|
|
262
262
|
const parts = [];
|
|
263
263
|
const onChain = usdFromMicros(result.balances.on_chain_usd_micros);
|
|
264
264
|
if (onChain) parts.push(`${onChain} ${result.balances.on_chain_token} on-chain`);
|
|
265
|
-
const
|
|
266
|
-
if (
|
|
265
|
+
const allowance = usdFromMicros(result.balances.allowance_usd_micros);
|
|
266
|
+
if (allowance) parts.push(`${allowance} allowance`);
|
|
267
267
|
if (parts.length) lines.push(`Balance: ${parts.join(", ")}`);
|
|
268
268
|
}
|
|
269
269
|
lines.push(`Next: ${statusNextAction(result)}`);
|