run402 4.93.1 → 4.94.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 +43 -41
- package/cli.mjs +34 -29
- 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 +1 -1
- package/lib/admin.mjs +1 -1
- package/lib/agent.mjs +20 -18
- package/lib/approve.mjs +81 -0
- package/lib/apps.mjs +3 -3
- package/lib/assets.mjs +2 -2
- package/lib/billing.mjs +6 -6
- package/lib/buzz-doctor.mjs +1 -1
- package/lib/buzz-notifications.mjs +1 -1
- package/lib/buzz.mjs +1 -1
- package/lib/buzz.test.mjs +1 -1
- package/lib/ci.mjs +4 -4
- package/lib/cold-start.mjs +21 -21
- package/lib/command-manifest.mjs +20 -34
- package/lib/config.mjs +24 -24
- package/lib/contacts.mjs +13 -13
- package/lib/contracts.mjs +1 -1
- package/lib/credentials.mjs +2 -2
- package/lib/daemon-path.mjs +1 -1
- package/lib/delegates.mjs +1 -1
- package/lib/deliveries.mjs +4 -4
- package/lib/deploy-rehearse-discovery.test.mjs +7 -7
- package/lib/deploy-v2.mjs +232 -184
- package/lib/deploy.mjs +21 -53
- package/lib/doctor-source-scan.mjs +1 -1
- package/lib/doctor.mjs +57 -57
- package/lib/escalations.mjs +1 -1
- package/lib/feedback.mjs +10 -10
- package/lib/gitvault-daemon.mjs +1 -1
- package/lib/image.mjs +3 -3
- package/lib/init.mjs +66 -66
- package/lib/lightning-wallet.mjs +28 -28
- package/lib/login.mjs +207 -0
- package/lib/logout.mjs +53 -0
- package/lib/next-actions.mjs +15 -15
- package/lib/org.mjs +100 -13
- package/lib/pay.mjs +1 -1
- package/lib/projects.mjs +26 -29
- package/lib/redeem.mjs +7 -8
- package/lib/remote-helper-session.mjs +12 -12
- package/lib/repos.mjs +62 -62
- package/lib/rooms.mjs +103 -67
- package/lib/sdk-errors.mjs +9 -4
- package/lib/sdk.mjs +3 -3
- package/lib/service.mjs +5 -5
- package/lib/session-proofs.mjs +21 -0
- package/lib/sign-in.mjs +296 -0
- package/lib/sites.mjs +8 -8
- package/lib/stats.mjs +2 -2
- package/lib/status.mjs +25 -25
- package/lib/subdomains.mjs +19 -19
- package/lib/subscriptions.mjs +8 -8
- package/lib/tier.mjs +29 -22
- package/lib/transfer.mjs +58 -85
- package/lib/up.mjs +7 -7
- package/lib/wallet-context.mjs +3 -3
- package/lib/wallet-context.test.mjs +3 -3
- package/lib/wallets.mjs +196 -31
- package/lib/webhook-secret.mjs +6 -6
- package/lib/whoami.mjs +75 -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 +1 -1
- package/sdk/dist/actions.d.ts.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 +19 -19
- package/sdk/dist/credentials.d.ts.map +1 -1
- package/sdk/dist/credentials.js +1 -1
- package/sdk/dist/errors.d.ts +24 -26
- package/sdk/dist/errors.d.ts.map +1 -1
- package/sdk/dist/errors.js +34 -37
- 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 +29 -16
- 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 +51 -120
- 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 +1 -1
- package/sdk/dist/namespaces/ai.js +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/credentials.types.d.ts +1 -1
- package/sdk/dist/namespaces/deploy.d.ts +1 -1
- package/sdk/dist/namespaces/deploy.js +5 -5
- package/sdk/dist/namespaces/deploy.js.map +1 -1
- package/sdk/dist/namespaces/deploy.types.d.ts +4 -4
- 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 +1 -1
- package/sdk/dist/namespaces/gitvault.crypto.d.ts +1 -1
- package/sdk/dist/namespaces/gitvault.d.ts +24 -24
- package/sdk/dist/namespaces/gitvault.d.ts.map +1 -1
- package/sdk/dist/namespaces/gitvault.js +109 -109
- 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 +43 -20
- 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 +74 -71
- 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 +36 -36
- package/sdk/dist/node/actions-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 +17 -16
- package/sdk/dist/node/credentials.d.ts.map +1 -1
- package/sdk/dist/node/credentials.js +28 -27
- package/sdk/dist/node/credentials.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-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/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/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 +3 -3
- package/sdk/dist/scoped.d.ts.map +1 -1
- package/sdk/dist/scoped.js +2 -2
- package/sdk/dist/scoped.js.map +1 -1
- package/core-dist/operator-session.js +0 -154
- package/lib/allowance.mjs +0 -355
- 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";
|
|
@@ -64,7 +64,7 @@ Notes:
|
|
|
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 = {
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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 {
|
|
@@ -288,10 +288,10 @@ async function deployDir(args) {
|
|
|
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 {
|
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)}`);
|
package/lib/subdomains.mjs
CHANGED
|
@@ -9,16 +9,16 @@ Usage:
|
|
|
9
9
|
run402 subdomains <subcommand> [args...]
|
|
10
10
|
|
|
11
11
|
Subcommands:
|
|
12
|
-
|
|
12
|
+
add <name> [--project <project_id>] [--release <release_id> | --deployment <deployment_id>] Add a subdomain
|
|
13
13
|
delete <name> --confirm [--project <id>] Release a subdomain. Requires --confirm.
|
|
14
14
|
list [--project <id>] List subdomains for a project
|
|
15
15
|
|
|
16
16
|
Options default to the active project. With neither --release nor
|
|
17
|
-
--deployment,
|
|
17
|
+
--deployment, add binds the project's live release.
|
|
18
18
|
|
|
19
19
|
Examples:
|
|
20
|
-
run402 subdomains
|
|
21
|
-
run402 subdomains
|
|
20
|
+
run402 subdomains add myapp
|
|
21
|
+
run402 subdomains add myapp --release rel_abc123 --project prj_abc123
|
|
22
22
|
run402 subdomains delete myapp --confirm
|
|
23
23
|
run402 subdomains list
|
|
24
24
|
|
|
@@ -29,10 +29,10 @@ Notes:
|
|
|
29
29
|
`;
|
|
30
30
|
|
|
31
31
|
const SUB_HELP = {
|
|
32
|
-
|
|
32
|
+
add: `run402 subdomains add — Add a subdomain for a release
|
|
33
33
|
|
|
34
34
|
Usage:
|
|
35
|
-
run402 subdomains
|
|
35
|
+
run402 subdomains add <name> [--project <project_id>] [--release <release_id> | --deployment <deployment_id>]
|
|
36
36
|
|
|
37
37
|
Arguments:
|
|
38
38
|
<name> Subdomain name (3-63 chars, lowercase alphanumeric +
|
|
@@ -51,10 +51,10 @@ Notes:
|
|
|
51
51
|
- A deploy manifest can declare the same thing: "subdomains": { "set": ["<name>"] }
|
|
52
52
|
|
|
53
53
|
Examples:
|
|
54
|
-
run402 subdomains
|
|
55
|
-
run402 subdomains
|
|
54
|
+
run402 subdomains add myapp
|
|
55
|
+
run402 subdomains add myapp --release rel_abc123 --project prj_abc123
|
|
56
56
|
`,
|
|
57
|
-
list: `run402 subdomains list — List
|
|
57
|
+
list: `run402 subdomains list — List a project's subdomains
|
|
58
58
|
|
|
59
59
|
Usage:
|
|
60
60
|
run402 subdomains list [--project <id>]
|
|
@@ -66,7 +66,7 @@ Examples:
|
|
|
66
66
|
run402 subdomains list
|
|
67
67
|
run402 subdomains list --project prj_abc123
|
|
68
68
|
`,
|
|
69
|
-
delete: `run402 subdomains delete — Release a
|
|
69
|
+
delete: `run402 subdomains delete — Release a subdomain
|
|
70
70
|
|
|
71
71
|
Usage:
|
|
72
72
|
run402 subdomains delete <name> --confirm [--project <id>]
|
|
@@ -76,7 +76,7 @@ Arguments:
|
|
|
76
76
|
|
|
77
77
|
Options:
|
|
78
78
|
--confirm Required: releasing a subdomain is irreversible and
|
|
79
|
-
makes it available for any other project to
|
|
79
|
+
makes it available for any other project to add
|
|
80
80
|
--project <id> Project ID (defaults to the active project)
|
|
81
81
|
|
|
82
82
|
Examples:
|
|
@@ -85,7 +85,7 @@ Examples:
|
|
|
85
85
|
`,
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
-
async function
|
|
88
|
+
async function add(args) {
|
|
89
89
|
const parsedArgs = normalizeArgv(args);
|
|
90
90
|
const valueFlags = ["--project", "--deployment", "--release"];
|
|
91
91
|
assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
|
|
@@ -99,8 +99,8 @@ async function claim(args) {
|
|
|
99
99
|
if (positionals.length > 1) {
|
|
100
100
|
fail({
|
|
101
101
|
code: "BAD_USAGE",
|
|
102
|
-
message: `Unexpected argument for subdomains
|
|
103
|
-
hint: "Use `run402 subdomains
|
|
102
|
+
message: `Unexpected argument for subdomains add: ${positionals[1]}`,
|
|
103
|
+
hint: "Use `run402 subdomains add <name> [--release <rel_id>]`.",
|
|
104
104
|
});
|
|
105
105
|
}
|
|
106
106
|
if (positionals.length === 1) {
|
|
@@ -110,14 +110,14 @@ async function claim(args) {
|
|
|
110
110
|
fail({
|
|
111
111
|
code: "BAD_USAGE",
|
|
112
112
|
message: "Missing <name>.",
|
|
113
|
-
hint: "run402 subdomains
|
|
113
|
+
hint: "run402 subdomains add <name> [--project <project_id>] [--release <release_id> | --deployment <deployment_id>]",
|
|
114
114
|
});
|
|
115
115
|
}
|
|
116
116
|
if (opts.deployment && opts.release) {
|
|
117
117
|
fail({
|
|
118
118
|
code: "BAD_USAGE",
|
|
119
119
|
message: "Pass either --release or --deployment, not both.",
|
|
120
|
-
hint: "run402 subdomains
|
|
120
|
+
hint: "run402 subdomains add <name> [--release <id> | --deployment <id>]",
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
123
|
const projectId = resolveProjectId(opts.project);
|
|
@@ -129,7 +129,7 @@ async function claim(args) {
|
|
|
129
129
|
const deploymentId = opts.deployment || (opts.release ? undefined : p.last_deployment_id) || undefined;
|
|
130
130
|
const releaseId = opts.release || undefined;
|
|
131
131
|
try {
|
|
132
|
-
const data = await getSdk().subdomains.
|
|
132
|
+
const data = await getSdk().subdomains.add({ name, deploymentId, releaseId, projectId });
|
|
133
133
|
console.log(JSON.stringify(data, null, 2));
|
|
134
134
|
} catch (err) {
|
|
135
135
|
reportSdkError(err);
|
|
@@ -156,7 +156,7 @@ async function deleteSubdomain(allArgs) {
|
|
|
156
156
|
if (!argList.includes("--confirm")) {
|
|
157
157
|
fail({
|
|
158
158
|
code: "CONFIRMATION_REQUIRED",
|
|
159
|
-
message: `Destructive: releasing subdomain '${name}' makes it available for any other project to
|
|
159
|
+
message: `Destructive: releasing subdomain '${name}' makes it available for any other project to add. This is irreversible. Re-run with --confirm to proceed.`,
|
|
160
160
|
details: { name },
|
|
161
161
|
});
|
|
162
162
|
}
|
|
@@ -202,7 +202,7 @@ export async function run(sub, args) {
|
|
|
202
202
|
if (!sub || sub === '--help' || sub === '-h') { console.log(HELP); process.exit(0); }
|
|
203
203
|
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) { console.log(SUB_HELP[sub] || HELP); process.exit(0); }
|
|
204
204
|
switch (sub) {
|
|
205
|
-
case "
|
|
205
|
+
case "add": await add(args); break;
|
|
206
206
|
case "delete": await deleteSubdomain(args); break;
|
|
207
207
|
case "list": await list(args); break;
|
|
208
208
|
default:
|