run402 4.96.0 → 4.97.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 +2 -2
- package/cli.mjs +1 -7
- package/lib/buzz.mjs +1 -1
- package/lib/command-manifest.mjs +3 -6
- package/lib/credentials.mjs +4 -4
- package/lib/deploy-v2.mjs +27 -27
- package/lib/errors.mjs +1 -1
- package/lib/events.mjs +1 -1
- package/lib/grants.mjs +148 -46
- package/lib/logs.mjs +1 -1
- package/lib/projects.mjs +1 -1
- package/lib/sites.mjs +5 -5
- package/package.json +1 -1
- package/sdk/dist/errors.d.ts +1 -1
- package/sdk/dist/errors.js +2 -2
- package/sdk/dist/errors.js.map +1 -1
- package/sdk/dist/grant-key-credentials.d.ts +38 -0
- package/sdk/dist/grant-key-credentials.d.ts.map +1 -0
- package/sdk/dist/grant-key-credentials.js +42 -0
- package/sdk/dist/grant-key-credentials.js.map +1 -0
- package/sdk/dist/index.d.ts +7 -14
- package/sdk/dist/index.d.ts.map +1 -1
- package/sdk/dist/index.js +6 -13
- package/sdk/dist/index.js.map +1 -1
- package/sdk/dist/namespaces/billing.d.ts +1 -1
- package/sdk/dist/namespaces/billing.js +1 -1
- package/sdk/dist/namespaces/buzz.js +1 -1
- package/sdk/dist/namespaces/buzz.js.map +1 -1
- package/sdk/dist/namespaces/credentials.d.ts +4 -4
- package/sdk/dist/namespaces/credentials.js +4 -4
- package/sdk/dist/namespaces/deploy.js +5 -5
- package/sdk/dist/namespaces/deploy.js.map +1 -1
- package/sdk/dist/namespaces/domains.d.ts +1 -1
- package/sdk/dist/namespaces/domains.js +1 -1
- package/sdk/dist/namespaces/errors.d.ts +1 -1
- package/sdk/dist/namespaces/errors.js +2 -2
- package/sdk/dist/namespaces/escalations.d.ts +2 -2
- package/sdk/dist/namespaces/escalations.js +2 -2
- package/sdk/dist/namespaces/escalations.types.d.ts +3 -3
- package/sdk/dist/namespaces/escalations.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/events.d.ts +1 -1
- package/sdk/dist/namespaces/events.js +1 -1
- package/sdk/dist/namespaces/functions.js +1 -1
- package/sdk/dist/namespaces/functions.js.map +1 -1
- package/sdk/dist/namespaces/grants.d.ts +43 -9
- package/sdk/dist/namespaces/grants.d.ts.map +1 -1
- package/sdk/dist/namespaces/grants.js +99 -10
- package/sdk/dist/namespaces/grants.js.map +1 -1
- package/sdk/dist/namespaces/grants.types.d.ts +120 -15
- package/sdk/dist/namespaces/grants.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/grants.types.js +12 -3
- package/sdk/dist/namespaces/grants.types.js.map +1 -1
- package/sdk/dist/namespaces/identity-links.types.d.ts +2 -2
- package/sdk/dist/namespaces/identity-links.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/org.d.ts +1 -1
- package/sdk/dist/namespaces/org.js +1 -1
- package/sdk/dist/namespaces/projects.d.ts +1 -1
- package/sdk/dist/namespaces/projects.js +1 -1
- package/sdk/dist/namespaces/repos.js +2 -2
- package/sdk/dist/namespaces/repos.js.map +1 -1
- package/sdk/dist/namespaces/rooms.d.ts +3 -3
- package/sdk/dist/namespaces/rooms.js +3 -3
- package/sdk/dist/node/credentials.d.ts +9 -9
- package/sdk/dist/node/credentials.d.ts.map +1 -1
- package/sdk/dist/node/credentials.js +12 -12
- package/sdk/dist/node/credentials.js.map +1 -1
- package/sdk/dist/node/index.d.ts +5 -5
- package/sdk/dist/node/index.d.ts.map +1 -1
- package/sdk/dist/node/index.js +2 -2
- package/sdk/dist/node/index.js.map +1 -1
- package/sdk/dist/project-auth-classification.d.ts +8 -8
- package/sdk/dist/project-auth-classification.d.ts.map +1 -1
- package/sdk/dist/project-auth-classification.js +7 -7
- package/sdk/dist/project-auth-classification.js.map +1 -1
- package/sdk/dist/scoped.d.ts +6 -14
- package/sdk/dist/scoped.d.ts.map +1 -1
- package/sdk/dist/scoped.js +10 -21
- package/sdk/dist/scoped.js.map +1 -1
- package/vault-surface.json +1 -1
- package/lib/delegates.mjs +0 -189
- package/sdk/dist/delegate-credentials.d.ts +0 -37
- package/sdk/dist/delegate-credentials.d.ts.map +0 -1
- package/sdk/dist/delegate-credentials.js +0 -41
- package/sdk/dist/delegate-credentials.js.map +0 -1
- package/sdk/dist/namespaces/delegates.d.ts +0 -50
- package/sdk/dist/namespaces/delegates.d.ts.map +0 -1
- package/sdk/dist/namespaces/delegates.js +0 -99
- package/sdk/dist/namespaces/delegates.js.map +0 -1
- package/sdk/dist/namespaces/delegates.types.d.ts +0 -88
- package/sdk/dist/namespaces/delegates.types.d.ts.map +0 -1
- package/sdk/dist/namespaces/delegates.types.js +0 -10
- package/sdk/dist/namespaces/delegates.types.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Command-line interface for [Run402](https://run402.com) — provision Postgres databases, deploy static sites, run Node 22 serverless functions, host content-addressed CDN assets, send email, sign on-chain. Paid autonomously with x402 (USDC on Base) or MPP (pathUSD on Tempo, or sats over Bitcoin Lightning). **Prototype tier is free on testnet.**
|
|
6
6
|
|
|
7
|
-
The CLI lets an agent act as its own first-class Run402 principal rather than borrowing a human account. Identity records who invoked an action; organization roles, grants,
|
|
7
|
+
The CLI lets an agent act as its own first-class Run402 principal rather than borrowing a human account. Identity records who invoked an action; organization roles, grants, grant keys, freshness, and spend policy determine what that principal may do. People remain first-class owners and members without having to run the CLI themselves.
|
|
8
8
|
|
|
9
9
|
For the full CLI reference (every flag, every subcommand) start at **<https://docs.run402.com/llms-cli.txt>** — the index holds the first-deploy contract and links one fetchable `/llms-cli-<slice>.txt` per topic (deploy, commands, repos, orgs, functions, assets, ops, errors, frontend, platform); <https://docs.run402.com/llms-cli-full.txt> is the whole reference as one document.
|
|
10
10
|
|
|
@@ -121,7 +121,7 @@ This links two separately held public identities through fresh signatures; it ne
|
|
|
121
121
|
|
|
122
122
|
### Buzz community control plane
|
|
123
123
|
|
|
124
|
-
`run402 buzz status` reports skill installation, human-adoption offers/adoptions, community installation, and this agent's enrollment independently, and safely reports `supported: false` against an older gateway. The canonical ownership workflow is `buzz adopt offer --org … --identity-link …`: it capability-checks before mutation and returns a normal durable HTTPS handoff. Poll with `buzz adopt offer show <buzzhao_id>` and cancel with `buzz adopt offer cancel <buzzhao_id>`. The raw challenge command is explicitly advanced as `buzz adopt direct …`; direct completion/cancellation remain `buzz adopt complete|cancel`. Other goal workflows are `buzz install` and `buzz enroll`; decisions are `buzz install activate|update|revoke` and `buzz approve|deny|revoke`. `buzz install discover --community <buzz:community:host>` lists Run402-owned active descriptors without authentication. MCP intentionally has no signing/passkey mutation and only renders exact HTTPS/CLI handoffs. All commands print JSON to stdout, advice to stderr, have zero spend impact, and never accept Nostr/wallet secrets, session material, service keys,
|
|
124
|
+
`run402 buzz status` reports skill installation, human-adoption offers/adoptions, community installation, and this agent's enrollment independently, and safely reports `supported: false` against an older gateway. The canonical ownership workflow is `buzz adopt offer --org … --identity-link …`: it capability-checks before mutation and returns a normal durable HTTPS handoff. Poll with `buzz adopt offer show <buzzhao_id>` and cancel with `buzz adopt offer cancel <buzzhao_id>`. The raw challenge command is explicitly advanced as `buzz adopt direct …`; direct completion/cancellation remain `buzz adopt complete|cancel`. Other goal workflows are `buzz install` and `buzz enroll`; decisions are `buzz install activate|update|revoke` and `buzz approve|deny|revoke`. `buzz install discover --community <buzz:community:host>` lists Run402-owned active descriptors without authentication. MCP intentionally has no signing/passkey mutation and only renders exact HTTPS/CLI handoffs. All commands print JSON to stdout, advice to stderr, have zero spend impact, and never accept Nostr/wallet secrets, session material, service keys, grant keys, or payment credentials. Failure JSON preserves the gateway's code-specific `next_actions`, exact repair `field`, and retry safety; never replace it with a generic edit-and-retry. See the [Fizz/Honey examples](../buzz/references/community-control-plane.md).
|
|
125
125
|
|
|
126
126
|
### Buy from an x402 URL
|
|
127
127
|
|
package/cli.mjs
CHANGED
|
@@ -70,8 +70,7 @@ COORDINATE — work alongside other agents and humans
|
|
|
70
70
|
|
|
71
71
|
AUTHORITY — who may act, and with what credential
|
|
72
72
|
credentials Manage local credential material (project-keys)
|
|
73
|
-
|
|
74
|
-
grants Per-project capability grants for agent/CI principals (create, revoke)
|
|
73
|
+
grants Project grants and grant keys for agents (create, list, revoke, revoke-key, rotate-key)
|
|
75
74
|
orgs Organizations, members, invites & audit (list, get, members, invite, audit)
|
|
76
75
|
identity Public proof-backed external agent identity links
|
|
77
76
|
auth Manage project user authentication (magic link, passwords, settings)
|
|
@@ -285,11 +284,6 @@ switch (cmd) {
|
|
|
285
284
|
await run(sub, rest);
|
|
286
285
|
break;
|
|
287
286
|
}
|
|
288
|
-
case "delegates": {
|
|
289
|
-
const { run } = await import("./lib/delegates.mjs");
|
|
290
|
-
await run(sub, rest);
|
|
291
|
-
break;
|
|
292
|
-
}
|
|
293
287
|
case "events": {
|
|
294
288
|
const { run } = await import("./lib/events.mjs");
|
|
295
289
|
await run(sub, rest);
|
package/lib/buzz.mjs
CHANGED
|
@@ -67,7 +67,7 @@ Advanced recovery reads:
|
|
|
67
67
|
Security:
|
|
68
68
|
- JSON stdout only; progress and pending-state advice go to stderr.
|
|
69
69
|
- This command never accepts an nsec, private key, mnemonic, seed, service key,
|
|
70
|
-
|
|
70
|
+
grant key, recovery code, or payment material.
|
|
71
71
|
- Buzz signatures are public evidence. Run402 authority still comes only from
|
|
72
72
|
human/agent principals, org membership, and project grants.
|
|
73
73
|
- Every command in this group has zero spend impact.
|
package/lib/command-manifest.mjs
CHANGED
|
@@ -235,13 +235,10 @@ export const COMMAND_MANIFEST = [
|
|
|
235
235
|
|
|
236
236
|
// ── grants ───────────────────────────────────────────────────────────────
|
|
237
237
|
{ path: ["grants", "create"], positionals: [p("wallet")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["0x1111111111111111111111111111111111111111", "--capability", "deploy"] },
|
|
238
|
+
{ path: ["grants", "list"], positionals: [], projectScoped: true, legacyPositionalProject: true, minimalArgs: [] },
|
|
238
239
|
{ path: ["grants", "revoke"], positionals: [p("grant_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["grt_gate1"] },
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
{ path: ["delegates", "create"], legacyPositionalProject: false, positionals: [], projectScoped: true, minimalArgs: ["--grant", "11111111-1111-1111-1111-111111111111"] },
|
|
242
|
-
{ path: ["delegates", "list"], legacyPositionalProject: false, positionals: [], projectScoped: true, minimalArgs: [] },
|
|
243
|
-
{ path: ["delegates", "revoke"], legacyPositionalProject: false, positionals: [p("delegate_id")], projectScoped: true, minimalArgs: ["dlg_gate1"] },
|
|
244
|
-
{ path: ["delegates", "rotate"], legacyPositionalProject: false, positionals: [p("delegate_id")], projectScoped: true, minimalArgs: ["dlg_gate1"] },
|
|
240
|
+
{ path: ["grants", "revoke-key"], positionals: [p("key_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["key_gate1"] },
|
|
241
|
+
{ path: ["grants", "rotate-key"], positionals: [p("key_id")], projectScoped: true, legacyPositionalProject: true, minimalArgs: ["key_gate1"] },
|
|
245
242
|
|
|
246
243
|
// ── events / live ────────────────────────────────────────────────────────
|
|
247
244
|
{ path: ["events", "list"], positionals: [], projectScoped: true, legacyPositionalProject: false, minimalArgs: [], runStyle: "sub" },
|
package/lib/credentials.mjs
CHANGED
|
@@ -39,7 +39,7 @@ Project credentials (on the gateway — named, revocable, rotatable):
|
|
|
39
39
|
Revoke immediately, freeing the name
|
|
40
40
|
token [--project <project_id>] [--kind <anon|service>]
|
|
41
41
|
Mint a SHORT-LIVED token. Works with only a
|
|
42
|
-
|
|
42
|
+
grant key — the unattended recovery path.
|
|
43
43
|
|
|
44
44
|
Local cache (on this machine):
|
|
45
45
|
project-keys Manage the local project-key cache
|
|
@@ -49,7 +49,7 @@ Notes:
|
|
|
49
49
|
plus a fresh step-up, so a scoped agent credential can never escalate
|
|
50
50
|
itself into a permanent root. Authenticate with a wallet (SIWX) or a
|
|
51
51
|
sign-in session ('run402 login').
|
|
52
|
-
- 'token' is the exception: a
|
|
52
|
+
- 'token' is the exception: a grant key can mint one with no human present.
|
|
53
53
|
- Secrets are returned EXACTLY ONCE and are never recoverable. Full JSON goes
|
|
54
54
|
to stdout so you can pipe it; the warnings go to stderr.
|
|
55
55
|
|
|
@@ -106,7 +106,7 @@ Usage:
|
|
|
106
106
|
The secret is printed ONCE, on stdout, inside the JSON. Pipe it:
|
|
107
107
|
run402 credentials issue --kind service --name ci | jq -r .secret
|
|
108
108
|
|
|
109
|
-
Requires owner membership on the project's org plus a fresh step-up. A
|
|
109
|
+
Requires owner membership on the project's org plus a fresh step-up. A grant key
|
|
110
110
|
can NEVER do this; use 'run402 credentials token' instead.
|
|
111
111
|
`,
|
|
112
112
|
list: `run402 credentials list — list a project's credentials
|
|
@@ -157,7 +157,7 @@ Usage:
|
|
|
157
157
|
run402 credentials token [--project <project_id>] [--kind <anon|service>]
|
|
158
158
|
|
|
159
159
|
The cold-restart recovery path, and the ONE credential call an agent can make
|
|
160
|
-
with no human present: a
|
|
160
|
+
with no human present: a grant key is accepted here. There is no step-up because
|
|
161
161
|
there is nobody to prompt, and what you get back expires, so it cannot become a
|
|
162
162
|
durable root. Defaults to --kind service.
|
|
163
163
|
`,
|
package/lib/deploy-v2.mjs
CHANGED
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
import { getSdk } from "./sdk.mjs";
|
|
45
45
|
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
46
46
|
import { API, walletAuthHeaders, getActiveProjectId, resolveProjectId, isCoreApiTarget, updateProject } from "./config.mjs";
|
|
47
|
-
import {
|
|
47
|
+
import { grantKeyFromEnv } from "#sdk/node";
|
|
48
48
|
import { flagValue, normalizeArgv } from "./argparse.mjs";
|
|
49
49
|
import { loadLiveControlPlaneSession } from "../core-dist/control-plane-session.js";
|
|
50
50
|
import { withAutoApprove } from "./sign-in.mjs";
|
|
@@ -486,10 +486,10 @@ async function rehearseCmd(rawArgs) {
|
|
|
486
486
|
fail({ code: "BAD_USAGE", message: "--teardown must be one of: on_pass, keep, always", details: { flag: "--teardown", value: teardown } });
|
|
487
487
|
}
|
|
488
488
|
const project = flagValue(args, "--project") ?? undefined;
|
|
489
|
-
// A
|
|
489
|
+
// A grant key is a complete deploy credential and the gateway explicitly
|
|
490
490
|
// supports rehearsing with one (the route rejects only CI sessions).
|
|
491
491
|
// Rehearsal is the SAFE path; never make it the harder one.
|
|
492
|
-
if (!isCoreApiTarget() && !loadLiveControlPlaneSession() && !
|
|
492
|
+
if (!isCoreApiTarget() && !loadLiveControlPlaneSession() && !grantKeyFromEnv()) {
|
|
493
493
|
walletAuthHeaders(`/apply/v1/plans/${givenPlanId ?? "_"}/rehearse`);
|
|
494
494
|
}
|
|
495
495
|
const sdk = getSdk();
|
|
@@ -723,9 +723,9 @@ async function promoteCmd(args) {
|
|
|
723
723
|
|
|
724
724
|
// Preserve the aggressive early-exit when no local wallet is configured
|
|
725
725
|
// — same as apply.
|
|
726
|
-
// A
|
|
726
|
+
// A grant key holds `deploy` and the gateway accepts it on this route; refusing
|
|
727
727
|
// locally would tell a wallet-less holder to run `run402 init` (see 4.11.2).
|
|
728
|
-
if (!
|
|
728
|
+
if (!grantKeyFromEnv()) walletAuthHeaders("/apply/v1/releases");
|
|
729
729
|
|
|
730
730
|
try {
|
|
731
731
|
// Call the engine directly (matches the pattern used by apply / resume
|
|
@@ -1295,20 +1295,20 @@ async function deployCmd(args) {
|
|
|
1295
1295
|
|
|
1296
1296
|
|
|
1297
1297
|
let sdkOpts;
|
|
1298
|
-
const
|
|
1298
|
+
const grantKey = grantKeyFromEnv();
|
|
1299
1299
|
if (useGithubActionsOidc) {
|
|
1300
1300
|
sdkOpts = {
|
|
1301
1301
|
credentials: githubActionsCredentials({ projectId: releaseSpec.project_id, apiBase: API }),
|
|
1302
1302
|
disablePaidFetch: true,
|
|
1303
1303
|
};
|
|
1304
|
-
} else if (
|
|
1305
|
-
// A
|
|
1304
|
+
} else if (grantKey) {
|
|
1305
|
+
// A grant key is a complete, self-contained deploy credential: the owner
|
|
1306
1306
|
// minted it with their wallet and handed it over, so this process needs no
|
|
1307
1307
|
// wallet of its own. Skipping the guard is the point — an agent that
|
|
1308
1308
|
// lost its local state (or never had a wallet) can still deploy. Paid fetch
|
|
1309
|
-
// is disabled for the same reason it is under CI: a
|
|
1309
|
+
// is disabled for the same reason it is under CI: a grant key authorizes
|
|
1310
1310
|
// deploys, not spending.
|
|
1311
|
-
sdkOpts = {
|
|
1311
|
+
sdkOpts = { grantKey, disablePaidFetch: true };
|
|
1312
1312
|
} else if (!isCoreApiTarget() && !loadLiveControlPlaneSession()) {
|
|
1313
1313
|
// Aggressive early exit when no local wallet is configured — unless a
|
|
1314
1314
|
// wallet-less person is deploying via their sign-in session
|
|
@@ -1766,9 +1766,9 @@ async function resumeCmd(args) {
|
|
|
1766
1766
|
emitDeployUpdateNotice("resume", args, { quiet: opts.quiet });
|
|
1767
1767
|
const project = resolveProjectId(opts.project);
|
|
1768
1768
|
|
|
1769
|
-
// A
|
|
1769
|
+
// A grant key holds `deploy` and the gateway accepts it on this route; refusing
|
|
1770
1770
|
// locally would tell a wallet-less holder to run `run402 init` (see 4.11.2).
|
|
1771
|
-
if (!
|
|
1771
|
+
if (!grantKeyFromEnv()) walletAuthHeaders("/apply/v1/operations");
|
|
1772
1772
|
|
|
1773
1773
|
try {
|
|
1774
1774
|
const result = await getSdk({ disablePaidFetch: true })._applyEngine.resume(opts.operationId, {
|
|
@@ -1794,9 +1794,9 @@ async function statusCmd(args) {
|
|
|
1794
1794
|
missing: "Missing <operation_id>.",
|
|
1795
1795
|
});
|
|
1796
1796
|
const project = resolveProjectId(parsed.flags["--project"] ?? null);
|
|
1797
|
-
// A
|
|
1797
|
+
// A grant key holds `deploy` and the gateway accepts it on this route; refusing
|
|
1798
1798
|
// locally would tell a wallet-less holder to run `run402 init` (see 4.11.2).
|
|
1799
|
-
if (!
|
|
1799
|
+
if (!grantKeyFromEnv()) walletAuthHeaders("/apply/v1/operations");
|
|
1800
1800
|
|
|
1801
1801
|
try {
|
|
1802
1802
|
const result = await getSdk()._applyEngine.status(operationId, { project });
|
|
@@ -1822,24 +1822,24 @@ async function listCmd(args) {
|
|
|
1822
1822
|
};
|
|
1823
1823
|
|
|
1824
1824
|
const project = resolveProjectId(opts.project);
|
|
1825
|
-
// NOT ungated for
|
|
1825
|
+
// NOT ungated for grant keys, deliberately: /apply/v1/operations (list) is
|
|
1826
1826
|
// `apikeyAuth` only — unlike get/events/resume/promote, which accept a
|
|
1827
|
-
//
|
|
1827
|
+
// grant-key bearer. Listing every operation on a project is broader than
|
|
1828
1828
|
// the per-operation reads a deploy credential is scoped to. If this is
|
|
1829
1829
|
// ever widened, widen the ROUTE first, then this guard.
|
|
1830
1830
|
//
|
|
1831
1831
|
// The refusal is correct; the REMEDY must still be honest. Falling through
|
|
1832
|
-
// to NO_WALLET would tell a
|
|
1832
|
+
// to NO_WALLET would tell a grant-key holder to run `run402 init`, which
|
|
1833
1833
|
// is wrong for a credential that is wallet-less by design — the same
|
|
1834
1834
|
// misleading-remedy shape removed from the payment path in 4.11.2.
|
|
1835
|
-
if (
|
|
1835
|
+
if (grantKeyFromEnv()) {
|
|
1836
1836
|
fail({
|
|
1837
|
-
code: "
|
|
1838
|
-
message: "Listing deploy operations is not available to a
|
|
1839
|
-
hint: "Use `run402 deploy status <operation_id>`, `deploy events <operation_id>` or `deploy verify <operation_id>` for a specific operation, which a
|
|
1840
|
-
details: { command: "deploy list", credential: "
|
|
1837
|
+
code: "GRANT_KEY_SCOPE_INSUFFICIENT",
|
|
1838
|
+
message: "Listing deploy operations is not available to a grant key.",
|
|
1839
|
+
hint: "Use `run402 deploy status <operation_id>`, `deploy events <operation_id>` or `deploy verify <operation_id>` for a specific operation, which a grant key CAN read. A full listing needs the project apikey or the owner wallet.",
|
|
1840
|
+
details: { command: "deploy list", credential: "grant_key", route: "GET /apply/v1/operations" },
|
|
1841
1841
|
next_actions: [
|
|
1842
|
-
{ type: "run_command", command: "run402 deploy status <operation_id>", why: "Per-operation reads are within a
|
|
1842
|
+
{ type: "run_command", command: "run402 deploy status <operation_id>", why: "Per-operation reads are within a grant key's deploy scope." },
|
|
1843
1843
|
],
|
|
1844
1844
|
});
|
|
1845
1845
|
}
|
|
@@ -1870,9 +1870,9 @@ async function eventsCmd(args) {
|
|
|
1870
1870
|
const opts = { operationId, project: parsed.flags["--project"] ?? null };
|
|
1871
1871
|
|
|
1872
1872
|
const project = resolveProjectId(opts.project);
|
|
1873
|
-
// A
|
|
1873
|
+
// A grant key holds `deploy` and the gateway accepts it on this route; refusing
|
|
1874
1874
|
// locally would tell a wallet-less holder to run `run402 init` (see 4.11.2).
|
|
1875
|
-
if (!
|
|
1875
|
+
if (!grantKeyFromEnv()) walletAuthHeaders("/apply/v1/operations");
|
|
1876
1876
|
|
|
1877
1877
|
try {
|
|
1878
1878
|
const result = await getSdk()._applyEngine.events(opts.operationId, { project });
|
|
@@ -1916,9 +1916,9 @@ async function verifyCmd(args) {
|
|
|
1916
1916
|
? 60
|
|
1917
1917
|
: parsePositiveInt(parsed.flags["--timeout"], "--timeout");
|
|
1918
1918
|
|
|
1919
|
-
// A
|
|
1919
|
+
// A grant key holds `deploy` and the gateway accepts it on this route; refusing
|
|
1920
1920
|
// locally would tell a wallet-less holder to run `run402 init` (see 4.11.2).
|
|
1921
|
-
if (!
|
|
1921
|
+
if (!grantKeyFromEnv()) walletAuthHeaders("/apply/v1/operations");
|
|
1922
1922
|
|
|
1923
1923
|
try {
|
|
1924
1924
|
let result;
|
package/lib/errors.mjs
CHANGED
|
@@ -110,7 +110,7 @@ Exit codes (the promote gate — only when --fail-on-new is set):
|
|
|
110
110
|
|
|
111
111
|
Auth:
|
|
112
112
|
The addressed project's own service_key when it is cached locally; otherwise
|
|
113
|
-
your wallet / session /
|
|
113
|
+
your wallet / session / grant key, which the gateway authorizes with
|
|
114
114
|
project.read — an org member or teammate agent needs no project key. A key
|
|
115
115
|
for project A requesting project B's errors gets 403 (never a 404 that
|
|
116
116
|
leaks existence). Read-only; never lifecycle-gated.
|
package/lib/events.mjs
CHANGED
|
@@ -95,7 +95,7 @@ Event shape:
|
|
|
95
95
|
|
|
96
96
|
Auth:
|
|
97
97
|
--project: the project's own service_key, your SIWX wallet, a control-plane
|
|
98
|
-
session, or a
|
|
98
|
+
session, or a grant key with project.read.
|
|
99
99
|
--org: wallet / control-plane session with an active org membership
|
|
100
100
|
(a project service_key cannot read sibling projects' events).
|
|
101
101
|
The feed is read-only and never lifecycle-gated: a frozen project's feed
|
package/lib/grants.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getSdk } from "./sdk.mjs";
|
|
2
|
-
import { reportSdkError, parseFlagJson } from "./sdk-errors.mjs";
|
|
2
|
+
import { reportSdkError, parseFlagJson, fail } from "./sdk-errors.mjs";
|
|
3
3
|
import {
|
|
4
4
|
normalizeArgv,
|
|
5
5
|
assertKnownFlags,
|
|
@@ -9,114 +9,213 @@ import {
|
|
|
9
9
|
failUnknownSubcommand,
|
|
10
10
|
} from "./argparse.mjs";
|
|
11
11
|
|
|
12
|
-
const HELP = `run402 grants — per-project capability grants
|
|
12
|
+
const HELP = `run402 grants — per-project capability grants and their grant keys
|
|
13
13
|
|
|
14
14
|
Usage:
|
|
15
15
|
run402 grants <subcommand> [args...]
|
|
16
16
|
|
|
17
17
|
Subcommands:
|
|
18
|
-
create <wallet> --capability <cap> [--project <project_id>] [--policy <json>] [--expires <iso8601>]
|
|
19
|
-
|
|
18
|
+
create <wallet> --capability <cap> [--project <project_id>] [--policy <json>] [--expires-at <iso8601>]
|
|
19
|
+
[--key [--kind <kind>] [--scope <json>] [--spend-cap <json>]]
|
|
20
|
+
Issue a grant; with --key also mint its first
|
|
21
|
+
grant key (the token is printed ONCE)
|
|
22
|
+
list [--project <project_id>] List grants with their keys nested (never tokens)
|
|
20
23
|
revoke <grant_id> [--project <project_id>]
|
|
21
|
-
Revoke a
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
Revoke a grant and every key minted against it
|
|
25
|
+
revoke-key <key_id> [--project <project_id>]
|
|
26
|
+
Revoke one grant key; the grant stays
|
|
27
|
+
rotate-key <key_id> [--project <project_id>]
|
|
28
|
+
Replace a lost key; the new token is printed ONCE
|
|
26
29
|
|
|
27
30
|
Notes:
|
|
28
|
-
-
|
|
29
|
-
|
|
31
|
+
- A grant is the permission row: this wallet may do this capability on this
|
|
32
|
+
project until this date. An agent with its own wallet needs only the grant.
|
|
33
|
+
- A grant key is a credential minted against exactly one grant: narrower than
|
|
34
|
+
it, spend-capped, expiring, revocable on its own, never an owner. Hand it to
|
|
35
|
+
an agent without a wallet: RUN402_GRANT_KEY=<token> run402 deploy ...
|
|
36
|
+
- Every subcommand requires owner of the project's org. Minting a key with a
|
|
37
|
+
sign-in session takes a fresh step-up.
|
|
38
|
+
- The key token is shown ONCE and cannot be read back. Store it immediately;
|
|
39
|
+
if you lose it, 'rotate-key' issues a new one.
|
|
30
40
|
- capability examples: deploy, functions:write.
|
|
31
41
|
- JSON in, JSON out.
|
|
32
42
|
|
|
33
43
|
Examples:
|
|
34
44
|
run402 grants create 0xf39Fd6...92266 --capability deploy --project prj_abc
|
|
35
|
-
run402 grants create
|
|
36
|
-
run402 grants
|
|
45
|
+
run402 grants create 0x90F3eB...F80a --capability deploy --key --project prj_abc
|
|
46
|
+
run402 grants list --project prj_abc
|
|
47
|
+
run402 grants revoke-key 5845f17b-... --project prj_abc
|
|
48
|
+
run402 grants revoke 1d86452e-... --project prj_abc
|
|
37
49
|
`;
|
|
38
50
|
|
|
39
51
|
const SUB_HELP = {
|
|
40
|
-
create: `run402 grants create — issue a per-project capability grant
|
|
52
|
+
create: `run402 grants create — issue a per-project capability grant, optionally with a grant key
|
|
41
53
|
|
|
42
54
|
Usage:
|
|
43
|
-
run402 grants create <wallet> --capability <cap> [--project <project_id>] [--policy <json>] [--expires <iso8601>]
|
|
44
|
-
|
|
45
|
-
Legacy (still supported):
|
|
46
|
-
run402 grants create <project_id> <wallet> <capability> [options]
|
|
55
|
+
run402 grants create <wallet> --capability <cap> [--project <project_id>] [--policy <json>] [--expires-at <iso8601>]
|
|
56
|
+
[--key [--kind <kind>] [--scope <json>] [--spend-cap <json>]]
|
|
47
57
|
|
|
48
58
|
Arguments:
|
|
49
|
-
<wallet>
|
|
59
|
+
<wallet> EVM address or named wallet the grant is issued to
|
|
50
60
|
|
|
51
61
|
Options:
|
|
52
|
-
--project <project_id>
|
|
53
|
-
--capability <cap>
|
|
54
|
-
--policy <json>
|
|
55
|
-
--expires <iso8601>
|
|
62
|
+
--project <project_id> Project to grant access to (defaults to the active project)
|
|
63
|
+
--capability <cap> e.g. deploy, functions:write
|
|
64
|
+
--policy <json> Capability-scoping policy object (gateway-interpreted)
|
|
65
|
+
--expires-at <iso8601> Expiry; omit for a non-expiring grant. With --key the
|
|
66
|
+
key takes the same expiry.
|
|
67
|
+
--key Also mint the grant's first grant key in the same
|
|
68
|
+
transaction. Its token is printed ONCE.
|
|
69
|
+
--kind <kind> Key kind (with --key). Default run402_agent_key, the
|
|
70
|
+
only kind that returns a bearer token.
|
|
71
|
+
--scope <json> Key scope (with --key), e.g. {"v":1,"capabilities":["deploy"]}.
|
|
72
|
+
Defaults to the grant's own capability; never wider.
|
|
73
|
+
--spend-cap <json> Key spend cap (with --key), e.g.
|
|
74
|
+
{"v":1,"currency":"usd_micros","per_period":5000000,"period":"month"}
|
|
56
75
|
|
|
57
76
|
Requires you to be an owner of the project's org.
|
|
77
|
+
`,
|
|
78
|
+
list: `run402 grants list — list a project's grants with their grant keys
|
|
79
|
+
|
|
80
|
+
Usage:
|
|
81
|
+
run402 grants list [--project <project_id>]
|
|
82
|
+
|
|
83
|
+
Each grant carries its keys under "keys" (kind, scope, spend cap, expiry,
|
|
84
|
+
revocation). Never returns a token or secret material.
|
|
58
85
|
`,
|
|
59
86
|
revoke: `run402 grants revoke — revoke a per-project capability grant
|
|
60
87
|
|
|
61
88
|
Usage:
|
|
62
89
|
run402 grants revoke <grant_id> [--project <project_id>]
|
|
63
90
|
|
|
64
|
-
|
|
65
|
-
|
|
91
|
+
Revokes the grant and every grant key minted against it. Requires you to be an
|
|
92
|
+
owner of the project's org.
|
|
93
|
+
`,
|
|
94
|
+
"revoke-key": `run402 grants revoke-key — revoke one grant key
|
|
66
95
|
|
|
96
|
+
Usage:
|
|
97
|
+
run402 grants revoke-key <key_id> [--project <project_id>]
|
|
98
|
+
|
|
99
|
+
Takes effect for every subsequent request. The grant and its other keys stay.
|
|
67
100
|
Requires you to be an owner of the project's org.
|
|
101
|
+
`,
|
|
102
|
+
"rotate-key": `run402 grants rotate-key — replace a grant key
|
|
103
|
+
|
|
104
|
+
Usage:
|
|
105
|
+
run402 grants rotate-key <key_id> [--project <project_id>]
|
|
106
|
+
|
|
107
|
+
Revokes the key and mints a fresh one against the same grant with the same
|
|
108
|
+
kind, scope, spend cap and expiry. The NEW token is printed once. Use this when
|
|
109
|
+
a token is lost or possibly exposed.
|
|
68
110
|
`,
|
|
69
111
|
};
|
|
70
112
|
|
|
71
|
-
const CREATE_VALUE_FLAGS = ["--project", "--capability", "--policy", "--expires"];
|
|
113
|
+
const CREATE_VALUE_FLAGS = ["--project", "--capability", "--policy", "--expires-at", "--kind", "--scope", "--spend-cap"];
|
|
114
|
+
const KEY_ONLY_FLAGS = ["--kind", "--scope", "--spend-cap"];
|
|
115
|
+
|
|
116
|
+
function warnTokenOnce(key) {
|
|
117
|
+
if (!key?.token) return;
|
|
118
|
+
console.error("");
|
|
119
|
+
console.error("The grant key token above is shown ONCE and cannot be read back.");
|
|
120
|
+
console.error("Store it now, then use it as: RUN402_GRANT_KEY=<token>");
|
|
121
|
+
}
|
|
72
122
|
|
|
73
123
|
async function create(args) {
|
|
74
124
|
const a = normalizeArgv(args);
|
|
75
|
-
assertKnownFlags(a, [...CREATE_VALUE_FLAGS, "--help", "-h"], CREATE_VALUE_FLAGS);
|
|
125
|
+
assertKnownFlags(a, [...CREATE_VALUE_FLAGS, "--key", "--help", "-h"], CREATE_VALUE_FLAGS);
|
|
76
126
|
const policyRaw = flagValue(a, "--policy");
|
|
77
|
-
const expiresAt = flagValue(a, "--expires");
|
|
78
|
-
const
|
|
127
|
+
const expiresAt = flagValue(a, "--expires-at") || undefined;
|
|
128
|
+
const capability = flagValue(a, "--capability");
|
|
129
|
+
const wantsKey = a.includes("--key");
|
|
130
|
+
const kind = flagValue(a, "--kind") || undefined;
|
|
131
|
+
const scopeRaw = flagValue(a, "--scope");
|
|
132
|
+
const spendCapRaw = flagValue(a, "--spend-cap");
|
|
79
133
|
const { projectId, rest } = resolveProjectSelector(a, { valueFlags: CREATE_VALUE_FLAGS });
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
missing: capabilityFlag ? "Missing <wallet>." : "Missing <wallet> and/or <capability>.",
|
|
134
|
+
const [wallet] = requirePositionalCount(rest, CREATE_VALUE_FLAGS, {
|
|
135
|
+
min: 1,
|
|
136
|
+
max: 1,
|
|
137
|
+
command: "run402 grants create <wallet> --capability <cap> [--project <project_id>] [--key]",
|
|
138
|
+
missing: "Missing <wallet>.",
|
|
86
139
|
});
|
|
87
|
-
|
|
88
|
-
|
|
140
|
+
if (!capability) {
|
|
141
|
+
fail({
|
|
142
|
+
code: "BAD_USAGE",
|
|
143
|
+
message: "Missing --capability <cap>.",
|
|
144
|
+
hint: "run402 grants create <wallet> --capability deploy [--project <project_id>]",
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (!wantsKey) {
|
|
148
|
+
const stray = KEY_ONLY_FLAGS.filter((f) => a.includes(f));
|
|
149
|
+
if (stray.length > 0) {
|
|
150
|
+
fail({
|
|
151
|
+
code: "BAD_USAGE",
|
|
152
|
+
message: `${stray.join(", ")} describe a grant key; add --key to mint one.`,
|
|
153
|
+
hint: "run402 grants create <wallet> --capability <cap> --key [--kind <kind>] [--scope <json>] [--spend-cap <json>]",
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
89
157
|
const policy = policyRaw != null ? parseFlagJson("--policy", policyRaw) : undefined;
|
|
158
|
+
let key;
|
|
159
|
+
if (wantsKey) {
|
|
160
|
+
key = {};
|
|
161
|
+
if (kind) key.kind = kind;
|
|
162
|
+
if (scopeRaw != null) key.scope = parseFlagJson("--scope", scopeRaw);
|
|
163
|
+
if (spendCapRaw != null) key.spendCap = parseFlagJson("--spend-cap", spendCapRaw);
|
|
164
|
+
if (expiresAt) key.expiresAt = expiresAt;
|
|
165
|
+
}
|
|
90
166
|
try {
|
|
91
167
|
const res = await getSdk().grants.create(projectId, {
|
|
92
168
|
wallet,
|
|
93
169
|
capability,
|
|
94
170
|
policy,
|
|
95
|
-
expiresAt
|
|
171
|
+
expiresAt,
|
|
172
|
+
key,
|
|
96
173
|
});
|
|
97
174
|
console.log(JSON.stringify(res, null, 2));
|
|
175
|
+
warnTokenOnce(res?.key);
|
|
98
176
|
} catch (err) {
|
|
99
177
|
reportSdkError(err);
|
|
100
178
|
}
|
|
101
179
|
}
|
|
102
180
|
|
|
103
|
-
async function
|
|
181
|
+
async function list(args) {
|
|
104
182
|
const a = normalizeArgv(args);
|
|
105
183
|
assertKnownFlags(a, ["--project", "--help", "-h"], ["--project"]);
|
|
106
184
|
const { projectId, rest } = resolveProjectSelector(a, { valueFlags: ["--project"] });
|
|
107
|
-
|
|
108
|
-
min:
|
|
109
|
-
max:
|
|
110
|
-
command: "run402 grants
|
|
111
|
-
missing: "
|
|
185
|
+
requirePositionalCount(rest, ["--project"], {
|
|
186
|
+
min: 0,
|
|
187
|
+
max: 0,
|
|
188
|
+
command: "run402 grants list [--project <project_id>]",
|
|
189
|
+
missing: "",
|
|
112
190
|
});
|
|
113
191
|
try {
|
|
114
|
-
console.log(JSON.stringify(await getSdk().grants.
|
|
192
|
+
console.log(JSON.stringify(await getSdk().grants.list(projectId), null, 2));
|
|
115
193
|
} catch (err) {
|
|
116
194
|
reportSdkError(err);
|
|
117
195
|
}
|
|
118
196
|
}
|
|
119
197
|
|
|
198
|
+
function oneIdCommand({ verb, idName, method }) {
|
|
199
|
+
return async (args) => {
|
|
200
|
+
const a = normalizeArgv(args);
|
|
201
|
+
assertKnownFlags(a, ["--project", "--help", "-h"], ["--project"]);
|
|
202
|
+
const { projectId, rest } = resolveProjectSelector(a, { valueFlags: ["--project"] });
|
|
203
|
+
const [id] = requirePositionalCount(rest, ["--project"], {
|
|
204
|
+
min: 1,
|
|
205
|
+
max: 1,
|
|
206
|
+
command: `run402 grants ${verb} <${idName}> [--project <project_id>]`,
|
|
207
|
+
missing: `Missing <${idName}>.`,
|
|
208
|
+
});
|
|
209
|
+
try {
|
|
210
|
+
const res = await getSdk().grants[method](projectId, id);
|
|
211
|
+
console.log(JSON.stringify(res, null, 2));
|
|
212
|
+
warnTokenOnce(res?.key);
|
|
213
|
+
} catch (err) {
|
|
214
|
+
reportSdkError(err);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
120
219
|
export async function run(sub, args) {
|
|
121
220
|
if (!sub || sub === "--help" || sub === "-h") {
|
|
122
221
|
console.log(HELP);
|
|
@@ -128,7 +227,10 @@ export async function run(sub, args) {
|
|
|
128
227
|
}
|
|
129
228
|
switch (sub) {
|
|
130
229
|
case "create": await create(args); break;
|
|
131
|
-
case "
|
|
230
|
+
case "list": await list(args); break;
|
|
231
|
+
case "revoke": await oneIdCommand({ verb: "revoke", idName: "grant_id", method: "revoke" })(args); break;
|
|
232
|
+
case "revoke-key": await oneIdCommand({ verb: "revoke-key", idName: "key_id", method: "revokeKey" })(args); break;
|
|
233
|
+
case "rotate-key": await oneIdCommand({ verb: "rotate-key", idName: "key_id", method: "rotateKey" })(args); break;
|
|
132
234
|
default:
|
|
133
235
|
failUnknownSubcommand("grants", sub);
|
|
134
236
|
}
|
package/lib/logs.mjs
CHANGED
|
@@ -62,7 +62,7 @@ Output:
|
|
|
62
62
|
|
|
63
63
|
Auth:
|
|
64
64
|
The project's cached service_key when present; otherwise your wallet /
|
|
65
|
-
session /
|
|
65
|
+
session / grant key, which the gateway authorizes with project.read — an org
|
|
66
66
|
member or teammate agent following an error fingerprint's drill-down needs
|
|
67
67
|
no project key.
|
|
68
68
|
|
package/lib/projects.mjs
CHANGED
|
@@ -164,7 +164,7 @@ Options:
|
|
|
164
164
|
|
|
165
165
|
Notes:
|
|
166
166
|
- Requires project.tenant_payments.read: org developer+ or a read-scoped
|
|
167
|
-
project grant
|
|
167
|
+
project grant or grant key.
|
|
168
168
|
- Output is the gateway's redacted payment page. Raw X-PAYMENT headers,
|
|
169
169
|
authorization hashes, and internal metadata are never returned.
|
|
170
170
|
`,
|
package/lib/sites.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
2
2
|
import { tmpdir } from "os";
|
|
3
3
|
import { dirname, join, resolve } from "path";
|
|
4
|
-
import { fileSetFromDir,
|
|
4
|
+
import { fileSetFromDir, grantKeyFromEnv } from "#sdk/node";
|
|
5
5
|
import { walletAuthHeaders, resolveProjectId, updateProject } from "./config.mjs";
|
|
6
6
|
import { resolveFilePathsInManifest } from "./manifest.mjs";
|
|
7
7
|
import { getSdk } from "./sdk.mjs";
|
|
@@ -220,9 +220,9 @@ async function deploy(args) {
|
|
|
220
220
|
if (opts.manifest) resolveFilePathsInManifest(manifest, dirname(resolve(opts.manifest)));
|
|
221
221
|
|
|
222
222
|
// Preserve the aggressive early exit when no local wallet is configured.
|
|
223
|
-
// A
|
|
223
|
+
// A grant key 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 (!
|
|
225
|
+
if (!grantKeyFromEnv()) walletAuthHeaders("/apply/v1/plans");
|
|
226
226
|
|
|
227
227
|
const stage = stageFilesToTempDir(manifest.files || []);
|
|
228
228
|
try {
|
|
@@ -289,9 +289,9 @@ async function deployDir(args) {
|
|
|
289
289
|
if (opts.target !== undefined) failUnsupportedTarget();
|
|
290
290
|
|
|
291
291
|
// Preserve the aggressive early exit when no local wallet is configured.
|
|
292
|
-
// A
|
|
292
|
+
// A grant key 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 (!
|
|
294
|
+
if (!grantKeyFromEnv()) walletAuthHeaders("/apply/v1/plans");
|
|
295
295
|
|
|
296
296
|
let fileSet;
|
|
297
297
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "run402",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.97.0",
|
|
4
4
|
"description": "CLI for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/sdk/dist/errors.d.ts
CHANGED
|
@@ -120,7 +120,7 @@ export declare class ProjectNotFound extends Run402Error {
|
|
|
120
120
|
* `NOT_AUTHORIZED`, `STEP_UP_REQUIRED`) — since 402 is reserved for
|
|
121
121
|
* genuine payment challenges, this now also covers non-payment 403 denials
|
|
122
122
|
* such as quota (`QUOTA_EXCEEDED`), lifecycle (`PROJECT_FROZEN` /
|
|
123
|
-
* `PROJECT_DORMANT`), and
|
|
123
|
+
* `PROJECT_DORMANT`), and grant-key spend-cap denials. Check `body.code` (or
|
|
124
124
|
* use `formatCanonicalErrorContext`-style parsing) to distinguish these
|
|
125
125
|
* from a true auth failure.
|
|
126
126
|
*/
|