@run402/sdk 4.93.2 → 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 +21 -19
- package/core-dist/config.d.ts +16 -2
- package/core-dist/config.js +52 -18
- package/core-dist/control-plane-session.d.ts +29 -14
- package/core-dist/control-plane-session.js +31 -6
- package/core-dist/keystore.js +7 -7
- package/core-dist/profiles.d.ts +3 -3
- package/core-dist/profiles.js +9 -7
- package/core-dist/{allowance-auth.d.ts → wallet-auth.d.ts} +6 -6
- package/core-dist/{allowance-auth.js → wallet-auth.js} +15 -15
- package/core-dist/wallet.d.ts +22 -0
- package/core-dist/{allowance.js → wallet.js} +20 -20
- package/core-dist/{write-auth-session.d.ts → write-approvals.d.ts} +28 -26
- package/core-dist/{write-auth-session.js → write-approvals.js} +19 -19
- package/dist/actions.d.ts +1 -1
- package/dist/actions.d.ts.map +1 -1
- package/dist/control-plane-credentials.d.ts +8 -8
- package/dist/control-plane-credentials.js +7 -7
- package/dist/credentials.d.ts +19 -19
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +1 -1
- package/dist/errors.d.ts +24 -26
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +34 -37
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +29 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -16
- package/dist/index.js.map +1 -1
- package/dist/kernel.d.ts +1 -1
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +10 -9
- package/dist/kernel.js.map +1 -1
- package/dist/namespaces/admin.d.ts +51 -120
- package/dist/namespaces/admin.d.ts.map +1 -1
- package/dist/namespaces/admin.js +41 -45
- package/dist/namespaces/admin.js.map +1 -1
- package/dist/namespaces/agent.d.ts +2 -2
- package/dist/namespaces/agent.d.ts.map +1 -1
- package/dist/namespaces/agent.js +2 -2
- package/dist/namespaces/agent.js.map +1 -1
- package/dist/namespaces/ai.d.ts +1 -1
- package/dist/namespaces/ai.js +1 -1
- package/dist/namespaces/billing.d.ts +6 -3
- package/dist/namespaces/billing.d.ts.map +1 -1
- package/dist/namespaces/billing.js.map +1 -1
- package/dist/namespaces/buzz-notifications.types.d.ts +1 -1
- package/dist/namespaces/buzz.d.ts +2 -2
- package/dist/namespaces/buzz.d.ts.map +1 -1
- package/dist/namespaces/buzz.js +2 -2
- package/dist/namespaces/buzz.js.map +1 -1
- package/dist/namespaces/buzz.types.d.ts +2 -2
- package/dist/namespaces/buzz.types.d.ts.map +1 -1
- package/dist/namespaces/credentials.types.d.ts +1 -1
- package/dist/namespaces/deploy.d.ts +1 -1
- package/dist/namespaces/deploy.js +5 -5
- package/dist/namespaces/deploy.js.map +1 -1
- package/dist/namespaces/deploy.types.d.ts +4 -4
- package/dist/namespaces/escalations.d.ts +1 -1
- package/dist/namespaces/escalations.js +1 -1
- package/dist/namespaces/escalations.types.d.ts +2 -2
- package/dist/namespaces/escalations.types.d.ts.map +1 -1
- package/dist/namespaces/events.types.d.ts +1 -1
- package/dist/namespaces/functions.d.ts +1 -1
- package/dist/namespaces/functions.js +1 -1
- package/dist/namespaces/gitvault.crypto.d.ts +1 -1
- package/dist/namespaces/gitvault.d.ts +24 -24
- package/dist/namespaces/gitvault.d.ts.map +1 -1
- package/dist/namespaces/gitvault.js +109 -109
- package/dist/namespaces/gitvault.js.map +1 -1
- package/dist/namespaces/me.d.ts +120 -0
- package/dist/namespaces/me.d.ts.map +1 -0
- package/dist/namespaces/me.js +34 -0
- package/dist/namespaces/me.js.map +1 -0
- package/dist/namespaces/org.d.ts +108 -6
- package/dist/namespaces/org.d.ts.map +1 -1
- package/dist/namespaces/org.js +63 -7
- package/dist/namespaces/org.js.map +1 -1
- package/dist/namespaces/org.types.d.ts +29 -9
- package/dist/namespaces/org.types.d.ts.map +1 -1
- package/dist/namespaces/pay.js +3 -3
- package/dist/namespaces/pay.js.map +1 -1
- package/dist/namespaces/projects.d.ts +9 -9
- package/dist/namespaces/projects.js +15 -15
- package/dist/namespaces/projects.js.map +1 -1
- package/dist/namespaces/projects.types.d.ts +28 -12
- package/dist/namespaces/projects.types.d.ts.map +1 -1
- package/dist/namespaces/rooms.d.ts +7 -7
- package/dist/namespaces/rooms.js +17 -17
- package/dist/namespaces/rooms.js.map +1 -1
- package/dist/namespaces/rooms.types.d.ts +9 -9
- package/dist/namespaces/rooms.types.d.ts.map +1 -1
- package/dist/namespaces/service.d.ts +1 -1
- package/dist/namespaces/service.js +1 -1
- package/dist/namespaces/{operator-session.d.ts → session.d.ts} +135 -51
- package/dist/namespaces/session.d.ts.map +1 -0
- package/dist/namespaces/{operator-session.js → session.js} +118 -30
- package/dist/namespaces/session.js.map +1 -0
- package/dist/namespaces/subdomains.d.ts +5 -5
- package/dist/namespaces/subdomains.d.ts.map +1 -1
- package/dist/namespaces/subdomains.js +6 -6
- package/dist/namespaces/subdomains.js.map +1 -1
- package/dist/namespaces/tier.d.ts +43 -20
- package/dist/namespaces/tier.d.ts.map +1 -1
- package/dist/namespaces/tier.js +7 -7
- package/dist/namespaces/tier.js.map +1 -1
- package/dist/namespaces/transfers.d.ts +74 -71
- package/dist/namespaces/transfers.d.ts.map +1 -1
- package/dist/namespaces/transfers.js +31 -38
- package/dist/namespaces/transfers.js.map +1 -1
- package/dist/namespaces/vouchers.d.ts +9 -10
- package/dist/namespaces/vouchers.d.ts.map +1 -1
- package/dist/namespaces/vouchers.js +4 -5
- package/dist/namespaces/vouchers.js.map +1 -1
- package/dist/namespaces/wallets.d.ts +72 -3
- package/dist/namespaces/wallets.d.ts.map +1 -1
- package/dist/namespaces/wallets.js +141 -3
- package/dist/namespaces/wallets.js.map +1 -1
- package/dist/namespaces/write-approval.d.ts +101 -0
- package/dist/namespaces/write-approval.d.ts.map +1 -0
- package/dist/namespaces/write-approval.js +62 -0
- package/dist/namespaces/write-approval.js.map +1 -0
- package/dist/node/actions-node.d.ts +3 -3
- package/dist/node/actions-node.d.ts.map +1 -1
- package/dist/node/actions-node.js +36 -36
- package/dist/node/actions-node.js.map +1 -1
- package/dist/node/{bearer-claim-key.d.ts → bearer-redeem-key.d.ts} +12 -12
- package/dist/node/bearer-redeem-key.d.ts.map +1 -0
- package/dist/node/{bearer-claim-key.js → bearer-redeem-key.js} +21 -21
- package/dist/node/bearer-redeem-key.js.map +1 -0
- package/dist/node/ci.d.ts +1 -1
- package/dist/node/ci.d.ts.map +1 -1
- package/dist/node/ci.js +6 -6
- package/dist/node/ci.js.map +1 -1
- package/dist/node/credentials.d.ts +17 -16
- package/dist/node/credentials.d.ts.map +1 -1
- package/dist/node/credentials.js +28 -27
- package/dist/node/credentials.js.map +1 -1
- package/dist/node/funding-recovery.d.ts +1 -1
- package/dist/node/funding-recovery.d.ts.map +1 -1
- package/dist/node/funding-recovery.js +1 -1
- package/dist/node/funding-recovery.js.map +1 -1
- package/dist/node/gitvault-apply.d.ts +1 -1
- package/dist/node/gitvault-apply.d.ts.map +1 -1
- package/dist/node/gitvault-handoff.d.ts +22 -22
- package/dist/node/gitvault-handoff.d.ts.map +1 -1
- package/dist/node/gitvault-handoff.js +45 -45
- package/dist/node/gitvault-handoff.js.map +1 -1
- package/dist/node/gitvault-prewarm.js +7 -7
- package/dist/node/gitvault-prewarm.js.map +1 -1
- package/dist/node/gitvault-restore.d.ts +1 -1
- package/dist/node/gitvault-restore.js +1 -1
- package/dist/node/index.d.ts +16 -16
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +15 -15
- package/dist/node/index.js.map +1 -1
- package/dist/node/org-adopt.d.ts +49 -0
- package/dist/node/org-adopt.d.ts.map +1 -0
- package/dist/node/org-adopt.js +77 -0
- package/dist/node/org-adopt.js.map +1 -0
- package/dist/node/paid-fetch.d.ts +7 -7
- package/dist/node/paid-fetch.d.ts.map +1 -1
- package/dist/node/paid-fetch.js +54 -54
- package/dist/node/paid-fetch.js.map +1 -1
- package/dist/node/preflight.d.ts +1 -1
- package/dist/node/preflight.d.ts.map +1 -1
- package/dist/node/preflight.js +1 -1
- package/dist/node/preflight.js.map +1 -1
- package/dist/node/source-scan.d.ts +1 -1
- package/dist/node/source-scan.js +2 -2
- package/dist/node/source-scan.js.map +1 -1
- package/dist/scoped.d.ts +3 -3
- package/dist/scoped.d.ts.map +1 -1
- package/dist/scoped.js +2 -2
- package/dist/scoped.js.map +1 -1
- package/package.json +1 -1
- package/core-dist/allowance.d.ts +0 -22
- package/core-dist/operator-session.d.ts +0 -80
- package/core-dist/operator-session.js +0 -154
- package/dist/namespaces/allowance.d.ts +0 -79
- package/dist/namespaces/allowance.d.ts.map +0 -1
- package/dist/namespaces/allowance.js +0 -149
- package/dist/namespaces/allowance.js.map +0 -1
- package/dist/namespaces/operator-session.d.ts.map +0 -1
- package/dist/namespaces/operator-session.js.map +0 -1
- package/dist/namespaces/operator.d.ts +0 -359
- package/dist/namespaces/operator.d.ts.map +0 -1
- package/dist/namespaces/operator.js +0 -272
- package/dist/namespaces/operator.js.map +0 -1
- package/dist/node/bearer-claim-key.d.ts.map +0 -1
- package/dist/node/bearer-claim-key.js.map +0 -1
- package/dist/node/operator-claim.d.ts +0 -49
- package/dist/node/operator-claim.d.ts.map +0 -1
- package/dist/node/operator-claim.js +0 -77
- package/dist/node/operator-claim.js.map +0 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Deployment summaries share the SDK workflow view. CLI writes redacted detail und
|
|
|
14
14
|
|
|
15
15
|
| Import | Use when |
|
|
16
16
|
|---|---|
|
|
17
|
-
| `@run402/sdk/node` | Running in Node 22 with the local profile state, project-key credential cache, and
|
|
17
|
+
| `@run402/sdk/node` | Running in Node 22 with the local profile state, project-key credential cache, and wallet. Auto-loads the configured API base, profile `credentials/project-keys.v1.json`, and signs x402 payments from the selected wallet or opaque signer. Includes `r.actions.run(...)`, `r.up(...)`, `r.sites.deployDir(dir)`, `fileSetFromDir(dir)`, `loadDeployManifest(path)`, `normalizeDeployManifest(input)`, and `resolveRun402TargetProfile()`. |
|
|
18
18
|
| `@run402/sdk` | Isomorphic — works in Node, Deno, Bun, V8 isolates. No filesystem access. Bring your own `CredentialsProvider` (a session-token shim, a remote vault, anything that resolves project keys + auth headers). |
|
|
19
19
|
|
|
20
20
|
The Node entry sends bounded client-version metadata on gateway requests using the unprefixed `Run402-Client` header, for example `surface="sdk", version="3.7.14", sdk="3.7.14"`. The CLI passes `surface: "cli"`, so gateway compatibility hints can distinguish CLI-created SDK traffic from direct SDK callers. Metadata never includes local paths, package manager details, wallet/org/project ids, secrets, or install confidence. The isomorphic entry does not send this header by default; pass `clientMetadata` explicitly only in runtimes where custom headers are expected.
|
|
@@ -78,14 +78,14 @@ provider controls API authentication; the x402 payer is resolved exactly once
|
|
|
78
78
|
in this order:
|
|
79
79
|
|
|
80
80
|
1. `paymentSigner` — an explicit async EVM signer provider (KMS/HSM friendly).
|
|
81
|
-
2. `
|
|
82
|
-
3. `credentials.
|
|
83
|
-
4. The Node default provider's active-profile
|
|
81
|
+
2. `walletPath` — an explicit local wallet file.
|
|
82
|
+
3. `credentials.readWallet()` — when a supplied provider implements it.
|
|
83
|
+
4. The Node default provider's active-profile wallet — only when the caller
|
|
84
84
|
did not supply a custom credentials provider.
|
|
85
85
|
|
|
86
86
|
Once a source is selected, the SDK never falls back to the ambient/global
|
|
87
|
-
wallet. `paymentSigner` and `
|
|
88
|
-
`PAYMENT_SOURCE_CONFLICT`. Passing both `credentials` and `
|
|
87
|
+
wallet. `paymentSigner` and `walletPath` together throw
|
|
88
|
+
`PAYMENT_SOURCE_CONFLICT`. Passing both `credentials` and `walletPath` is
|
|
89
89
|
valid: auth uses `credentials`, while payment intentionally uses that file.
|
|
90
90
|
`fetch` still takes precedence over built-in paid fetch, and
|
|
91
91
|
`disablePaidFetch: true` disables automatic payment entirely.
|
|
@@ -123,7 +123,7 @@ const payer = await r.paymentPayer();
|
|
|
123
123
|
|
|
124
124
|
The provider may return `null` for an unsupported Base network. Paid-fetch
|
|
125
125
|
initialization is lazy and retries after missing/recoverable local state, so a
|
|
126
|
-
long-lived client can start paying after its selected
|
|
126
|
+
long-lived client can start paying after its selected wallet/provider
|
|
127
127
|
becomes available without being reconstructed. `r.paymentPayer()` initializes
|
|
128
128
|
the selected source if necessary and returns only its source, rail, public
|
|
129
129
|
address(es), and network(s); it never returns a key, signed authorization, or
|
|
@@ -211,7 +211,7 @@ await r.actions.run({
|
|
|
211
211
|
});
|
|
212
212
|
```
|
|
213
213
|
|
|
214
|
-
Action identifiers are exported constants plus a string-literal union, so inputs narrow by `type`. `up` validates `run402.deploy.json` / `app.json` before any mutation, resolves the project as explicit `projectId` → `.run402/project.json` → manifest `project_id` → approved creation from `name`; global active state never selects a deployment target, then delegates to `r.project(id).apply(...)`. `name` is only project creation/link metadata; it is not a manifest field and never renames an existing project. If
|
|
214
|
+
Action identifiers are exported constants plus a string-literal union, so inputs narrow by `type`. `up` validates `run402.deploy.json` / `app.json` before any mutation, resolves the project as explicit `projectId` → `.run402/project.json` → manifest `project_id` → approved creation from `name`; global active state never selects a deployment target, then delegates to `r.project(id).apply(...)`. `name` is only project creation/link metadata; it is not a manifest field and never renames an existing project. If wallet/tier/project/link are already configured, `r.up()` can run the requested deploy with the default approval policy; pass `{ approval: "yes" }` only when you want recursive prerequisites/local writes to proceed unattended.
|
|
215
215
|
|
|
216
216
|
Before any gateway call, in every mode, `up` verifies every local file the manifest references (migration `sql_path`/`sql_file`, function `source`/`files`, site `{ path }` entries and `dir()` targets, `assets.put[].source`) and throws `MANIFEST_FILE_MISSING` (`details.missing[]` of `{ field_path, path, kind }`, one `create_file` next action per file); `manifest` pointing at a missing path is `MANIFEST_NOT_FOUND`, and with no manifest in the directory `UP_MANIFEST_REQUIRED` lists `details.nearby_manifests[]` one level down with a read-only `run_in_directory` action for a single candidate, or one unranked `select_application` action for multiple apps. The same check is exported standalone from `@run402/sdk/node`: `collectLocalFileReferences(spec)`, `findMissingLocalFileReferences(spec)`, `assertLocalFileReferencesExist(spec, { manifestPath? })`, plus the `manifestFileMissingError` / `manifestNotFoundError` builders.
|
|
217
217
|
|
|
@@ -303,14 +303,14 @@ const r = new Run402({
|
|
|
303
303
|
});
|
|
304
304
|
```
|
|
305
305
|
|
|
306
|
-
The `CredentialsProvider` interface has two required methods (`getAuth`, `getProject`) plus optional ones (`saveProject`, `removeProject`, `setActiveProject`, `
|
|
306
|
+
The `CredentialsProvider` interface has two required methods (`getAuth`, `getProject`) plus optional ones (`saveProject`, `removeProject`, `setActiveProject`, `readWallet`, `saveWallet`, …) for hosts that want full sticky-default behavior.
|
|
307
307
|
|
|
308
308
|
## Namespaces
|
|
309
309
|
|
|
310
310
|
| Namespace | Highlights |
|
|
311
311
|
|---|---|
|
|
312
312
|
| `actions` | Node entry only (`@run402/sdk/node`). Generic recursive action runner: `actions.run({ type: Run402Action.Up | ProjectsProvision | TierSet, ... })`; `r.up(input, opts)` is the convenience for repo-level manifest deploys. Recursive mutations are approval-gated; `mode: "check" | "printSpec" | "printManifest" | "plan" | { kind: "applyReviewed" }` distinguishes local validation, gateway review, and exact reviewed apply. Child gateway mutations derive idempotency keys from the root action. |
|
|
313
|
-
| `pay` | `fetch(url, init?, { maxUsdMicros?, idempotencyKey?, requireReceipt? })` — bounded arbitrary-URL x402 buyer; Node uses the selected
|
|
313
|
+
| `pay` | `fetch(url, init?, { maxUsdMicros?, idempotencyKey?, requireReceipt? })` — bounded arbitrary-URL x402 buyer; Node uses the selected wallet/signer and returns the response plus settlement and independently verified merchant evidence. |
|
|
314
314
|
| `projects` | `provision`, `delete`, `list`, `get`, `use`, `active`, `sql`, `rest`, `validateExpose`, `applyExpose`, `getExpose`, `getUsage`, `getSchema`, `info`, `keys`, `pin`, `getQuote`. `list`/`get`/`use` are server-authoritative; local key reads are moving to `credentials.projectKeys`. |
|
|
315
315
|
| `snapshots` | Internal project restore points: `create`, `list`, `get`, `restorePlan`, `restore`, `delete`. Restore is a two-step plan/confirm handshake. |
|
|
316
316
|
| `branches` | Contained project data branches: `create`, `list`, `renew`, `delete`. Branches default to expiring, noindex, sandboxed-email copies. |
|
|
@@ -323,23 +323,25 @@ The `CredentialsProvider` interface has two required methods (`getAuth`, `getPro
|
|
|
323
323
|
| `functions` | `deploy`, `invoke`, `logs` (`{ tail?, since?, requestId?, origin? }`; every entry carries `origin: "app" \| "platform"`, computed by the exported `classifyFunctionLogLine`, and `origin` filters client-side with `hidden: { platform, app }` counts), `logsByRequestId(projectId, requestId, { tail?, since?, origin?, functionName? })` (project-wide search for a `req_`/`fnrun_`/`fnatt_` id, the `x-run402-request-id` response header: reads every function, merges oldest-first, tags each entry with its `function`; also `r.project(id).functions.logsByRequestId`), `update`, `list`, `delete`, `rebuild`, `rebuildAll`, `runs.*` durable function requests |
|
|
324
324
|
| `jobs` | `submit`, `get`, `logs`, `cancel`, `purge` for platform-managed jobs |
|
|
325
325
|
| `secrets` | `set`, `list`, `delete` |
|
|
326
|
-
| `subdomains` | `
|
|
326
|
+
| `subdomains` | `add`, `list`, `delete` (most agents declare subdomains in `r.project(id).apply({ subdomains: { set: [...] } })` instead) |
|
|
327
327
|
| `domains` | The ProjectDomain lifecycle — the one surface for custom domains (web + email): `ensure` (connect / update desired state), `get`, `list`, `check` (refresh observations), `apply` (records Run402 has authority over), `repair`, `wait`, `testReceive`, `activate`, `disconnect`. `desired` carries `web`, `email`, and an optional `authority` — `"hosted_dns_zone"` is the root-domain path (one nameserver change at the registrar; Run402 applies every in-zone record and issues TLS). Every response carries `next_actions[]`. |
|
|
328
328
|
| `email` | `createMailbox`, `listMailboxes`, `setMailboxDefaults`, `updateMailbox`, `getMailbox`, `deleteMailbox`, `send`, `list`, `get`, `getRaw`, `webhooks.*` |
|
|
329
329
|
| `auth` | `requestMagicLink` (link/code/both), `verifyMagicLink`, `verifyEmailCode`, `createUser`, `inviteUser`, `setUserPassword`, `settings`, passkey registration/login/list/delete helpers, typed `providers`, `promote`, `demote` |
|
|
330
330
|
| `apps` | `browse`, `getApp`, `fork`, `publish`, `listVersions`, `updateVersion`, `deleteVersion` |
|
|
331
331
|
| `tier` | `set`, `status` (tier pricing lives on `r.projects.getQuote()`) |
|
|
332
332
|
| `billing` | `createEmailOrganization`, `linkWallet`, `createCheckout`, `setAutoRecharge`, `checkBalance`, `getOrganization`, `lookupOrganization`, `getHistory`, `balance`, `history` |
|
|
333
|
-
| `vouchers` | `redeem` (promo code →
|
|
333
|
+
| `vouchers` | `redeem` (promo code → the organization's allowance; safe to retry) |
|
|
334
334
|
| `contracts` | `provisionSigner`, `getSigner`, `listSigners`, `setRecovery`, `setLowBalanceAlert`, `call`, `read`, `callStatus`, `drain`, `deleteSigner` |
|
|
335
335
|
| `ai` | `translate`, `moderate`, `usage`, `generateImage` |
|
|
336
|
-
| `allowance` | `status`, `create`, `export`, `faucet` |
|
|
337
336
|
| `service` | `status`, `health` (no auth, no setup — works on a fresh install) |
|
|
338
|
-
| `admin` |
|
|
339
|
-
| `
|
|
337
|
+
| `admin` | Admin endpoints: messages/contact, notifications, staff-only per-project finance (`getProjectFinance`) |
|
|
338
|
+
| `session` | **A person's sign-in session** — distinct from the agent's per-wallet SIWX identity. One session class, graded by provenance (`grade`: `browser`, `loopback`, `device` — read-only). CLI seams: `buildCliAuthorizeUrl`/`exchangeCliToken` (loopback PKCE, `run402 login`) and `deviceStart`/`devicePoll` (RFC 8628, `run402 login --device`). Browser surface: email magic link / passkey / OAuth sign-in, `whoami`/`refresh`/`revoke`, step-up, authenticators, recovery. Carry a minted session SDK-wide with `controlPlaneSessionCredentials({ token })`. No MCP tool by design. |
|
|
339
|
+
| `writeApproval` | `requestChallenge`, `exchangeClaimCode` — the passkey-signed, `(action, target)`-scoped write approval a person without a wallet needs to provision, deploy, or write secrets (`run402 approve`); a gated write without one throws `WriteApprovalRequiredError`. |
|
|
340
|
+
| `me` | `overview()` (`GET /agent/v1/me/overview`, joined into `run402 org list`), `status()` (`GET /agent/v1/me/status`, read by `run402 doctor` and MCP `whoami`) |
|
|
340
341
|
| `identityLinks` | Public human/agent external identity attribution with a discriminated proof protocol. Agent `nostr.begin`/`complete` uses EOA + kind 1; human creation/revocation is browser-canonical. `list`, `getProof`, and `revoke` preserve multiple active and revoked records. Never accepts a Nostr secret and never grants authority. |
|
|
341
342
|
| `buzz` | Capability-detecting Buzz control plane. `offerAdoption` plus `humanAdoptionOffers` creates/reads/cancels durable HTTPS handoffs and creates human-bound attempts; direct `humanAdoptions` is advanced compatibility. `install/enroll` and typed community/enrollment methods preserve separate principals, bounded named-project grants, drift, and scoped revocation. `notifications` routes project events into a Buzz channel (`createRoute`/`list`/`get`/`update`/`pause`/`resume`/`rotate`/`revoke`/`test`/`deliveries`/`testAndWait` — configure → authorize → test → live; the signing secret never leaves the gateway). Buzz signing stays outside the SDK. |
|
|
342
|
-
| `
|
|
343
|
+
| `wallets` | The local wallet: `status`, `create`, `export`, `faucet`; plus `getLabel(address)` |
|
|
344
|
+
| `wallet(address)` | `getLabel()`, `setLabel(label)` — the signed server-side wallet label (gateway `/wallets/v1/:address/label`) surfaced in the console; pushed on `wallets use` unless `RUN402_WALLET_LABEL_SYNC=0`. Use the `r.wallet(address)` handle; `r.wallets.getLabel(address)` remains a bare read |
|
|
343
345
|
| `orgs` | **Org-owned control plane** (first-class orgs). `create`, `list`, `whoami` (the gateway-resolved control-plane identity) on the collection; the scoped `r.org(id)` sub-client (org analog of `r.project(id)`) adds `get`, `rename`, `setPayoutWallet`, `members.*` (`list`/`add`/`setRole`/`revoke`), `invites.*` (`list`/`create`/`revoke`), `audit`. Org create/read/rename summaries include `tier`, `lease_started_at`, and `lease_expires_at`. |
|
|
344
346
|
| `grants` | `create`, `revoke` — per-project capability grants (e.g. `"deploy"`, `"functions:write"`) for agent/CI principals; owner-gated, also reachable project-scoped as `r.project(id).grants` |
|
|
345
347
|
| `events` | `list`, `listForOrg` — the cursored project events feed ("what happened since I last looked"): deploy activations, suspensions, transfers, lifecycle cliffs, each with platform-suggested `next_actions`, plus app-emitted business facts (`source: "app"`) alongside the platform's own (`source: "platform"`) — filter with `{ source?, eventType? }`. Opaque store-and-echo cursor; `reset: true` + `earliest_cursor` instead of errors on expiry. Also reachable project-scoped as `r.project(id).events` |
|
|
@@ -823,12 +825,12 @@ const minted = await r.gitvault.handoff({
|
|
|
823
825
|
});
|
|
824
826
|
console.log(minted.handoff_key); // kgh1_… — print it ALONE, this is the only copy
|
|
825
827
|
|
|
826
|
-
// on another machine, with no shared keystore or
|
|
828
|
+
// on another machine, with no shared keystore or wallet:
|
|
827
829
|
const resumed = await r.gitvault.resume({ key: "kgh1_…" });
|
|
828
830
|
console.log(resumed.restored.dir); // the fresh checkout, dirty state reapplied
|
|
829
831
|
```
|
|
830
832
|
|
|
831
|
-
**Invite / join — a second agent into the SAME work, dirty tree included (kygit-invite).** The second claim kind beside handoff/resume, sharing the same crypto/keystore/restore machinery, kind-parameterized: a Handoff passes the work on and the sender stops; an Invite grows the team while the sender keeps working. `r.gitvault.invite({ project_id, note, roomKey?, role?, ttlSeconds?, includeSensitive?, program?, model?, sessionKey? })` captures the checkpoint exactly like `handoff` — the inviter's own worktree, index, branch, refs, and access are all untouched — registers the inviter's OWN presence in the invite's room (the project's default room, or `roomKey` for a named org room) BEFORE minting, mints a single-use bearer `kgi1_<base64url(invite_id[16] ‖ master_secret[32])>` key (returned in `invite_key`, the ONLY copy), and posts ONE room message naming the checkpoint and invite id (never the key) — a presence or fact-post failure is reported in `inviter_presence`/`
|
|
833
|
+
**Invite / join — a second agent into the SAME work, dirty tree included (kygit-invite).** The second claim kind beside handoff/resume, sharing the same crypto/keystore/restore machinery, kind-parameterized: a Handoff passes the work on and the sender stops; an Invite grows the team while the sender keeps working. `r.gitvault.invite({ project_id, note, roomKey?, role?, ttlSeconds?, includeSensitive?, program?, model?, sessionKey? })` captures the checkpoint exactly like `handoff` — the inviter's own worktree, index, branch, refs, and access are all untouched — registers the inviter's OWN presence in the invite's room (the project's default room, or `roomKey` for a named org room) BEFORE minting, mints a single-use bearer `kgi1_<base64url(invite_id[16] ‖ master_secret[32])>` key (returned in `invite_key`, the ONLY copy), and posts ONE room message naming the checkpoint and invite id (never the key) — a presence or fact-post failure is reported in `inviter_presence`/`room_message` and never voids the mint. `r.gitvault.join({ key, to?, program?, model?, sessionKey? })` claims it (a same-principal replay dedups safely), clones a fresh checkout, reapplies the checkpoint with `git stash apply --index`, pins the invite's OWN room locally (`r402.room`), registers this session's presence, posts ONE arrival message, and returns the inviter (name, labels, liveness), `live_presences`, the catch-up `cursor`, and the last few `recent_messages` — the joiner's first message needs no lookup. `r.gitvault.listInvites`/`revokeInvite` manage outstanding keys. A `kgh1_…` key handed to `join()`, or a `kgi1_…` key handed to `resume()`, is refused BY NAME (`INVITE_KEY_WRONG_KIND`/`HANDOFF_KEY_WRONG_KIND`) before any network call.
|
|
832
834
|
|
|
833
835
|
```ts
|
|
834
836
|
const minted = await r.gitvault.invite({
|
|
@@ -837,7 +839,7 @@ const minted = await r.gitvault.invite({
|
|
|
837
839
|
});
|
|
838
840
|
console.log(minted.invite_key); // kgi1_… — print it ALONE, this is the only copy
|
|
839
841
|
|
|
840
|
-
// on another machine, with no shared keystore or
|
|
842
|
+
// on another machine, with no shared keystore or wallet:
|
|
841
843
|
const joined = await r.gitvault.join({ key: "kgi1_…" });
|
|
842
844
|
console.log(joined.restored.dir); // the fresh checkout, dirty state reapplied
|
|
843
845
|
console.log(joined.inviter?.name); // who invited you, and whether they're still live
|
package/core-dist/config.d.ts
CHANGED
|
@@ -39,7 +39,7 @@ export declare function getProfilesDir(): string;
|
|
|
39
39
|
* The effective config directory for the *active* wallet. The `default` wallet
|
|
40
40
|
* resolves to the base dir (zero migration for existing single-wallet
|
|
41
41
|
* installs); any named wallet resolves to `{base}/profiles/<name>`. Because
|
|
42
|
-
* keystore,
|
|
42
|
+
* keystore, wallet, and meta paths all derive from this, switching the
|
|
43
43
|
* profile env var moves the whole wallet bundle atomically — and the SDK/MCP
|
|
44
44
|
* inherit profile selection for free.
|
|
45
45
|
*/
|
|
@@ -57,5 +57,19 @@ export declare function configureApiBase(apiBase: string, options?: Omit<ApiTarg
|
|
|
57
57
|
export declare function getConfiguredApiBase(): string | null;
|
|
58
58
|
export declare function getApiTargetKind(): ApiTargetKind;
|
|
59
59
|
export declare function isCoreApiTarget(): boolean;
|
|
60
|
-
|
|
60
|
+
/**
|
|
61
|
+
* The file that holds a wallet's key, in the base config dir (the `default`
|
|
62
|
+
* wallet) and in each `profiles/<name>/` directory.
|
|
63
|
+
*/
|
|
64
|
+
export declare const WALLET_FILE_NAME = "wallet.json";
|
|
65
|
+
/**
|
|
66
|
+
* One-time local move of the wallet key file to its current name: in the base
|
|
67
|
+
* config dir and every `profiles/<name>/` directory, `allowance.json` is
|
|
68
|
+
* renamed to `wallet.json` (an atomic same-directory rename) when
|
|
69
|
+
* `wallet.json` is absent, then tightened to 0600. A directory that already
|
|
70
|
+
* holds `wallet.json` is left untouched. Runs once per base dir per process;
|
|
71
|
+
* nothing reads the previous name afterwards.
|
|
72
|
+
*/
|
|
73
|
+
export declare function moveWalletFiles(): void;
|
|
74
|
+
export declare function getWalletPath(): string;
|
|
61
75
|
//# sourceMappingURL=config.d.ts.map
|
package/core-dist/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
-
import { existsSync, renameSync, mkdirSync, chmodSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, renameSync, mkdirSync, chmodSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { randomBytes } from "node:crypto";
|
|
5
5
|
import { describeRejectedValue } from "./redact.js";
|
|
6
6
|
export const DEFAULT_API_BASE = "https://api.run402.com";
|
|
@@ -118,7 +118,7 @@ export function getProfilesDir() {
|
|
|
118
118
|
* The effective config directory for the *active* wallet. The `default` wallet
|
|
119
119
|
* resolves to the base dir (zero migration for existing single-wallet
|
|
120
120
|
* installs); any named wallet resolves to `{base}/profiles/<name>`. Because
|
|
121
|
-
* keystore,
|
|
121
|
+
* keystore, wallet, and meta paths all derive from this, switching the
|
|
122
122
|
* profile env var moves the whole wallet bundle atomically — and the SDK/MCP
|
|
123
123
|
* inherit profile selection for free.
|
|
124
124
|
*/
|
|
@@ -232,27 +232,61 @@ export function isCoreApiTarget() {
|
|
|
232
232
|
return true;
|
|
233
233
|
return false;
|
|
234
234
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
235
|
+
/**
|
|
236
|
+
* The file that holds a wallet's key, in the base config dir (the `default`
|
|
237
|
+
* wallet) and in each `profiles/<name>/` directory.
|
|
238
|
+
*/
|
|
239
|
+
export const WALLET_FILE_NAME = "wallet.json";
|
|
240
|
+
/** The name the wallet key file had before it was `wallet.json`. */
|
|
241
|
+
const PREVIOUS_WALLET_FILE_NAME = "allowance.json";
|
|
242
|
+
const walletFilesMovedFor = new Set();
|
|
243
|
+
/**
|
|
244
|
+
* One-time local move of the wallet key file to its current name: in the base
|
|
245
|
+
* config dir and every `profiles/<name>/` directory, `allowance.json` is
|
|
246
|
+
* renamed to `wallet.json` (an atomic same-directory rename) when
|
|
247
|
+
* `wallet.json` is absent, then tightened to 0600. A directory that already
|
|
248
|
+
* holds `wallet.json` is left untouched. Runs once per base dir per process;
|
|
249
|
+
* nothing reads the previous name afterwards.
|
|
250
|
+
*/
|
|
251
|
+
export function moveWalletFiles() {
|
|
252
|
+
const base = getConfigBaseDir();
|
|
253
|
+
if (walletFilesMovedFor.has(base))
|
|
254
|
+
return;
|
|
255
|
+
walletFilesMovedFor.add(base);
|
|
256
|
+
const dirs = [base];
|
|
257
|
+
try {
|
|
258
|
+
for (const entry of readdirSync(join(base, "profiles"), { withFileTypes: true })) {
|
|
259
|
+
if (entry.isDirectory())
|
|
260
|
+
dirs.push(join(base, "profiles", entry.name));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// No profiles directory yet.
|
|
265
|
+
}
|
|
266
|
+
for (const dir of dirs) {
|
|
267
|
+
const from = join(dir, PREVIOUS_WALLET_FILE_NAME);
|
|
268
|
+
const to = join(dir, WALLET_FILE_NAME);
|
|
269
|
+
if (existsSync(to) || !existsSync(from))
|
|
270
|
+
continue;
|
|
271
|
+
try {
|
|
272
|
+
renameSync(from, to);
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
248
277
|
try {
|
|
249
|
-
chmodSync(
|
|
278
|
+
chmodSync(to, 0o600);
|
|
250
279
|
}
|
|
251
280
|
catch {
|
|
252
281
|
// Best-effort (e.g. Windows / exotic filesystems). Read-time self-heal
|
|
253
|
-
// in
|
|
282
|
+
// in readWallet() is the backstop.
|
|
254
283
|
}
|
|
255
284
|
}
|
|
256
|
-
|
|
285
|
+
}
|
|
286
|
+
export function getWalletPath() {
|
|
287
|
+
if (process.env.RUN402_WALLET_PATH)
|
|
288
|
+
return process.env.RUN402_WALLET_PATH;
|
|
289
|
+
moveWalletFiles();
|
|
290
|
+
return join(getConfigDir(), WALLET_FILE_NAME);
|
|
257
291
|
}
|
|
258
292
|
//# sourceMappingURL=config.js.map
|
|
@@ -1,33 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* (`
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
2
|
+
* The cached **sign-in session** — a person's one Run402 session, bound to
|
|
3
|
+
* their principal and graded by how it was minted: `loopback` (`run402 login`,
|
|
4
|
+
* the loopback-PKCE browser ceremony; full, step-up-able) or `device`
|
|
5
|
+
* (`run402 login --device`, RFC 8628; read-only — the gateway refuses every
|
|
6
|
+
* mutation with `SESSION_READ_ONLY`). The wire token class is
|
|
7
|
+
* `control_plane_session`. The session authorizes most control-plane writes,
|
|
8
|
+
* but provisioning, deploying, and writing secrets additionally need a
|
|
9
|
+
* passkey-signed **write approval** (`X-Run402-Write-Approval`, minted by
|
|
10
|
+
* `run402 approve`; see `write-approvals.ts`). Cached at the BASE config dir
|
|
11
|
+
* (principal-scoped, shared across local named wallets), mode 0600 — the token
|
|
12
|
+
* is as sensitive as the wallet key.
|
|
12
13
|
*
|
|
13
14
|
* Stored shape vs the gateway payload: the gateway returns a relative
|
|
14
15
|
* `expires_in` (seconds); we persist the absolute `expires_at` (epoch ms) so a
|
|
15
16
|
* cached session can be expiry-checked without knowing when it was written.
|
|
16
17
|
*/
|
|
18
|
+
/** How a sign-in session was minted, as the gateway reports it. */
|
|
19
|
+
export type SessionGrade = "browser" | "loopback" | "device";
|
|
17
20
|
export interface ControlPlaneSessionCache {
|
|
18
21
|
control_plane_session_token: string;
|
|
19
22
|
token_type: string;
|
|
20
|
-
|
|
23
|
+
grade: SessionGrade;
|
|
21
24
|
principal_id: string;
|
|
22
25
|
amr: string[];
|
|
23
26
|
/** Epoch ms when the session expires (issued_at + expires_in). */
|
|
24
27
|
expires_at: number;
|
|
25
28
|
}
|
|
26
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* The token payload returned by `POST /agent/v1/control-plane/cli/token`
|
|
31
|
+
* (grade `loopback`) and `POST /agent/v1/control-plane/cli/device/token`
|
|
32
|
+
* (grade `device`).
|
|
33
|
+
*/
|
|
27
34
|
export interface ControlPlaneSessionTokenResponse {
|
|
28
35
|
control_plane_session_token: string;
|
|
29
36
|
token_type?: string;
|
|
30
|
-
|
|
37
|
+
grade?: string;
|
|
31
38
|
principal_id?: string;
|
|
32
39
|
amr?: string[];
|
|
33
40
|
expires_in?: number;
|
|
@@ -45,7 +52,7 @@ export declare function getControlPlaneSessionPath(): string;
|
|
|
45
52
|
export declare function readControlPlaneSession(path?: string): ControlPlaneSessionCache | null;
|
|
46
53
|
/** Persist a control-plane session atomically (temp-file + rename), mode 0600. */
|
|
47
54
|
export declare function saveControlPlaneSession(data: ControlPlaneSessionCache, path?: string): void;
|
|
48
|
-
/** Delete the cached
|
|
55
|
+
/** Delete the cached sign-in session — local half of `run402 logout`. Idempotent. */
|
|
49
56
|
export declare function clearControlPlaneSession(path?: string): void;
|
|
50
57
|
/** Whether a cached session is past its usable life (with a small skew buffer). */
|
|
51
58
|
export declare function isControlPlaneSessionExpired(session: ControlPlaneSessionCache, nowMs?: number, skewMs?: number): boolean;
|
|
@@ -53,4 +60,12 @@ export declare function isControlPlaneSessionExpired(session: ControlPlaneSessio
|
|
|
53
60
|
export declare function loadLiveControlPlaneSession(path?: string, nowMs?: number): ControlPlaneSessionCache | null;
|
|
54
61
|
/** Map a gateway token payload (relative `expires_in`) into the cached shape (absolute `expires_at`). */
|
|
55
62
|
export declare function controlPlaneSessionFromTokenResponse(resp: ControlPlaneSessionTokenResponse, nowMs?: number): ControlPlaneSessionCache;
|
|
63
|
+
/**
|
|
64
|
+
* Delete the session caches of the retired email-scoped session
|
|
65
|
+
* (`{base}/operator-session.json`) and the retired approval cache file
|
|
66
|
+
* (`{base}/write-auth-session.json`). Neither is read any more; `run402 login`
|
|
67
|
+
* and `run402 logout` call this so a stale token does not linger on disk.
|
|
68
|
+
* Best-effort and idempotent.
|
|
69
|
+
*/
|
|
70
|
+
export declare function clearRetiredSessionCaches(): void;
|
|
56
71
|
//# sourceMappingURL=control-plane-session.d.ts.map
|
|
@@ -10,6 +10,14 @@ export function getControlPlaneSessionPath() {
|
|
|
10
10
|
return (process.env.RUN402_CONTROL_PLANE_SESSION_PATH ||
|
|
11
11
|
join(getConfigBaseDir(), "control-plane-session.json"));
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Read a wire grade. An unknown or absent value is treated as `device` (the
|
|
15
|
+
* read-only grade) so a cache the client cannot classify never claims more
|
|
16
|
+
* authority than the gateway will grant.
|
|
17
|
+
*/
|
|
18
|
+
function toGrade(raw) {
|
|
19
|
+
return raw === "browser" || raw === "loopback" || raw === "device" ? raw : "device";
|
|
20
|
+
}
|
|
13
21
|
/** Tighten 0600 if group/other-readable, warning once. Best-effort, POSIX-only. */
|
|
14
22
|
function selfHealPermissions(p) {
|
|
15
23
|
if (process.platform === "win32")
|
|
@@ -50,20 +58,20 @@ export function readControlPlaneSession(path) {
|
|
|
50
58
|
return null;
|
|
51
59
|
}
|
|
52
60
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
53
|
-
throw new Error("control-plane-session.json must contain a JSON object. Delete it and run 'run402
|
|
61
|
+
throw new Error("control-plane-session.json must contain a JSON object. Delete it and run 'run402 login' to recreate it.");
|
|
54
62
|
}
|
|
55
63
|
const data = parsed;
|
|
56
64
|
if (typeof data.control_plane_session_token !== "string" ||
|
|
57
65
|
data.control_plane_session_token.length === 0) {
|
|
58
|
-
throw new Error("control-plane-session.json missing valid 'control_plane_session_token'. Run 'run402
|
|
66
|
+
throw new Error("control-plane-session.json missing valid 'control_plane_session_token'. Run 'run402 login' to refresh it.");
|
|
59
67
|
}
|
|
60
68
|
if (typeof data.expires_at !== "number" || !Number.isFinite(data.expires_at)) {
|
|
61
|
-
throw new Error("control-plane-session.json missing valid 'expires_at'. Run 'run402
|
|
69
|
+
throw new Error("control-plane-session.json missing valid 'expires_at'. Run 'run402 login' to refresh it.");
|
|
62
70
|
}
|
|
63
71
|
return {
|
|
64
72
|
control_plane_session_token: data.control_plane_session_token,
|
|
65
73
|
token_type: typeof data.token_type === "string" ? data.token_type : "Bearer",
|
|
66
|
-
|
|
74
|
+
grade: toGrade(data.grade),
|
|
67
75
|
principal_id: typeof data.principal_id === "string" ? data.principal_id : "",
|
|
68
76
|
amr: Array.isArray(data.amr) ? data.amr.filter((a) => typeof a === "string") : [],
|
|
69
77
|
expires_at: data.expires_at,
|
|
@@ -79,7 +87,7 @@ export function saveControlPlaneSession(data, path) {
|
|
|
79
87
|
renameSync(tmp, p);
|
|
80
88
|
chmodSync(p, 0o600);
|
|
81
89
|
}
|
|
82
|
-
/** Delete the cached
|
|
90
|
+
/** Delete the cached sign-in session — local half of `run402 logout`. Idempotent. */
|
|
83
91
|
export function clearControlPlaneSession(path) {
|
|
84
92
|
const p = path ?? getControlPlaneSessionPath();
|
|
85
93
|
try {
|
|
@@ -105,10 +113,27 @@ export function controlPlaneSessionFromTokenResponse(resp, nowMs = Date.now()) {
|
|
|
105
113
|
return {
|
|
106
114
|
control_plane_session_token: resp.control_plane_session_token,
|
|
107
115
|
token_type: resp.token_type ?? "Bearer",
|
|
108
|
-
|
|
116
|
+
grade: toGrade(resp.grade),
|
|
109
117
|
principal_id: resp.principal_id ?? "",
|
|
110
118
|
amr: Array.isArray(resp.amr) ? resp.amr.filter((a) => typeof a === "string") : [],
|
|
111
119
|
expires_at: nowMs + (typeof resp.expires_in === "number" ? resp.expires_in : 0) * 1000,
|
|
112
120
|
};
|
|
113
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Delete the session caches of the retired email-scoped session
|
|
124
|
+
* (`{base}/operator-session.json`) and the retired approval cache file
|
|
125
|
+
* (`{base}/write-auth-session.json`). Neither is read any more; `run402 login`
|
|
126
|
+
* and `run402 logout` call this so a stale token does not linger on disk.
|
|
127
|
+
* Best-effort and idempotent.
|
|
128
|
+
*/
|
|
129
|
+
export function clearRetiredSessionCaches() {
|
|
130
|
+
for (const name of ["operator-session.json", "write-auth-session.json"]) {
|
|
131
|
+
try {
|
|
132
|
+
rmSync(join(getConfigBaseDir(), name), { force: true });
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Best-effort: a failed unlink never blocks login or logout.
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
114
139
|
//# sourceMappingURL=control-plane-session.js.map
|
package/core-dist/keystore.js
CHANGED
|
@@ -3,7 +3,7 @@ import { dirname, join } from "node:path";
|
|
|
3
3
|
import { randomBytes } from "node:crypto";
|
|
4
4
|
import { getLegacyProjectsPath, getProjectCredentialsPath } from "./config.js";
|
|
5
5
|
import { clearActiveProjectId as clearProfileActiveProjectId, getActiveProjectId as getProfileActiveProjectId, recordMigration, setActiveProjectId as setProfileActiveProjectId, } from "./profile-state.js";
|
|
6
|
-
import {
|
|
6
|
+
import { readWallet } from "./wallet.js";
|
|
7
7
|
function withFileLock(path, fn, { retries = 200, delayMs = 20 } = {}) {
|
|
8
8
|
const lockDir = path + ".lock";
|
|
9
9
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -99,7 +99,7 @@ function migrateLegacyProjectsJson(targetPath) {
|
|
|
99
99
|
// Scoped the same way every other write in this module now is (see
|
|
100
100
|
// `currentPrincipal`'s doc comment) — an unscoped write here is exactly
|
|
101
101
|
// what poisons the "unknown"-principal bucket for good on a machine that
|
|
102
|
-
// migrates before its wallet
|
|
102
|
+
// migrates before its wallet exists yet.
|
|
103
103
|
setProfileActiveProjectId(legacy.active_project_id, undefined, { principal: currentPrincipal() });
|
|
104
104
|
}
|
|
105
105
|
recordMigration("projects_json_import", {
|
|
@@ -174,7 +174,7 @@ export function removeProject(projectId, path) {
|
|
|
174
174
|
}
|
|
175
175
|
/**
|
|
176
176
|
* The active-project scope's `principal`, derived from the CURRENT wallet's
|
|
177
|
-
*
|
|
177
|
+
* wallet — matching exactly what `NodeCredentialsProvider.setActiveProject`
|
|
178
178
|
* (sdk/src/node/credentials.ts) writes after `projects.provision` /
|
|
179
179
|
* `projects.use`. Without this, every reader here used the profile-state
|
|
180
180
|
* module's own default (empty) scope, which resolves to a FIXED
|
|
@@ -185,7 +185,7 @@ export function removeProject(projectId, path) {
|
|
|
185
185
|
* `profile-state.ts#getActiveProjectId` happens to agree), but once the
|
|
186
186
|
* "unknown" bucket is EVER populated by any principal-less write — the
|
|
187
187
|
* one-time legacy `projects.json` migration below, or any provision/`use`
|
|
188
|
-
* call made before this machine had a wallet
|
|
188
|
+
* call made before this machine had a wallet — it never gets
|
|
189
189
|
* updated again (real wallet operations write to the principal-keyed
|
|
190
190
|
* bucket, not "unknown") and PERMANENTLY shadows every later wallet-scoped
|
|
191
191
|
* activation for every caller that reads through this module: `resolveProjectId`
|
|
@@ -197,13 +197,13 @@ export function removeProject(projectId, path) {
|
|
|
197
197
|
* stale "unknown"-bucket entry first and never falls through to the
|
|
198
198
|
* freshly-updated flat value.
|
|
199
199
|
*
|
|
200
|
-
* Best-effort: an unreadable/malformed
|
|
200
|
+
* Best-effort: an unreadable/malformed wallet degrades to `null` (the
|
|
201
201
|
* "unknown" bucket callers tolerate), never a
|
|
202
|
-
* throw — this is a read-scoping concern, not
|
|
202
|
+
* throw — this is a read-scoping concern, not a wallet-validity one.
|
|
203
203
|
*/
|
|
204
204
|
function currentPrincipal() {
|
|
205
205
|
try {
|
|
206
|
-
return
|
|
206
|
+
return readWallet()?.address ?? null;
|
|
207
207
|
}
|
|
208
208
|
catch {
|
|
209
209
|
return null;
|
package/core-dist/profiles.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A "wallet" is a whole config directory. The reserved `default` wallet lives
|
|
5
5
|
* at the base config dir (zero migration for existing installs); every named
|
|
6
|
-
* wallet lives under `{base}/profiles/<name>/` with its own `
|
|
6
|
+
* wallet lives under `{base}/profiles/<name>/` with its own `wallet.json`,
|
|
7
7
|
* `projects.json`, and this module's non-secret `meta.json`.
|
|
8
8
|
*
|
|
9
9
|
* Two levels of active state, mirroring the wallet → project model:
|
|
@@ -40,10 +40,10 @@ export declare function profileDir(name: string): string;
|
|
|
40
40
|
export declare function ensureProfileDir(name: string): string;
|
|
41
41
|
export declare function readMeta(name: string): ProfileMeta | null;
|
|
42
42
|
export declare function writeMeta(name: string, meta: ProfileMeta): void;
|
|
43
|
-
/** True when a wallet's `
|
|
43
|
+
/** True when a wallet's `wallet.json` exists on disk. */
|
|
44
44
|
export declare function profileExists(name: string): boolean;
|
|
45
45
|
/**
|
|
46
|
-
* All wallet names on disk: `default` (only if a root
|
|
46
|
+
* All wallet names on disk: `default` (only if a root wallet.json exists)
|
|
47
47
|
* plus every valid directory under `profiles/`.
|
|
48
48
|
*/
|
|
49
49
|
export declare function listProfileNames(): string[];
|
package/core-dist/profiles.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A "wallet" is a whole config directory. The reserved `default` wallet lives
|
|
5
5
|
* at the base config dir (zero migration for existing installs); every named
|
|
6
|
-
* wallet lives under `{base}/profiles/<name>/` with its own `
|
|
6
|
+
* wallet lives under `{base}/profiles/<name>/` with its own `wallet.json`,
|
|
7
7
|
* `projects.json`, and this module's non-secret `meta.json`.
|
|
8
8
|
*
|
|
9
9
|
* Two levels of active state, mirroring the wallet → project model:
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { readFileSync, writeFileSync, mkdirSync, renameSync, chmodSync, existsSync, readdirSync, rmSync, } from "node:fs";
|
|
18
18
|
import { dirname, join } from "node:path";
|
|
19
19
|
import { randomBytes } from "node:crypto";
|
|
20
|
-
import { getConfigBaseDir, getProfilesDir, DEFAULT_PROFILE, isValidProfileName, } from "./config.js";
|
|
20
|
+
import { getConfigBaseDir, getProfilesDir, DEFAULT_PROFILE, isValidProfileName, moveWalletFiles, WALLET_FILE_NAME, } from "./config.js";
|
|
21
21
|
import { describeRejectedValue } from "./redact.js";
|
|
22
22
|
function atomicWrite(p, content, mode) {
|
|
23
23
|
const dir = dirname(p);
|
|
@@ -114,17 +114,19 @@ export function writeMeta(name, meta) {
|
|
|
114
114
|
atomicWrite(metaPath(name), JSON.stringify(meta, null, 2), 0o600);
|
|
115
115
|
}
|
|
116
116
|
// --- enumeration + lifecycle ---
|
|
117
|
-
/** True when a wallet's `
|
|
117
|
+
/** True when a wallet's `wallet.json` exists on disk. */
|
|
118
118
|
export function profileExists(name) {
|
|
119
|
-
|
|
119
|
+
moveWalletFiles();
|
|
120
|
+
return existsSync(join(profileDir(name), WALLET_FILE_NAME));
|
|
120
121
|
}
|
|
121
122
|
/**
|
|
122
|
-
* All wallet names on disk: `default` (only if a root
|
|
123
|
+
* All wallet names on disk: `default` (only if a root wallet.json exists)
|
|
123
124
|
* plus every valid directory under `profiles/`.
|
|
124
125
|
*/
|
|
125
126
|
export function listProfileNames() {
|
|
127
|
+
moveWalletFiles();
|
|
126
128
|
const names = [];
|
|
127
|
-
if (existsSync(join(getConfigBaseDir(),
|
|
129
|
+
if (existsSync(join(getConfigBaseDir(), WALLET_FILE_NAME))) {
|
|
128
130
|
names.push(DEFAULT_PROFILE);
|
|
129
131
|
}
|
|
130
132
|
try {
|
|
@@ -184,7 +186,7 @@ export function renameProfile(oldName, newName) {
|
|
|
184
186
|
/* best-effort */
|
|
185
187
|
}
|
|
186
188
|
const base = getConfigBaseDir();
|
|
187
|
-
for (const f of [
|
|
189
|
+
for (const f of [WALLET_FILE_NAME, "projects.json", "meta.json"]) {
|
|
188
190
|
const src = join(base, f);
|
|
189
191
|
if (existsSync(src))
|
|
190
192
|
renameSync(src, join(dest, f));
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* SIWX auth helper — generates SIWX (Sign-In With X / EIP-4361) headers for Run402 API.
|
|
3
3
|
* Uses @noble/curves (lighter than viem) for signing.
|
|
4
4
|
*/
|
|
5
|
-
import type {
|
|
5
|
+
import type { WalletData } from "./wallet.js";
|
|
6
6
|
export interface SIWxAuthHeaders {
|
|
7
7
|
"SIGN-IN-WITH-X": string;
|
|
8
8
|
}
|
|
@@ -22,7 +22,7 @@ interface SIWEMessageOpts {
|
|
|
22
22
|
resources?: string[];
|
|
23
23
|
}
|
|
24
24
|
export interface SIWxAuthOptions extends SIWEMessageOpts {
|
|
25
|
-
|
|
25
|
+
wallet: Pick<WalletData, "address" | "privateKey">;
|
|
26
26
|
type?: "eip191";
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
@@ -33,10 +33,10 @@ export declare function formatSIWEMessage(opts: SIWEMessageOpts, address: string
|
|
|
33
33
|
export declare function buildSIWxAuthHeaders(opts: SIWxAuthOptions): SIWxAuthHeaders;
|
|
34
34
|
/**
|
|
35
35
|
* Get SIWX auth headers for the Run402 API.
|
|
36
|
-
* Returns null if no
|
|
36
|
+
* Returns null if no local wallet is configured.
|
|
37
37
|
*
|
|
38
38
|
* @param path - API path (e.g. "/projects/v1") used to build the SIWE uri field.
|
|
39
39
|
*/
|
|
40
|
-
export declare function
|
|
40
|
+
export declare function getWalletAuthHeaders(path: string, walletPath?: string): SIWxAuthHeaders | null;
|
|
41
41
|
export {};
|
|
42
|
-
//# sourceMappingURL=
|
|
42
|
+
//# sourceMappingURL=wallet-auth.d.ts.map
|