run402 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 +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/wallets.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import { writeFileSync, readFileSync, existsSync, rmSync } from "node:fs";
|
|
|
17
17
|
import { failUnknownSubcommand } from "./argparse.mjs";
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
import { fail, reportSdkError } from "./sdk-errors.mjs";
|
|
20
|
+
import { walletFile, readWallet as readActiveWallet, saveWallet as saveActiveWallet } from "./config.mjs";
|
|
20
21
|
import { isValidProfileName, getActiveProfile } from "../core-dist/config.js";
|
|
21
22
|
import {
|
|
22
23
|
listProfileNames,
|
|
@@ -30,10 +31,11 @@ import {
|
|
|
30
31
|
getDefaultWallet,
|
|
31
32
|
setDefaultWallet,
|
|
32
33
|
} from "../core-dist/profiles.js";
|
|
33
|
-
import {
|
|
34
|
+
import { readWallet, saveWallet } from "../core-dist/wallet.js";
|
|
34
35
|
import { describeRejectedValue } from "../core-dist/redact.js";
|
|
35
36
|
import { getSdk } from "./sdk.mjs";
|
|
36
37
|
import { readBindingFile, updateBindingFile } from "./wallet-context.mjs";
|
|
38
|
+
import { initializeWalletAction } from "./next-actions.mjs";
|
|
37
39
|
|
|
38
40
|
const DEFAULT = "default";
|
|
39
41
|
const PRIVATE_KEY_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
@@ -42,16 +44,18 @@ const HELP = `run402 wallets — manage named wallets (profiles)
|
|
|
42
44
|
|
|
43
45
|
Usage:
|
|
44
46
|
run402 wallets list List all wallets (local_label, server_label, address, rail, active)
|
|
45
|
-
run402 wallets current Show the
|
|
46
|
-
run402 wallets
|
|
47
|
+
run402 wallets current Show the active wallet: how it was selected, address, rail, file path, faucet use
|
|
48
|
+
run402 wallets fund Request testnet funds from the faucet into the active wallet (Base Sepolia or Tempo)
|
|
49
|
+
run402 wallets balance The active wallet's on-chain balances, plus its organization's allowance
|
|
50
|
+
run402 wallets new <name> Create a new wallet (key stays local); 'default' creates the root wallet
|
|
47
51
|
run402 wallets use <name> Set the global default wallet
|
|
48
52
|
run402 wallets rename <old> --to <new> Rename a wallet (legacy: rename <old> <new>)
|
|
49
53
|
run402 wallets bind [<name>] Write ./.run402.json binding this directory to a wallet
|
|
50
54
|
run402 wallets unbind Remove ./.run402.json
|
|
51
55
|
run402 wallets import <name> --key <path|-> Adopt an existing private key as a named wallet
|
|
52
56
|
run402 wallets rm <name> --yes Delete a wallet and its keys (requires --yes)
|
|
53
|
-
run402 wallets lightning status The active wallet's Lightning
|
|
54
|
-
run402 wallets lightning mint Mint the Lightning
|
|
57
|
+
run402 wallets lightning status The active wallet's Lightning wallet (balance, budget, custody)
|
|
58
|
+
run402 wallets lightning mint Mint the Lightning wallet for the active wallet (same as init lightning)
|
|
55
59
|
run402 wallets lightning revoke Revoke it: the sub-wallet is deleted on Run402's Hub; the rail returns to x402
|
|
56
60
|
|
|
57
61
|
Selection precedence for normal commands:
|
|
@@ -102,7 +106,7 @@ function walletInfo(name, active) {
|
|
|
102
106
|
const label = meta?.label ?? null;
|
|
103
107
|
if (!address) {
|
|
104
108
|
try {
|
|
105
|
-
const a =
|
|
109
|
+
const a = readWallet(join(profileDir(name), "wallet.json"));
|
|
106
110
|
address = a?.address ?? null;
|
|
107
111
|
rail = rail ?? a?.rail ?? null;
|
|
108
112
|
} catch {
|
|
@@ -127,7 +131,7 @@ function cmdList() {
|
|
|
127
131
|
out(listProfileNames().map((n) => walletInfo(n, active)));
|
|
128
132
|
}
|
|
129
133
|
|
|
130
|
-
function cmdCurrent() {
|
|
134
|
+
async function cmdCurrent() {
|
|
131
135
|
const ctx = activeContext();
|
|
132
136
|
const name = ctx?.name ?? getActiveProfile();
|
|
133
137
|
const info = walletInfo(name, name);
|
|
@@ -146,21 +150,33 @@ function cmdCurrent() {
|
|
|
146
150
|
hint: "Run 'run402 wallets rename' to reconcile.",
|
|
147
151
|
});
|
|
148
152
|
}
|
|
153
|
+
// The active wallet's own file: when it was created, its persisted rail, and
|
|
154
|
+
// whether the faucet has been used on it. `faucet_used` tracks faucet
|
|
155
|
+
// invocation, not pay-readiness — `run402 wallets balance` reads real funds.
|
|
156
|
+
let local = null;
|
|
157
|
+
try {
|
|
158
|
+
local = await getSdk().wallets.status();
|
|
159
|
+
} catch (err) {
|
|
160
|
+
reportSdkError(err);
|
|
161
|
+
}
|
|
149
162
|
out({
|
|
150
163
|
local_label: name,
|
|
151
164
|
source: ctx?.source ?? "unknown",
|
|
152
165
|
source_detail: ctx?.sourceDetail ?? null,
|
|
153
|
-
address: info.address,
|
|
166
|
+
address: local?.configured ? local.address : info.address,
|
|
154
167
|
server_label: info.server_label,
|
|
168
|
+
configured: !!local?.configured,
|
|
169
|
+
created: local?.created ?? null,
|
|
170
|
+
rail: local?.configured ? local.rail ?? "x402" : info.rail,
|
|
171
|
+
faucet_used: !!local?.faucet_used,
|
|
172
|
+
path: local?.path ?? walletFile(),
|
|
173
|
+
...(local?.configured ? {} : { next_actions: [initializeWalletAction()] }),
|
|
155
174
|
warnings,
|
|
156
175
|
});
|
|
157
176
|
}
|
|
158
177
|
|
|
159
178
|
async function cmdNew(args) {
|
|
160
179
|
const name = requireName(args.find((a) => a && !a.startsWith("-")));
|
|
161
|
-
if (name === DEFAULT) {
|
|
162
|
-
fail({ code: "BAD_WALLET_NAME", message: "'default' is reserved. Use 'run402 init' for the default wallet.", details: { name } });
|
|
163
|
-
}
|
|
164
180
|
if (profileExists(name)) {
|
|
165
181
|
fail({ code: "WALLET_EXISTS", message: `A wallet named '${name}' already exists.`, hint: "run402 wallets list", details: { name } });
|
|
166
182
|
}
|
|
@@ -175,12 +191,23 @@ async function cmdNew(args) {
|
|
|
175
191
|
const address = privateKeyToAccount(privateKey).address;
|
|
176
192
|
const created = new Date().toISOString();
|
|
177
193
|
ensureProfileDir(name);
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
194
|
+
saveWallet({ address, privateKey, created, funded: false, rail }, join(profileDir(name), "wallet.json"));
|
|
195
|
+
// The reserved `default` wallet lives at the config-dir root and carries no
|
|
196
|
+
// server label; a named wallet mirrors its name as the label.
|
|
197
|
+
if (name === DEFAULT) {
|
|
198
|
+
writeMeta(name, { name, address, rail, created });
|
|
199
|
+
} else {
|
|
200
|
+
writeMeta(name, { name, address, label: name, rail, created });
|
|
201
|
+
await maybePushLabel(name, name, address);
|
|
202
|
+
}
|
|
181
203
|
// requireName constrains aliases to shell-safe lowercase identifiers.
|
|
182
|
-
const command = rail === "lightning"
|
|
183
|
-
|
|
204
|
+
const command = rail === "lightning"
|
|
205
|
+
? `run402 --wallet ${name} init lightning`
|
|
206
|
+
: name === DEFAULT ? "run402 wallets fund" : `run402 wallets use ${name}`;
|
|
207
|
+
const why = rail === "lightning"
|
|
208
|
+
? "Initialize this Lightning wallet."
|
|
209
|
+
: name === DEFAULT ? "Fund the wallet you just created from the testnet faucet." : "Select the wallet you just created.";
|
|
210
|
+
out({ local_label: name, address, rail, created: true, next: command, next_actions: [{ type: "run_command", command, why }] });
|
|
184
211
|
}
|
|
185
212
|
|
|
186
213
|
function cmdUse(args) {
|
|
@@ -216,7 +243,7 @@ async function cmdRename(args) {
|
|
|
216
243
|
let address = meta.address ?? null;
|
|
217
244
|
if (!address) {
|
|
218
245
|
try {
|
|
219
|
-
address =
|
|
246
|
+
address = readWallet(join(profileDir(newName), "wallet.json"))?.address ?? null;
|
|
220
247
|
} catch {
|
|
221
248
|
/* best-effort */
|
|
222
249
|
}
|
|
@@ -285,7 +312,7 @@ async function cmdImport(args) {
|
|
|
285
312
|
}
|
|
286
313
|
const created = new Date().toISOString();
|
|
287
314
|
ensureProfileDir(name);
|
|
288
|
-
|
|
315
|
+
saveWallet({ address, privateKey, created, funded: false, rail: "x402" }, join(profileDir(name), "wallet.json"));
|
|
289
316
|
writeMeta(name, { name, address, label: name, rail: "x402", created });
|
|
290
317
|
await maybePushLabel(name, name, address);
|
|
291
318
|
out({ local_label: name, address, imported: true });
|
|
@@ -323,11 +350,11 @@ function flagVal(args, flag) {
|
|
|
323
350
|
}
|
|
324
351
|
|
|
325
352
|
/**
|
|
326
|
-
* Best-effort server-side label push. Signs with the TARGET wallet's
|
|
353
|
+
* Best-effort server-side label push. Signs with the TARGET wallet's key
|
|
327
354
|
* (not the active one) so a just-created/renamed wallet can set its own label.
|
|
328
355
|
*
|
|
329
356
|
* ON by default — the gateway label endpoint is live, and the display label is
|
|
330
|
-
* what makes the wallet name show up cross-machine and in the
|
|
357
|
+
* what makes the wallet name show up cross-machine and in the console
|
|
331
358
|
* (WEB). Set `RUN402_WALLET_LABEL_SYNC=0` to disable (fully-offline wallet ops,
|
|
332
359
|
* or hermetic tests). The local folder name is always the source of truth; this
|
|
333
360
|
* only mirrors the display label to the server. Always best-effort — `setLabel`
|
|
@@ -339,7 +366,7 @@ async function maybePushLabel(name, label, address) {
|
|
|
339
366
|
if (!address) return;
|
|
340
367
|
try {
|
|
341
368
|
const sdk = getSdk({
|
|
342
|
-
|
|
369
|
+
walletPath: join(profileDir(name), "wallet.json"),
|
|
343
370
|
keystorePath: join(profileDir(name), "projects.json"),
|
|
344
371
|
});
|
|
345
372
|
await sdk.wallet(address).setLabel(label);
|
|
@@ -364,6 +391,8 @@ export async function run(sub, args = []) {
|
|
|
364
391
|
case "unbind": return cmdUnbind();
|
|
365
392
|
case "import": return cmdImport(rest);
|
|
366
393
|
case "rm": return cmdRm(rest);
|
|
394
|
+
case "fund": return cmdFund();
|
|
395
|
+
case "balance": return cmdBalance();
|
|
367
396
|
case "lightning": return cmdLightning(rest);
|
|
368
397
|
default:
|
|
369
398
|
failUnknownSubcommand("wallets", sub);
|
|
@@ -371,30 +400,30 @@ export async function run(sub, args = []) {
|
|
|
371
400
|
}
|
|
372
401
|
|
|
373
402
|
// ── lightning ────────────────────────────────────────────────────────────────
|
|
374
|
-
// The Lightning
|
|
375
|
-
// The pairing secret stays in
|
|
403
|
+
// The Lightning wallet of the ACTIVE wallet (mpp-lightning-over-nwc).
|
|
404
|
+
// The pairing secret stays in wallet.json and is never printed.
|
|
376
405
|
async function cmdLightning(args) {
|
|
377
406
|
const action = args.find((a) => a && !a.startsWith("-")) ?? "status";
|
|
378
407
|
const { ensureLightningWallet, revokeLightningWallet, describeLightning, readLightningBalance } = await import("./lightning-wallet.mjs");
|
|
379
|
-
const {
|
|
408
|
+
const { readWallet } = await import("../core-dist/wallet.js");
|
|
380
409
|
try {
|
|
381
410
|
if (action === "status") {
|
|
382
|
-
const
|
|
383
|
-
if (!
|
|
411
|
+
const localWallet = readWallet();
|
|
412
|
+
if (!localWallet) fail({ code: "NO_WALLET", message: "No local wallet configured for this wallet.", hint: "run402 init lightning" });
|
|
384
413
|
let wallet = null;
|
|
385
414
|
try { wallet = await getSdk().agent.lightningWallet.get(); } catch (err) {
|
|
386
415
|
const code = err?.body?.code ?? err?.code;
|
|
387
416
|
if (code !== "LIGHTNING_WALLET_NOT_FOUND") throw err;
|
|
388
417
|
}
|
|
389
|
-
const balance = await readLightningBalance(
|
|
390
|
-
const lightning = describeLightning(
|
|
391
|
-
out({ rail:
|
|
418
|
+
const balance = await readLightningBalance(localWallet);
|
|
419
|
+
const lightning = describeLightning(localWallet, wallet, balance);
|
|
420
|
+
out({ rail: localWallet.rail ?? "x402", lightning, ...(lightning ? {} : { hint: "run402 init lightning" }) });
|
|
392
421
|
return;
|
|
393
422
|
}
|
|
394
423
|
if (action === "mint") {
|
|
395
424
|
const result = await ensureLightningWallet();
|
|
396
|
-
const balance = result.outcome === "stored" || result.outcome === "present" ? await readLightningBalance(result.
|
|
397
|
-
out({ outcome: result.outcome, rail: result.
|
|
425
|
+
const balance = result.outcome === "stored" || result.outcome === "present" ? await readLightningBalance(result.localWallet) : null;
|
|
426
|
+
out({ outcome: result.outcome, rail: result.localWallet.rail ?? "x402", lightning: describeLightning(result.localWallet, result.wallet, balance) });
|
|
398
427
|
return;
|
|
399
428
|
}
|
|
400
429
|
if (action === "revoke") {
|
|
@@ -407,3 +436,139 @@ async function cmdLightning(args) {
|
|
|
407
436
|
reportSdkError(err);
|
|
408
437
|
}
|
|
409
438
|
}
|
|
439
|
+
|
|
440
|
+
// ── fund / balance (the ACTIVE wallet) ─────────────────────────────────────
|
|
441
|
+
|
|
442
|
+
const USDC_ABI = [{ name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }] }];
|
|
443
|
+
const USDC_MAINNET = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
444
|
+
const USDC_SEPOLIA = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
|
|
445
|
+
const PATH_USD = "0x20c0000000000000000000000000000000000000";
|
|
446
|
+
const TEMPO_RPC = "https://rpc.moderato.tempo.xyz/";
|
|
447
|
+
|
|
448
|
+
async function loadDeps() {
|
|
449
|
+
const { generatePrivateKey, privateKeyToAccount } = await import("viem/accounts");
|
|
450
|
+
const { createPublicClient, http, defineChain } = await import("viem");
|
|
451
|
+
const { base, baseSepolia } = await import("viem/chains");
|
|
452
|
+
const tempoModerato = defineChain({
|
|
453
|
+
id: 42431,
|
|
454
|
+
name: "Tempo Moderato",
|
|
455
|
+
nativeCurrency: { name: "pathUSD", symbol: "pathUSD", decimals: 6 },
|
|
456
|
+
rpcUrls: { default: { http: [TEMPO_RPC] } },
|
|
457
|
+
});
|
|
458
|
+
return { generatePrivateKey, privateKeyToAccount, createPublicClient, http, base, baseSepolia, tempoModerato };
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async function cmdFund() {
|
|
462
|
+
const w = readActiveWallet();
|
|
463
|
+
if (!w) {
|
|
464
|
+
fail({
|
|
465
|
+
code: "NO_WALLET",
|
|
466
|
+
message: "No local wallet.",
|
|
467
|
+
hint: "Run: run402 init",
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (w.rail === "mpp") {
|
|
472
|
+
// Tempo Moderato faucet — instant, no polling needed
|
|
473
|
+
const { createPublicClient, http, tempoModerato } = await loadDeps();
|
|
474
|
+
const client = createPublicClient({ chain: tempoModerato, transport: http() });
|
|
475
|
+
const before = await readUsdcBalance(client, PATH_USD, w.address).catch(() => 0);
|
|
476
|
+
|
|
477
|
+
const res = await fetch(TEMPO_RPC, {
|
|
478
|
+
method: "POST",
|
|
479
|
+
headers: { "Content-Type": "application/json" },
|
|
480
|
+
body: JSON.stringify({ jsonrpc: "2.0", method: "tempo_fundAddress", params: [w.address], id: 1 }),
|
|
481
|
+
});
|
|
482
|
+
const data = await res.json();
|
|
483
|
+
if (data.error) {
|
|
484
|
+
fail({
|
|
485
|
+
code: "FAUCET_FAILED",
|
|
486
|
+
message: data.error.message || "Tempo faucet failed",
|
|
487
|
+
details: { rail: "mpp" },
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Re-read balance once (instant confirmation)
|
|
492
|
+
const now = await readUsdcBalance(client, PATH_USD, w.address).catch(() => before);
|
|
493
|
+
saveActiveWallet({ ...w, funded: true, lastFaucet: new Date().toISOString() });
|
|
494
|
+
console.log(JSON.stringify({
|
|
495
|
+
address: w.address,
|
|
496
|
+
rail: "mpp",
|
|
497
|
+
onchain: {
|
|
498
|
+
"tempo-moderato_pathusd_micros": now,
|
|
499
|
+
},
|
|
500
|
+
}, null, 2));
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Default: Base Sepolia faucet (existing behavior)
|
|
505
|
+
const { createPublicClient, http, baseSepolia } = await loadDeps();
|
|
506
|
+
const client = createPublicClient({ chain: baseSepolia, transport: http() });
|
|
507
|
+
const before = await readUsdcBalance(client, USDC_SEPOLIA, w.address).catch(() => 0);
|
|
508
|
+
|
|
509
|
+
let data;
|
|
510
|
+
try {
|
|
511
|
+
data = await getSdk().wallets.faucet(w.address);
|
|
512
|
+
} catch (err) {
|
|
513
|
+
reportSdkError(err);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const MAX_WAIT = 30;
|
|
517
|
+
for (let i = 0; i < MAX_WAIT; i++) {
|
|
518
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
519
|
+
const now = await readUsdcBalance(client, USDC_SEPOLIA, w.address).catch(() => before);
|
|
520
|
+
if (now > before) {
|
|
521
|
+
saveActiveWallet({ ...w, funded: true, lastFaucet: new Date().toISOString() });
|
|
522
|
+
console.log(JSON.stringify({
|
|
523
|
+
address: w.address,
|
|
524
|
+
rail: w.rail || "x402",
|
|
525
|
+
onchain: {
|
|
526
|
+
"base-sepolia_usd_micros": now,
|
|
527
|
+
},
|
|
528
|
+
}, null, 2));
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
saveActiveWallet({ ...w, funded: true, lastFaucet: new Date().toISOString() });
|
|
534
|
+
console.log(JSON.stringify({ ...data, balance_confirmed: false, hint: "Faucet request sent but on-chain balance not yet confirmed" }));
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async function readUsdcBalance(client, usdc, address) {
|
|
538
|
+
const raw = await client.readContract({ address: usdc, abi: USDC_ABI, functionName: "balanceOf", args: [address] });
|
|
539
|
+
return Number(raw);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
async function cmdBalance() {
|
|
543
|
+
const w = readActiveWallet();
|
|
544
|
+
if (!w) {
|
|
545
|
+
fail({
|
|
546
|
+
code: "NO_WALLET",
|
|
547
|
+
message: "No local wallet.",
|
|
548
|
+
hint: "Run: run402 init",
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const { createPublicClient, http, base, baseSepolia, tempoModerato } = await loadDeps();
|
|
553
|
+
const mainnetClient = createPublicClient({ chain: base, transport: http() });
|
|
554
|
+
const sepoliaClient = createPublicClient({ chain: baseSepolia, transport: http() });
|
|
555
|
+
const tempoClient = createPublicClient({ chain: tempoModerato, transport: http() });
|
|
556
|
+
|
|
557
|
+
const [mainnetUsdc, sepoliaUsdc, tempoPathUsd, billingRes] = await Promise.all([
|
|
558
|
+
readUsdcBalance(mainnetClient, USDC_MAINNET, w.address).catch(() => null),
|
|
559
|
+
readUsdcBalance(sepoliaClient, USDC_SEPOLIA, w.address).catch(() => null),
|
|
560
|
+
readUsdcBalance(tempoClient, PATH_USD, w.address).catch(() => null),
|
|
561
|
+
getSdk().billing.checkBalance(w.address).catch(() => null),
|
|
562
|
+
]);
|
|
563
|
+
|
|
564
|
+
console.log(JSON.stringify({
|
|
565
|
+
address: w.address,
|
|
566
|
+
rail: w.rail || "x402",
|
|
567
|
+
onchain: {
|
|
568
|
+
"base-mainnet_usd_micros": mainnetUsdc,
|
|
569
|
+
"base-sepolia_usd_micros": sepoliaUsdc,
|
|
570
|
+
"tempo-moderato_pathusd_micros": tempoPathUsd,
|
|
571
|
+
},
|
|
572
|
+
allowance_usd_micros: billingRes ? billingRes.allowance_usd_micros : null,
|
|
573
|
+
}, null, 2));
|
|
574
|
+
}
|
package/lib/webhook-secret.mjs
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* run402 webhook-secret — Manage
|
|
2
|
+
* run402 webhook-secret — Manage your webhook signing secret.
|
|
3
3
|
*
|
|
4
4
|
* Split out from notifications.mjs so the SURFACE inventory parser sees
|
|
5
5
|
* `webhook-secret:rotate` as its own command, separate from the
|
|
6
6
|
* `notifications:*` group.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { walletAuthHeaders } from "./config.mjs";
|
|
10
|
+
import { sessionProofs } from "./session-proofs.mjs";
|
|
11
11
|
import { getSdk } from "./sdk.mjs";
|
|
12
12
|
import { reportSdkError } from "./sdk-errors.mjs";
|
|
13
13
|
import { assertKnownFlags, normalizeArgv, failUnknownSubcommand } from "./argparse.mjs";
|
|
14
14
|
|
|
15
|
-
const HELP = `run402 webhook-secret — Manage
|
|
15
|
+
const HELP = `run402 webhook-secret — Manage your webhook signing secret
|
|
16
16
|
|
|
17
17
|
Usage:
|
|
18
18
|
run402 webhook-secret rotate
|
|
@@ -20,7 +20,7 @@ Usage:
|
|
|
20
20
|
Notes:
|
|
21
21
|
- Returns the new plaintext secret EXACTLY once. Store it immediately.
|
|
22
22
|
- Previous secret remains valid for 24 hours after rotation.
|
|
23
|
-
- Requires
|
|
23
|
+
- Requires passkey assurance ('run402 login', or 'run402 agent passkey enroll').
|
|
24
24
|
`;
|
|
25
25
|
|
|
26
26
|
export async function run(sub, args = []) {
|
|
@@ -39,7 +39,7 @@ export async function run(sub, args = []) {
|
|
|
39
39
|
}
|
|
40
40
|
const parsedArgs = normalizeArgv(args);
|
|
41
41
|
assertKnownFlags(parsedArgs, ["--help", "-h"]);
|
|
42
|
-
const proofs =
|
|
42
|
+
const proofs = sessionProofs("/agent/v1/webhook-secret/rotate");
|
|
43
43
|
try {
|
|
44
44
|
const data = await getSdk().admin.rotateWebhookSecret(proofs);
|
|
45
45
|
console.log(JSON.stringify(data, null, 2));
|
package/lib/whoami.mjs
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `run402 whoami` — who this CLI acts as: the principal, its org memberships,
|
|
3
|
+
* and the sign-in session (`session.grade`: `browser` | `loopback` | `device`,
|
|
4
|
+
* or `null` for a wallet caller) from `GET /agent/v1/whoami`, plus the write
|
|
5
|
+
* approvals cached on this machine. See sign-in.mjs.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { fail, reportSdkError } from "./sdk-errors.mjs";
|
|
9
|
+
import { getSdk } from "./sdk.mjs";
|
|
10
|
+
import { normalizeArgv, hasHelp, assertKnownFlags, positionalArgs } from "./argparse.mjs";
|
|
11
|
+
import { loadLiveControlPlaneSession, clearControlPlaneSession } from "../core-dist/control-plane-session.js";
|
|
12
|
+
import { readApprovals } from "../core-dist/write-approvals.js";
|
|
13
|
+
|
|
14
|
+
const HELP = `run402 whoami — who this CLI acts as
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
run402 whoami
|
|
18
|
+
|
|
19
|
+
Calls GET /agent/v1/whoami. With a live sign-in session ('run402 login') it
|
|
20
|
+
reads as that session and reports session.grade: loopback (full), browser, or
|
|
21
|
+
device (read-only). Without one it reads as the active wallet and session is
|
|
22
|
+
null. The output also lists the write approvals cached on this machine
|
|
23
|
+
('run402 approve'), each with its action, target, and expiry.
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
/** Live cached write approvals: action, target, expiry. Never the token. */
|
|
27
|
+
function cachedApprovals(nowMs = Date.now()) {
|
|
28
|
+
try {
|
|
29
|
+
return readApprovals()
|
|
30
|
+
.filter((a) => a.expires_at > nowMs)
|
|
31
|
+
.map((a) => ({
|
|
32
|
+
action: a.action,
|
|
33
|
+
org_id: a.org_id ?? null,
|
|
34
|
+
project_id: a.project_id ?? null,
|
|
35
|
+
expires_at: new Date(a.expires_at).toISOString(),
|
|
36
|
+
}));
|
|
37
|
+
} catch {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function run(args = []) {
|
|
43
|
+
args = normalizeArgv(args);
|
|
44
|
+
if (hasHelp(args)) {
|
|
45
|
+
console.log(HELP);
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
assertKnownFlags(args, ["--help", "-h"]);
|
|
49
|
+
const extra = positionalArgs(args, []);
|
|
50
|
+
if (extra.length > 0) {
|
|
51
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for whoami: ${extra[0]}`, hint: "Use `run402 whoami`." });
|
|
52
|
+
}
|
|
53
|
+
const sdk = getSdk();
|
|
54
|
+
const session = loadLiveControlPlaneSession();
|
|
55
|
+
let who;
|
|
56
|
+
try {
|
|
57
|
+
who = session
|
|
58
|
+
? await sdk.session.whoami({ token: session.control_plane_session_token })
|
|
59
|
+
: await sdk.orgs.whoami();
|
|
60
|
+
} catch (err) {
|
|
61
|
+
// A cached session the gateway no longer honours (revoked or expired
|
|
62
|
+
// server-side): drop it so the next command does not replay a dead token.
|
|
63
|
+
if (session && err && err.status === 401) {
|
|
64
|
+
clearControlPlaneSession();
|
|
65
|
+
return fail({
|
|
66
|
+
code: "SESSION_INVALID",
|
|
67
|
+
message: "The cached sign-in session is no longer valid (revoked or expired).",
|
|
68
|
+
hint: "Run 'run402 login' to sign in again.",
|
|
69
|
+
next_actions: [{ type: "authenticate", command: "run402 login" }],
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return reportSdkError(err);
|
|
73
|
+
}
|
|
74
|
+
console.log(JSON.stringify({ ...who, write_approvals: cachedApprovals() }, null, 2));
|
|
75
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "run402",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.94.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/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
|
|
@@ -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
|