run402 4.97.0 → 4.98.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 +1 -1
- package/core-dist/binding-file.js +28 -1
- package/core-dist/wallet.js +3 -3
- package/lib/auth.mjs +1 -2
- package/lib/buzz-doctor.mjs +21 -806
- package/lib/buzz-doctor.test.mjs +7 -6
- package/lib/buzz.test.mjs +13 -2
- package/lib/doctor.mjs +72 -811
- package/lib/init.mjs +35 -576
- package/lib/org-context.mjs +39 -376
- package/lib/orgs.mjs +22 -106
- package/lib/projects.mjs +1 -2
- package/lib/repos.mjs +6 -6
- package/lib/rooms.mjs +1 -2
- package/lib/sdk-errors.mjs +26 -0
- package/lib/status.mjs +14 -154
- package/lib/up.mjs +1 -2
- package/lib/vault-scaffold.mjs +2 -3
- package/lib/vault-target.mjs +14 -141
- package/lib/wallet-context.mjs +61 -227
- package/lib/wallets.mjs +47 -310
- package/package.json +1 -2
- package/sdk/core-dist/binding-file.js +28 -1
- package/sdk/core-dist/wallet.js +3 -3
- package/sdk/dist/errors.d.ts +24 -2
- package/sdk/dist/errors.d.ts.map +1 -1
- package/sdk/dist/errors.js +44 -2
- package/sdk/dist/errors.js.map +1 -1
- package/sdk/dist/index.d.ts +18 -3
- package/sdk/dist/index.d.ts.map +1 -1
- package/sdk/dist/index.js +12 -1
- package/sdk/dist/index.js.map +1 -1
- package/sdk/dist/kernel.d.ts +34 -0
- package/sdk/dist/kernel.d.ts.map +1 -1
- package/sdk/dist/kernel.js +15 -1
- package/sdk/dist/kernel.js.map +1 -1
- package/sdk/dist/namespaces/admin.d.ts.map +1 -1
- package/sdk/dist/namespaces/admin.js +8 -0
- 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 +4 -0
- package/sdk/dist/namespaces/agent.js.map +1 -1
- package/sdk/dist/namespaces/agent.types.d.ts +5 -0
- package/sdk/dist/namespaces/agent.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/apps.d.ts.map +1 -1
- package/sdk/dist/namespaces/apps.js +2 -0
- package/sdk/dist/namespaces/apps.js.map +1 -1
- package/sdk/dist/namespaces/auth.d.ts.map +1 -1
- package/sdk/dist/namespaces/auth.js +9 -0
- package/sdk/dist/namespaces/auth.js.map +1 -1
- package/sdk/dist/namespaces/branches.d.ts.map +1 -1
- package/sdk/dist/namespaces/branches.js +2 -0
- package/sdk/dist/namespaces/branches.js.map +1 -1
- package/sdk/dist/namespaces/ci.d.ts.map +1 -1
- package/sdk/dist/namespaces/ci.js +2 -0
- package/sdk/dist/namespaces/ci.js.map +1 -1
- package/sdk/dist/namespaces/credentials.d.ts +0 -23
- package/sdk/dist/namespaces/credentials.d.ts.map +1 -1
- package/sdk/dist/namespaces/credentials.js +29 -0
- package/sdk/dist/namespaces/credentials.js.map +1 -1
- package/sdk/dist/namespaces/escalations.d.ts.map +1 -1
- package/sdk/dist/namespaces/escalations.js +2 -0
- package/sdk/dist/namespaces/escalations.js.map +1 -1
- package/sdk/dist/namespaces/grants.d.ts.map +1 -1
- package/sdk/dist/namespaces/grants.js +4 -0
- package/sdk/dist/namespaces/grants.js.map +1 -1
- package/sdk/dist/namespaces/live.d.ts +0 -16
- package/sdk/dist/namespaces/live.d.ts.map +1 -1
- package/sdk/dist/namespaces/live.js +19 -0
- package/sdk/dist/namespaces/live.js.map +1 -1
- package/sdk/dist/namespaces/me.d.ts.map +1 -1
- package/sdk/dist/namespaces/me.js +3 -0
- package/sdk/dist/namespaces/me.js.map +1 -1
- package/sdk/dist/namespaces/org.d.ts.map +1 -1
- package/sdk/dist/namespaces/org.js +3 -0
- package/sdk/dist/namespaces/org.js.map +1 -1
- package/sdk/dist/namespaces/projects.d.ts.map +1 -1
- package/sdk/dist/namespaces/projects.js +5 -0
- package/sdk/dist/namespaces/projects.js.map +1 -1
- package/sdk/dist/namespaces/repos.d.ts.map +1 -1
- package/sdk/dist/namespaces/repos.js +5 -0
- package/sdk/dist/namespaces/repos.js.map +1 -1
- package/sdk/dist/namespaces/rooms.d.ts.map +1 -1
- package/sdk/dist/namespaces/rooms.js +3 -0
- package/sdk/dist/namespaces/rooms.js.map +1 -1
- package/sdk/dist/namespaces/session.d.ts.map +1 -1
- package/sdk/dist/namespaces/session.js +18 -0
- package/sdk/dist/namespaces/session.js.map +1 -1
- package/sdk/dist/namespaces/transfers.d.ts.map +1 -1
- package/sdk/dist/namespaces/transfers.js +3 -0
- package/sdk/dist/namespaces/transfers.js.map +1 -1
- package/sdk/dist/namespaces/wallets.d.ts.map +1 -1
- package/sdk/dist/namespaces/wallets.js +2 -0
- package/sdk/dist/namespaces/wallets.js.map +1 -1
- package/sdk/dist/namespaces/write-approval.d.ts.map +1 -1
- package/sdk/dist/namespaces/write-approval.js +3 -0
- package/sdk/dist/namespaces/write-approval.js.map +1 -1
- package/sdk/dist/node/actions-node.d.ts.map +1 -1
- package/sdk/dist/node/actions-node.js +3 -0
- package/sdk/dist/node/actions-node.js.map +1 -1
- package/sdk/dist/node/buzz-doctor-contract.d.ts +155 -0
- package/sdk/dist/node/buzz-doctor-contract.d.ts.map +1 -0
- package/sdk/dist/node/buzz-doctor-contract.js +385 -0
- package/sdk/dist/node/buzz-doctor-contract.js.map +1 -0
- package/sdk/dist/node/buzz-doctor.d.ts +155 -0
- package/sdk/dist/node/buzz-doctor.d.ts.map +1 -0
- package/sdk/dist/node/buzz-doctor.js +810 -0
- package/sdk/dist/node/buzz-doctor.js.map +1 -0
- package/sdk/dist/node/credentials.d.ts +9 -4
- package/sdk/dist/node/credentials.d.ts.map +1 -1
- package/sdk/dist/node/credentials.js +1 -1
- package/sdk/dist/node/credentials.js.map +1 -1
- package/sdk/dist/node/diagnostics.d.ts +45 -0
- package/sdk/dist/node/diagnostics.d.ts.map +1 -0
- package/sdk/dist/node/diagnostics.js +67 -0
- package/sdk/dist/node/diagnostics.js.map +1 -0
- package/sdk/dist/node/doctor.d.ts +101 -0
- package/sdk/dist/node/doctor.d.ts.map +1 -0
- package/sdk/dist/node/doctor.js +618 -0
- package/sdk/dist/node/doctor.js.map +1 -0
- package/sdk/dist/node/index.d.ts +57 -5
- package/sdk/dist/node/index.d.ts.map +1 -1
- package/sdk/dist/node/index.js +36 -1
- package/sdk/dist/node/index.js.map +1 -1
- package/sdk/dist/node/init.d.ts +137 -0
- package/sdk/dist/node/init.d.ts.map +1 -0
- package/sdk/dist/node/init.js +507 -0
- package/sdk/dist/node/init.js.map +1 -0
- package/sdk/dist/node/lightning-wallet.d.ts +61 -0
- package/sdk/dist/node/lightning-wallet.d.ts.map +1 -0
- package/sdk/dist/node/lightning-wallet.js +126 -0
- package/sdk/dist/node/lightning-wallet.js.map +1 -0
- package/sdk/dist/node/local-actions.d.ts +23 -0
- package/sdk/dist/node/local-actions.d.ts.map +1 -0
- package/sdk/dist/node/local-actions.js +38 -0
- package/sdk/dist/node/local-actions.js.map +1 -0
- package/sdk/dist/node/org-context.d.ts +169 -0
- package/sdk/dist/node/org-context.d.ts.map +1 -0
- package/sdk/dist/node/org-context.js +403 -0
- package/sdk/dist/node/org-context.js.map +1 -0
- package/sdk/dist/node/status.d.ts +62 -0
- package/sdk/dist/node/status.d.ts.map +1 -0
- package/sdk/dist/node/status.js +132 -0
- package/sdk/dist/node/status.js.map +1 -0
- package/sdk/dist/node/vault-target.d.ts +49 -0
- package/sdk/dist/node/vault-target.d.ts.map +1 -0
- package/sdk/dist/node/vault-target.js +109 -0
- package/sdk/dist/node/vault-target.js.map +1 -0
- package/sdk/dist/node/wallets.d.ts +242 -0
- package/sdk/dist/node/wallets.d.ts.map +1 -0
- package/sdk/dist/node/wallets.js +484 -0
- package/sdk/dist/node/wallets.js.map +1 -0
- package/sdk/dist/secret-gate.d.ts +377 -0
- package/sdk/dist/secret-gate.d.ts.map +1 -0
- package/sdk/dist/secret-gate.js +273 -0
- package/sdk/dist/secret-gate.js.map +1 -0
- package/vault-surface.json +1 -1
- package/fixtures/buzz-v0.5.2-cli-capabilities.json +0 -92
- package/fixtures/run402-buzz-doctor-v1-contract.json +0 -140
- package/lib/buzz-doctor-contract.mjs +0 -97
- package/lib/buzz-doctor-contract.test.mjs +0 -134
- package/lib/lightning-wallet.mjs +0 -115
package/lib/init.mjs
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* run402 init — the CLI edge of `r.init()` (`@run402/sdk/node`), which sets up
|
|
3
|
+
* the active profile (wallet, rail, funding, voucher, Lightning wallet, tier,
|
|
4
|
+
* projects, vault remote, organization, next step) or, with `--api-base`,
|
|
5
|
+
* configures a Run402 Core target. This module parses argv, refuses the flag
|
|
6
|
+
* combinations that cannot mean anything, streams the progress lines to
|
|
7
|
+
* stderr, and prints the summary. `run402 init astro` routes to the Astro
|
|
8
|
+
* scaffolder.
|
|
9
|
+
*/
|
|
2
10
|
import { getSdk } from "./sdk.mjs";
|
|
3
|
-
import { fail } from "./sdk-errors.mjs";
|
|
4
|
-
import { upDeployAction, deployAction } from "./next-actions.mjs";
|
|
5
|
-
import { resolveOwningOrgId } from "./org-context.mjs";
|
|
6
|
-
import { getActiveProfile } from "../core-dist/config.js";
|
|
7
|
-
import { readMeta } from "../core-dist/profiles.js";
|
|
8
|
-
import { mkdirSync } from "fs";
|
|
9
|
-
import { fundingRecovery, fundingBlocksBootstrap } from "#sdk/node";
|
|
10
|
-
|
|
11
|
-
const USDC_ABI = [{ name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }] }];
|
|
12
|
-
const USDC_SEPOLIA = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
|
|
13
|
-
const PATH_USD = "0x20c0000000000000000000000000000000000000";
|
|
14
|
-
const TEMPO_RPC = "https://rpc.moderato.tempo.xyz/";
|
|
11
|
+
import { fail, reportLocalOrSdkError } from "./sdk-errors.mjs";
|
|
15
12
|
|
|
16
13
|
const HELP = `run402 init — Set up wallet, funding, and check tier status
|
|
17
14
|
|
|
@@ -84,8 +81,6 @@ Steps (idempotent when re-run with the same rail; pass --switch-rail to change r
|
|
|
84
81
|
Run this once to get started, or again to check your setup.
|
|
85
82
|
`;
|
|
86
83
|
|
|
87
|
-
function short(addr) { return addr.slice(0, 6) + "..." + addr.slice(-4); }
|
|
88
|
-
|
|
89
84
|
function parseApiBaseFlag(args) {
|
|
90
85
|
for (let i = 0; i < args.length; i++) {
|
|
91
86
|
const arg = args[i];
|
|
@@ -183,70 +178,9 @@ function parseGitRemoteFlag(args) {
|
|
|
183
178
|
return { value: true, args: [...args.slice(0, idx), ...args.slice(idx + 1)] };
|
|
184
179
|
}
|
|
185
180
|
|
|
186
|
-
function sameOrigin(a, b) {
|
|
187
|
-
try {
|
|
188
|
-
return new URL(a).origin === new URL(b).origin;
|
|
189
|
-
} catch {
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
async function detectTarget(apiBase) {
|
|
195
|
-
try {
|
|
196
|
-
const health = await getSdk({ apiBase, disablePaidFetch: true, authMode: "none" }).service.health();
|
|
197
|
-
const kind = health && typeof health === "object" && health.mode === "core"
|
|
198
|
-
? "core"
|
|
199
|
-
: sameOrigin(apiBase, "https://api.run402.com") ? "cloud" : "core";
|
|
200
|
-
return {
|
|
201
|
-
kind,
|
|
202
|
-
health_status: typeof health?.status === "string" ? health.status : "ok",
|
|
203
|
-
};
|
|
204
|
-
} catch (err) {
|
|
205
|
-
return {
|
|
206
|
-
kind: sameOrigin(apiBase, "https://api.run402.com") ? "cloud" : "core",
|
|
207
|
-
health_error: errorMessage(err),
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function errorMessage(err) {
|
|
213
|
-
if (err?.body && typeof err.body === "object") return err.body.message || err.body.error || err.message;
|
|
214
|
-
return err?.message || String(err);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Which project the vault scaffold acts on — and why it may act on none.
|
|
219
|
-
*
|
|
220
|
-
* The CLI-wide resolution order (`RUN402_PROJECT_ID`, then the active
|
|
221
|
-
* project), not `getActiveProjectId()` alone. Reading only the active project
|
|
222
|
-
* meant an agent that had exported `RUN402_PROJECT_ID` — the normal way to
|
|
223
|
-
* address a project without mutating machine state — got a silent no-op: no
|
|
224
|
-
* remote, no allocation, and no `vault` key in the summary at all, with
|
|
225
|
-
* nothing anywhere saying `run402 projects use` was a prerequisite
|
|
226
|
-
* (dogfood #1, finding A). A command that exits 0 having done nothing is worse
|
|
227
|
-
* than an error, so the no-project case names itself.
|
|
228
|
-
*
|
|
229
|
-
* Exported so the decision is testable on its own: it is one expression, and
|
|
230
|
-
* it was wrong in two ways at once.
|
|
231
|
-
*/
|
|
232
|
-
export function resolveScaffoldProject(env = process.env, activeProjectId = getActiveProjectId()) {
|
|
233
|
-
const fromEnv = typeof env.RUN402_PROJECT_ID === "string" ? env.RUN402_PROJECT_ID.trim() : "";
|
|
234
|
-
const projectId = fromEnv || activeProjectId || null;
|
|
235
|
-
if (projectId) return { projectId, skipped: null };
|
|
236
|
-
return {
|
|
237
|
-
projectId: null,
|
|
238
|
-
skipped: "no project is selected, so there is nothing to point a run402 remote at — run `run402 projects use <project_id>` (or set RUN402_PROJECT_ID), then re-run `run402 init`",
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
|
|
242
181
|
export async function run(args = []) {
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
// outer --help check so `run402 init astro --help` shows the astro
|
|
246
|
-
// scaffolder's help, not the rail-setup help. The rest of init's
|
|
247
|
-
// payment-rail setup is intentionally orthogonal — agents typically
|
|
248
|
-
// run `run402 init astro <dir>` to scaffold AND `run402 init` once
|
|
249
|
-
// to set up wallet / tier.
|
|
182
|
+
// `run402 init astro <dir>` scaffolds an Astro project; handled BEFORE the
|
|
183
|
+
// --help check so `run402 init astro --help` shows the scaffolder's help.
|
|
250
184
|
if (args[0] === "astro") {
|
|
251
185
|
const { runInitAstro } = await import("./init-astro.mjs");
|
|
252
186
|
await runInitAstro(args.slice(1));
|
|
@@ -255,9 +189,8 @@ export async function run(args = []) {
|
|
|
255
189
|
|
|
256
190
|
if (args.includes("--help") || args.includes("-h")) { console.log(HELP); process.exit(0); }
|
|
257
191
|
|
|
258
|
-
// Strip
|
|
259
|
-
//
|
|
260
|
-
// rail name).
|
|
192
|
+
// Strip the value flags before the rail positional is read, so an
|
|
193
|
+
// unrecognized token is never taken for a rail name.
|
|
261
194
|
const parsedVoucher = parseVoucherFlag(args);
|
|
262
195
|
args = parsedVoucher.args;
|
|
263
196
|
const voucherCode = parsedVoucher.value;
|
|
@@ -266,9 +199,6 @@ export async function run(args = []) {
|
|
|
266
199
|
args = parsedGitRemote.args;
|
|
267
200
|
const scaffoldGitRemote = parsedGitRemote.value;
|
|
268
201
|
|
|
269
|
-
// principal-display-name (first-deploy-agent-dx): `--name <name>` sets this
|
|
270
|
-
// principal's display name once (PATCH /agent/v1/me). Stripped here so the
|
|
271
|
-
// rail/positional logic never sees it.
|
|
272
202
|
const parsedName = parseValueFlag(args, "--name");
|
|
273
203
|
args = parsedName.args;
|
|
274
204
|
const displayName = parsedName.value;
|
|
@@ -276,7 +206,9 @@ export async function run(args = []) {
|
|
|
276
206
|
fail({ code: "BAD_USAGE", message: "--name requires a non-empty value.", details: { flag: "--name" } });
|
|
277
207
|
}
|
|
278
208
|
|
|
209
|
+
const onLine = (line) => console.error(line);
|
|
279
210
|
const parsedApiBase = parseApiBaseFlag(args);
|
|
211
|
+
let summary;
|
|
280
212
|
if (parsedApiBase.value) {
|
|
281
213
|
if (parsedApiBase.args.some((arg) => typeof arg === "string" && !arg.startsWith("--"))) {
|
|
282
214
|
fail({
|
|
@@ -285,10 +217,8 @@ export async function run(args = []) {
|
|
|
285
217
|
hint: "Run `run402 init --api-base=http://my-core:4020` for Core, or `run402 init` for Run402 Cloud.",
|
|
286
218
|
});
|
|
287
219
|
}
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
// vouchers are a Run402 Cloud concept. Say so instead of accepting the
|
|
291
|
-
// flag and silently dropping it.
|
|
220
|
+
// The target branch touches no wallet and no project: a voucher has no
|
|
221
|
+
// organization to credit and a git scaffold has nothing to point at.
|
|
292
222
|
if (voucherCode) {
|
|
293
223
|
fail({
|
|
294
224
|
code: "BAD_USAGE",
|
|
@@ -296,9 +226,6 @@ export async function run(args = []) {
|
|
|
296
226
|
hint: "Configure the target first (`run402 init --api-base=…`), then redeem against Run402 Cloud with `run402 redeem <code>`.",
|
|
297
227
|
});
|
|
298
228
|
}
|
|
299
|
-
// Same reasoning as --voucher: this branch configures a target and returns
|
|
300
|
-
// without an active project, so a git scaffold has nothing to point at.
|
|
301
|
-
// Say so rather than accepting the flag and silently dropping it.
|
|
302
229
|
if (scaffoldGitRemote) {
|
|
303
230
|
fail({
|
|
304
231
|
code: "BAD_USAGE",
|
|
@@ -306,497 +233,29 @@ export async function run(args = []) {
|
|
|
306
233
|
hint: "Configure the target first (`run402 init --api-base=…`), provision a project, then run `run402 init --git-remote` from the project directory.",
|
|
307
234
|
});
|
|
308
235
|
}
|
|
309
|
-
const CONFIG_DIR = configDir();
|
|
310
|
-
const detected = await detectTarget(parsedApiBase.value);
|
|
311
|
-
const config = configureApiBase(parsedApiBase.value, {
|
|
312
|
-
target_kind: detected.kind,
|
|
313
|
-
...(detected.health_status ? { health_status: detected.health_status } : {}),
|
|
314
|
-
...(detected.health_error ? { health_error: detected.health_error } : {}),
|
|
315
|
-
});
|
|
316
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
317
|
-
console.error("");
|
|
318
|
-
console.error(` ${"Config".padEnd(10)} ${CONFIG_DIR}`);
|
|
319
|
-
console.error(` ${"API base".padEnd(10)} ${config.api_base}`);
|
|
320
|
-
console.error(` ${"Target".padEnd(10)} ${config.target_kind}`);
|
|
321
|
-
if (detected.health_status) console.error(` ${"Health".padEnd(10)} ${detected.health_status}`);
|
|
322
|
-
if (detected.health_error) console.error(` ${"Health".padEnd(10)} ${detected.health_error}`);
|
|
323
|
-
console.error("");
|
|
324
|
-
const summary = {
|
|
325
|
-
config_dir: CONFIG_DIR,
|
|
326
|
-
api_base: config.api_base,
|
|
327
|
-
api_base_source: "profile",
|
|
328
|
-
target: {
|
|
329
|
-
kind: config.target_kind,
|
|
330
|
-
...(config.health_status ? { health_status: config.health_status } : {}),
|
|
331
|
-
...(config.health_error ? { health_error: config.health_error } : {}),
|
|
332
|
-
},
|
|
333
|
-
payment_required: config.target_kind === "cloud",
|
|
334
|
-
next_actions: [{
|
|
335
|
-
type: "create_project",
|
|
336
|
-
command: 'run402 projects provision --name "my-app"',
|
|
337
|
-
}],
|
|
338
|
-
next_step: 'run402 projects provision --name "my-app"',
|
|
339
|
-
};
|
|
340
|
-
console.log(JSON.stringify(summary, null, 2));
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// Resolve once for this invocation — reflects the active wallet/profile that
|
|
345
|
-
// cli.mjs published to RUN402_WALLET before this module loaded.
|
|
346
|
-
const CONFIG_DIR = configDir();
|
|
347
|
-
|
|
348
|
-
const isMpp = args[0] === "mpp";
|
|
349
|
-
const isLightning = args[0] === "lightning";
|
|
350
|
-
const requestedRail = isLightning ? "lightning" : isMpp ? "mpp" : "x402";
|
|
351
|
-
const switchRailConfirmed = args.includes("--switch-rail");
|
|
352
|
-
|
|
353
|
-
const existingWallet = readWallet();
|
|
354
|
-
if (existingWallet?.rail && existingWallet.rail !== requestedRail && !switchRailConfirmed) {
|
|
355
|
-
fail({
|
|
356
|
-
code: "RAIL_SWITCH_REQUIRES_CONFIRM",
|
|
357
|
-
message: `Already on rail '${existingWallet.rail}'. Pass --switch-rail to switch to '${requestedRail}'.`,
|
|
358
|
-
details: { current_rail: existingWallet.rail, requested_rail: requestedRail },
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// Human-readable progress lines go to stderr so stdout stays JSON-clean for
|
|
363
|
-
// agents. Final structured summary emits to stdout at the end.
|
|
364
|
-
const write = (s) => console.error(s);
|
|
365
|
-
const line = (label, value) => write(` ${label.padEnd(10)} ${value}`);
|
|
366
|
-
const summary = {
|
|
367
|
-
config_dir: CONFIG_DIR,
|
|
368
|
-
wallet: null,
|
|
369
|
-
rail: null,
|
|
370
|
-
network: null,
|
|
371
|
-
balances: null,
|
|
372
|
-
// Present (null or an object) only when --voucher was passed, so its
|
|
373
|
-
// absence means "no code was offered" rather than "a code silently
|
|
374
|
-
// vanished". `voucher_error` appears alongside a null `voucher`.
|
|
375
|
-
...(voucherCode ? { voucher: null } : {}),
|
|
376
|
-
tier: null,
|
|
377
|
-
projects_saved: 0,
|
|
378
|
-
/** The project init acted on — the input to the vault scaffold decision. */
|
|
379
|
-
active_project_id: null,
|
|
380
|
-
next_actions: [],
|
|
381
|
-
next_step: null,
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
write("");
|
|
385
|
-
|
|
386
|
-
// 1. Config directory
|
|
387
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
388
|
-
line("Config", CONFIG_DIR);
|
|
389
|
-
|
|
390
|
-
// 2. Wallet
|
|
391
|
-
let localWallet = existingWallet;
|
|
392
|
-
const previousRail = localWallet?.rail;
|
|
393
|
-
if (!localWallet) {
|
|
394
|
-
const { generatePrivateKey, privateKeyToAccount } = await import("viem/accounts");
|
|
395
|
-
const privateKey = generatePrivateKey();
|
|
396
|
-
const account = privateKeyToAccount(privateKey);
|
|
397
|
-
localWallet = { address: account.address, privateKey, created: new Date().toISOString(), funded: false, rail: requestedRail };
|
|
398
|
-
saveWallet(localWallet);
|
|
399
|
-
line("Wallet", `${short(localWallet.address)} (created)`);
|
|
400
|
-
} else {
|
|
401
|
-
// Update rail if switching (a wallet leaving Lightning keeps its pairing on disk until revoked).
|
|
402
|
-
if (localWallet.rail !== requestedRail) {
|
|
403
|
-
localWallet = { ...localWallet, rail: requestedRail };
|
|
404
|
-
saveWallet(localWallet);
|
|
405
|
-
}
|
|
406
|
-
line("Wallet", short(localWallet.address));
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
const walletName = getActiveProfile();
|
|
410
|
-
const walletMeta = readMeta(walletName);
|
|
411
|
-
summary.wallet = { local_label: walletName, server_label: walletMeta?.label ?? null, address: localWallet.address };
|
|
412
|
-
summary.network = isLightning ? "bitcoin-mainnet" : isMpp ? "tempo-moderato" : "base-sepolia";
|
|
413
|
-
summary.rail = requestedRail;
|
|
414
|
-
|
|
415
|
-
line("Network", isLightning ? "Bitcoin mainnet (Lightning) + Base Sepolia fallback" : isMpp ? "Tempo Moderato (testnet)" : "Base Sepolia (testnet)");
|
|
416
|
-
line("Rail", requestedRail);
|
|
417
|
-
|
|
418
|
-
// 3. Balance — check on-chain, faucet if zero
|
|
419
|
-
let balance = 0;
|
|
420
|
-
let fundingError;
|
|
421
|
-
let fundingPending = false;
|
|
422
|
-
|
|
423
|
-
if (isMpp) {
|
|
424
|
-
// Tempo Moderato: read pathUSD balance
|
|
425
|
-
const { createPublicClient, http, defineChain } = await import("viem");
|
|
426
|
-
const tempoModerato = defineChain({
|
|
427
|
-
id: 42431,
|
|
428
|
-
name: "Tempo Moderato",
|
|
429
|
-
nativeCurrency: { name: "pathUSD", symbol: "pathUSD", decimals: 6 },
|
|
430
|
-
rpcUrls: { default: { http: [TEMPO_RPC] } },
|
|
431
|
-
});
|
|
432
|
-
const client = createPublicClient({ chain: tempoModerato, transport: http() });
|
|
433
|
-
|
|
434
|
-
try {
|
|
435
|
-
const raw = await client.readContract({ address: PATH_USD, abi: USDC_ABI, functionName: "balanceOf", args: [localWallet.address] });
|
|
436
|
-
balance = Number(raw);
|
|
437
|
-
} catch {}
|
|
438
|
-
|
|
439
|
-
if (balance === 0) {
|
|
440
|
-
line("Balance", "0 pathUSD — requesting Tempo faucet...");
|
|
441
|
-
try {
|
|
442
|
-
const res = await fetch(TEMPO_RPC, {
|
|
443
|
-
method: "POST",
|
|
444
|
-
headers: { "Content-Type": "application/json" },
|
|
445
|
-
body: JSON.stringify({ jsonrpc: "2.0", method: "tempo_fundAddress", params: [localWallet.address], id: 1 }),
|
|
446
|
-
});
|
|
447
|
-
const data = await res.json();
|
|
448
|
-
if (data.result) {
|
|
449
|
-
// Tempo faucet is "instant" on-chain, but the client RPC read can be
|
|
450
|
-
// racy relative to faucet settlement — poll up to 30s, mirroring
|
|
451
|
-
// the x402 path below.
|
|
452
|
-
for (let i = 0; i < 30; i++) {
|
|
453
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
454
|
-
try {
|
|
455
|
-
const raw = await client.readContract({ address: PATH_USD, abi: USDC_ABI, functionName: "balanceOf", args: [localWallet.address] });
|
|
456
|
-
balance = Number(raw);
|
|
457
|
-
if (balance > 0) break;
|
|
458
|
-
} catch {}
|
|
459
|
-
}
|
|
460
|
-
saveWallet({ ...localWallet, funded: true, lastFaucet: new Date().toISOString() });
|
|
461
|
-
if (balance > 0) {
|
|
462
|
-
line("Balance", `${(balance / 1e6).toFixed(2)} pathUSD (funded)`);
|
|
463
|
-
} else {
|
|
464
|
-
fundingPending = true;
|
|
465
|
-
line("Balance", "faucet sent — not yet confirmed on-chain");
|
|
466
|
-
}
|
|
467
|
-
} else {
|
|
468
|
-
fundingError = data.error ?? new Error("Faucet failed");
|
|
469
|
-
line("Balance", `faucet failed: ${data.error?.message || "unknown error"}`);
|
|
470
|
-
}
|
|
471
|
-
} catch (err) {
|
|
472
|
-
fundingError = err;
|
|
473
|
-
line("Balance", `faucet error: ${err.message}`);
|
|
474
|
-
}
|
|
475
|
-
} else {
|
|
476
|
-
line("Balance", `${(balance / 1e6).toFixed(2)} pathUSD`);
|
|
477
|
-
}
|
|
478
|
-
} else {
|
|
479
|
-
// Base Sepolia: read USDC balance (existing behavior)
|
|
480
|
-
const { createPublicClient, http } = await import("viem");
|
|
481
|
-
const { baseSepolia } = await import("viem/chains");
|
|
482
|
-
const client = createPublicClient({ chain: baseSepolia, transport: http() });
|
|
483
|
-
|
|
484
|
-
try {
|
|
485
|
-
const raw = await client.readContract({ address: USDC_SEPOLIA, abi: USDC_ABI, functionName: "balanceOf", args: [localWallet.address] });
|
|
486
|
-
balance = Number(raw);
|
|
487
|
-
} catch {}
|
|
488
|
-
|
|
489
|
-
if (balance === 0) {
|
|
490
|
-
line("Balance", "0 USDC — requesting faucet...");
|
|
491
|
-
try {
|
|
492
|
-
await getSdk().wallets.faucet(localWallet.address);
|
|
493
|
-
// Poll for up to 30s
|
|
494
|
-
for (let i = 0; i < 30; i++) {
|
|
495
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
496
|
-
try {
|
|
497
|
-
const raw = await client.readContract({ address: USDC_SEPOLIA, abi: USDC_ABI, functionName: "balanceOf", args: [localWallet.address] });
|
|
498
|
-
balance = Number(raw);
|
|
499
|
-
if (balance > 0) break;
|
|
500
|
-
} catch {}
|
|
501
|
-
}
|
|
502
|
-
saveWallet({ ...localWallet, funded: true, lastFaucet: new Date().toISOString() });
|
|
503
|
-
if (balance > 0) {
|
|
504
|
-
line("Balance", `${(balance / 1e6).toFixed(2)} USDC (funded)`);
|
|
505
|
-
} else {
|
|
506
|
-
fundingPending = true;
|
|
507
|
-
line("Balance", "faucet sent — not yet confirmed on-chain");
|
|
508
|
-
}
|
|
509
|
-
} catch (err) {
|
|
510
|
-
fundingError = err;
|
|
511
|
-
line("Balance", `faucet failed: ${errorMessage(err)}`);
|
|
512
|
-
}
|
|
513
|
-
} else {
|
|
514
|
-
line("Balance", `${(balance / 1e6).toFixed(2)} USDC`);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// 3b. Promo code, when one was handed to us.
|
|
519
|
-
//
|
|
520
|
-
// This runs BEFORE the balance read below so the redeemed amount shows up in
|
|
521
|
-
// `allowance_usd_micros` without a second round-trip, and AFTER the
|
|
522
|
-
// wallet exists so the redemption authenticates as this agent.
|
|
523
|
-
//
|
|
524
|
-
// NOTHING here may fail init. An advertised gift that dead-ends a build is
|
|
525
|
-
// worse than no gift at all: the agent was told to run one command, and that
|
|
526
|
-
// command must still leave it set up and able to work. Every failure — bad
|
|
527
|
-
// code, expired, already used by someone else, org at its ceiling, network
|
|
528
|
-
// down, gateway too old to know the route — warns on stderr, records
|
|
529
|
-
// `voucher_error` in the JSON summary, and lets setup finish.
|
|
530
|
-
if (voucherCode) {
|
|
531
236
|
try {
|
|
532
|
-
|
|
533
|
-
const redeemed = (redemption.amount_usd_micros / 1_000_000).toFixed(2);
|
|
534
|
-
line(
|
|
535
|
-
"Voucher",
|
|
536
|
-
redemption.already_redeemed
|
|
537
|
-
? `$${redeemed} already added to the allowance (not added twice)`
|
|
538
|
-
: `$${redeemed} added to the allowance`,
|
|
539
|
-
);
|
|
540
|
-
summary.voucher = {
|
|
541
|
-
voucher_id: redemption.voucher_id,
|
|
542
|
-
amount_usd_micros: redemption.amount_usd_micros,
|
|
543
|
-
already_redeemed: redemption.already_redeemed,
|
|
544
|
-
next_actions: Array.isArray(redemption.next_actions) ? redemption.next_actions : [],
|
|
545
|
-
};
|
|
546
|
-
// Relate the gift to what it buys, in the gateway's own words: its
|
|
547
|
-
// `set_tier` next action names the largest tier the allowance covers, and
|
|
548
|
-
// that purchase settles from the allowance — no wallet funding step between.
|
|
549
|
-
const covers = summary.voucher.next_actions.find((a) => a?.type === "set_tier" && typeof a.cli === "string");
|
|
550
|
-
if (covers) {
|
|
551
|
-
const tierName = covers.highest_affordable_tier ?? covers.cli.replace(/^run402 tier set\s+/, "");
|
|
552
|
-
line("Allowance", `$${redeemed} covers ${tierName} — ${covers.cli} (settles from the allowance; no wallet funds needed)`);
|
|
553
|
-
}
|
|
237
|
+
summary = await getSdk().init({ apiBase: parsedApiBase.value, onLine });
|
|
554
238
|
} catch (err) {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
const reason = errorMessage(err);
|
|
558
|
-
line("Voucher", `not applied: ${reason}`);
|
|
559
|
-
summary.voucher = null;
|
|
560
|
-
summary.voucher_error = {
|
|
561
|
-
code: err?.body?.code ?? err?.code ?? "VOUCHER_REDEEM_FAILED",
|
|
562
|
-
message: reason,
|
|
563
|
-
};
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// Balances mirror `run402 status`: the on-chain wallet figure above plus the
|
|
568
|
-
// organization's Run402-held allowance (rail-independent). The allowance is
|
|
569
|
-
// fetched best-effort so a billing read failure never blocks setup.
|
|
570
|
-
const billing = await getSdk().billing.checkBalance(localWallet.address).catch(() => null);
|
|
571
|
-
const hasBilling = billing && billing.exists !== false;
|
|
572
|
-
summary.balances = {
|
|
573
|
-
on_chain_usd_micros: balance,
|
|
574
|
-
on_chain_token: isMpp ? "pathUSD" : "USDC",
|
|
575
|
-
allowance_usd_micros: hasBilling ? billing.allowance_usd_micros : null,
|
|
576
|
-
held_usd_micros: hasBilling ? (billing.held_usd_micros ?? 0) : null,
|
|
577
|
-
};
|
|
578
|
-
|
|
579
|
-
// Show note if switching rails
|
|
580
|
-
if (previousRail && previousRail !== (isMpp ? "mpp" : "x402")) {
|
|
581
|
-
const prev = previousRail === "mpp" ? "Tempo pathUSD" : "Base Sepolia USDC";
|
|
582
|
-
line("Note", `Switched from ${previousRail} — ${prev} balance still available if you switch back`);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
// 3b. The Lightning wallet (mpp-lightning-over-nwc): a budgeted wallet
|
|
586
|
-
// on Run402's Hub, minted by the platform, its pairing kept beside the
|
|
587
|
-
// Base key. Never printed. A Hub that is not configured leaves the rail
|
|
588
|
-
// on Lightning with x402 as the live fallback, and says so.
|
|
589
|
-
summary.lightning = null;
|
|
590
|
-
if (isLightning) {
|
|
591
|
-
try {
|
|
592
|
-
const { ensureLightningWallet, describeLightning, readLightningBalance } = await import("./lightning-wallet.mjs");
|
|
593
|
-
const result = await ensureLightningWallet();
|
|
594
|
-
localWallet = result.localWallet;
|
|
595
|
-
const settled = result.outcome === "stored" || result.outcome === "present";
|
|
596
|
-
const balance = settled ? await readLightningBalance(localWallet) : null;
|
|
597
|
-
summary.lightning = { ...(describeLightning(localWallet, result.wallet, balance) ?? {}), outcome: result.outcome };
|
|
598
|
-
if (result.outcome === "stored") {
|
|
599
|
-
line("Lightning", `wallet minted on Run402's Hub (${result.wallet.budget_sats} sats budget, ${result.wallet.starter_sats} starter)`);
|
|
600
|
-
} else if (result.outcome === "present") {
|
|
601
|
-
line("Lightning", `wallet ${localWallet.lightning.wallet_id}${balance ? ` — ${balance.balance_sats} sats` : ""}`);
|
|
602
|
-
} else if (result.outcome === "minting") {
|
|
603
|
-
line("Lightning", "the platform is still minting the wallet — rerun `run402 init lightning` in a few seconds");
|
|
604
|
-
} else if (result.outcome === "unavailable") {
|
|
605
|
-
line("Lightning", "no Hub on this gateway — paying over x402 until it is back");
|
|
606
|
-
} else if (result.outcome === "pairing_lost") {
|
|
607
|
-
line("Lightning", "wallet is active but its pairing was handed to another machine — `run402 wallets lightning revoke`, then init again");
|
|
608
|
-
} else {
|
|
609
|
-
line("Lightning", `wallet is ${result.outcome}`);
|
|
610
|
-
}
|
|
611
|
-
} catch (err) {
|
|
612
|
-
summary.lightning = { outcome: "error", code: err?.body?.code ?? err?.code ?? "LIGHTNING_WALLET_FAILED", message: err?.message ?? String(err) };
|
|
613
|
-
line("Lightning", `wallet setup failed: ${err?.message ?? String(err)} — paying over x402 until it is fixed`);
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// 4. Tier status
|
|
618
|
-
const store = loadKeyStore();
|
|
619
|
-
let tierInfo = null;
|
|
620
|
-
try {
|
|
621
|
-
tierInfo = await getSdk().tier.status();
|
|
622
|
-
} catch {}
|
|
623
|
-
|
|
624
|
-
if (tierInfo && tierInfo.tier && tierInfo.active) {
|
|
625
|
-
const expiry = tierInfo.lease_expires_at ? tierInfo.lease_expires_at.split("T")[0] : "unknown";
|
|
626
|
-
line("Tier", `${tierInfo.tier} (expires ${expiry})`);
|
|
627
|
-
summary.tier = { name: tierInfo.tier, expires: tierInfo.lease_expires_at || null };
|
|
628
|
-
} else {
|
|
629
|
-
line("Tier", "(none)");
|
|
630
|
-
summary.tier = null;
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// 5. Projects — count locally saved project entries. Note: "saved" (not
|
|
634
|
-
// "active") — these are all projects in the keystore, regardless of whether
|
|
635
|
-
// the server considers them active. A project selected with
|
|
636
|
-
// `run402 projects use` but never provisioned FROM this machine holds no
|
|
637
|
-
// local keys, so it is legitimately not counted here; `active_project_id`
|
|
638
|
-
// below is the field that says which project init actually acted on, and
|
|
639
|
-
// reading `projects_saved: 0` as "init did nothing" is what made the
|
|
640
|
-
// vault skip look silent (dogfood #1, finding A).
|
|
641
|
-
summary.projects_saved = Object.keys(store.projects).length;
|
|
642
|
-
line("Projects", `${summary.projects_saved} saved`);
|
|
643
|
-
|
|
644
|
-
// 5b. vault git remote (vault-client-surface, task 5.7).
|
|
645
|
-
//
|
|
646
|
-
// Purely LOCAL git. No vault is allocated and no key material is written
|
|
647
|
-
// here — the spec is explicit that neither exists until first capture, so the
|
|
648
|
-
// cold-start path gains no prompt and no new failure mode. Allocation happens
|
|
649
|
-
// on the first `git push origin <branch>` or `run402 repos capture` (D2,
|
|
650
|
-
// repo-first-onramp) — NOT on deploy: `applyWithVault` only ever reads an
|
|
651
|
-
// EXISTING vault's policy (D3) and never allocates one that does not exist,
|
|
652
|
-
// so a project with no vault deploys exactly as it always did.
|
|
653
|
-
//
|
|
654
|
-
// Adding the remote is the DEFAULT inside a repository that already exists,
|
|
655
|
-
// because it is pure addition: `origin` is never modified or claimed, no file
|
|
656
|
-
// is created, nothing is rewritten. CREATING a repository is NOT the default
|
|
657
|
-
// — `run402 init` is routinely run outside a project directory, and
|
|
658
|
-
// `git init`-ing whatever directory the user happened to be in would be a
|
|
659
|
-
// genuinely bad surprise. `--git-remote` opts into that one step.
|
|
660
|
-
//
|
|
661
|
-
// NON-FATAL in every branch: a missing git, a directory that is not a
|
|
662
|
-
// repository, an unreachable gateway, or a `run402` remote already pointing
|
|
663
|
-
// somewhere else must warn and let setup finish.
|
|
664
|
-
const { projectId: activeProjectId, skipped: noProjectSkip } = resolveScaffoldProject();
|
|
665
|
-
summary.active_project_id = activeProjectId;
|
|
666
|
-
if (noProjectSkip) {
|
|
667
|
-
summary.vault = null;
|
|
668
|
-
summary.vault_skipped = noProjectSkip;
|
|
669
|
-
line("Vault", "skipped — no project selected (run402 projects use <project_id>)");
|
|
670
|
-
} else {
|
|
671
|
-
summary.vault = null;
|
|
672
|
-
try {
|
|
673
|
-
// Dynamic import: the scaffold is the only thing here that needs the
|
|
674
|
-
// Node SDK's hardened git runner, and a top-level import would drag it
|
|
675
|
-
// into every init invocation (and every test that mocks ./sdk.mjs).
|
|
676
|
-
const { hardenedGit } = await import("#sdk/node");
|
|
677
|
-
let insideRepo = true;
|
|
678
|
-
try {
|
|
679
|
-
await hardenedGit(process.cwd(), ["rev-parse", "--git-dir"]);
|
|
680
|
-
} catch {
|
|
681
|
-
insideRepo = false;
|
|
682
|
-
}
|
|
683
|
-
if (!insideRepo && !scaffoldGitRemote) {
|
|
684
|
-
summary.vault_skipped = "not a git repository — re-run with --git-remote to create one and add the remote";
|
|
685
|
-
line("Vault", "skipped — not a git repository (--git-remote creates one)");
|
|
686
|
-
} else {
|
|
687
|
-
const orgId = await resolveOwningOrgId(activeProjectId);
|
|
688
|
-
if (!orgId) {
|
|
689
|
-
summary.vault_skipped = `could not resolve the owning org for ${activeProjectId} — the run402 remote was not added`;
|
|
690
|
-
line("Vault", "skipped — owning org unresolved");
|
|
691
|
-
} else {
|
|
692
|
-
const remote = await getSdk().repos.scaffoldRemote({
|
|
693
|
-
repo_dir: process.cwd(),
|
|
694
|
-
org_id: orgId,
|
|
695
|
-
project_id: activeProjectId,
|
|
696
|
-
});
|
|
697
|
-
// `allocated: false` is stated, not left to be inferred: this was
|
|
698
|
-
// local git only, and no vault exists for the project yet.
|
|
699
|
-
summary.vault = { ...remote, allocated: false };
|
|
700
|
-
if (remote.already_present && remote.existing_url !== remote.url) {
|
|
701
|
-
// Left exactly as it was. Name the URL that is actually in place
|
|
702
|
-
// rather than implying the remote now points at this project.
|
|
703
|
-
line("Vault", `remote '${remote.name}' already points at ${remote.existing_url} — left unchanged (${remote.reason})`);
|
|
704
|
-
} else if (remote.already_present) {
|
|
705
|
-
line("Vault", `remote '${remote.name}' already set (${remote.url})`);
|
|
706
|
-
} else {
|
|
707
|
-
// D1: `origin` when it was free, `run402` when it was already
|
|
708
|
-
// taken by something else — `remote.reason` says which happened.
|
|
709
|
-
line("Vault", `${remote.created_repository ? "initialized a repository and added" : "added"} remote '${remote.name}' -> ${remote.url} (${remote.reason})`);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
} catch (err) {
|
|
714
|
-
const reason = errorMessage(err);
|
|
715
|
-
summary.vault = null;
|
|
716
|
-
summary.vault_error = {
|
|
717
|
-
code: err?.body?.code ?? err?.code ?? "VAULT_SCAFFOLD_FAILED",
|
|
718
|
-
message: reason,
|
|
719
|
-
};
|
|
720
|
-
line("Vault", `remote not added: ${reason}`);
|
|
239
|
+
reportLocalOrSdkError(err);
|
|
240
|
+
return;
|
|
721
241
|
}
|
|
242
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
243
|
+
return;
|
|
722
244
|
}
|
|
723
245
|
|
|
724
|
-
|
|
725
|
-
// authenticates, which provisions the wallet's org-of-one), so NOT reporting
|
|
726
|
-
// it forced every agent that wanted to coordinate to go find it with a second
|
|
727
|
-
// command and copy a UUID by hand. It is an identifier, not a credential.
|
|
246
|
+
const rail = args[0] === "lightning" ? "lightning" : args[0] === "mpp" ? "mpp" : "x402";
|
|
728
247
|
try {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
}
|
|
741
|
-
} catch {
|
|
742
|
-
// Best-effort, exactly like the billing read: a listing failure must never
|
|
743
|
-
// fail setup.
|
|
744
|
-
summary.orgs = null;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
// 6. Next step — canonical typed action(s); `next_step` is the back-compat
|
|
748
|
-
// string mirror of the first action's command (one spelling, surface-wide).
|
|
749
|
-
write("");
|
|
750
|
-
const tierMissing = !tierInfo || !tierInfo.tier || !tierInfo.active;
|
|
751
|
-
summary.funding = fundingRecovery(fundingError, fundingPending);
|
|
752
|
-
const fundingBlocked = fundingBlocksBootstrap(summary.funding, {
|
|
753
|
-
activeTier: !tierMissing, onChainBalance: balance,
|
|
754
|
-
allowanceBalance: summary.balances.allowance_usd_micros,
|
|
755
|
-
lightningBalance: summary.lightning?.balance_sats,
|
|
756
|
-
});
|
|
757
|
-
summary.next_actions = [tierMissing ? upDeployAction() : deployAction()];
|
|
758
|
-
if (fundingBlocked) {
|
|
759
|
-
const quotedWallet = `'${walletName.replaceAll("'", "'\\''")}'`;
|
|
760
|
-
summary.next_actions = summary.funding.next_actions.map(action => ({ ...action,
|
|
761
|
-
...(["retry", "check_balance"].includes(action.type) ? {
|
|
762
|
-
command: `run402 --wallet ${quotedWallet} wallets ${action.type === "retry" ? "fund" : "balance"}`,
|
|
763
|
-
} : {}),
|
|
764
|
-
}));
|
|
765
|
-
}
|
|
766
|
-
summary.next_step = summary.next_actions[0].command ?? null;
|
|
767
|
-
if (fundingBlocked) {
|
|
768
|
-
line("Funding", summary.funding.status);
|
|
769
|
-
write(` ${summary.next_actions[0].why}`);
|
|
770
|
-
if (summary.next_step) write(` Next: ${summary.next_step}`);
|
|
771
|
-
} else if (tierMissing) {
|
|
772
|
-
// `up -y` sets the prototype tier itself as part of the first
|
|
773
|
-
// deploy; `init` never buys the tier, so the one command that finishes
|
|
774
|
-
// the cold start is `up`, with `tier set` named as the standalone option.
|
|
775
|
-
const allowanceCovers = summary.voucher?.next_actions?.find((a) => a?.type === "set_tier" && typeof a.cli === "string");
|
|
776
|
-
write(" Next: run402 up -y");
|
|
777
|
-
write(" Deploy with run402 up -y — it sets the prototype tier (free on testnet) as part of the first deploy.");
|
|
778
|
-
if (allowanceCovers) {
|
|
779
|
-
write(` Your allowance covers ${allowanceCovers.highest_affordable_tier ?? "a larger tier"}: ${allowanceCovers.cli} — then run402 up -y.`);
|
|
780
|
-
} else {
|
|
781
|
-
write(" Or set it separately: run402 tier set prototype.");
|
|
782
|
-
}
|
|
783
|
-
} else {
|
|
784
|
-
write(" Ready to deploy. Run: run402 deploy --manifest app.json");
|
|
785
|
-
}
|
|
786
|
-
write("");
|
|
787
|
-
|
|
788
|
-
// 5c. Display name (principal-display-name). Best-effort: a hiccup never
|
|
789
|
-
// fails init — the summary says what happened.
|
|
790
|
-
if (displayName !== undefined) {
|
|
791
|
-
try {
|
|
792
|
-
const me = await getSdk().orgs.setDisplayName(displayName.trim());
|
|
793
|
-
summary.display_name = me?.principal?.display_name ?? displayName.trim();
|
|
794
|
-
line("Name", summary.display_name);
|
|
795
|
-
} catch (err) {
|
|
796
|
-
summary.display_name = null;
|
|
797
|
-
summary.display_name_error = { code: err?.body?.code ?? err?.code ?? "DISPLAY_NAME_FAILED", message: err?.message ?? String(err) };
|
|
798
|
-
}
|
|
248
|
+
summary = await getSdk().init({
|
|
249
|
+
rail,
|
|
250
|
+
switchRail: args.includes("--switch-rail"),
|
|
251
|
+
voucher: voucherCode,
|
|
252
|
+
gitRemote: scaffoldGitRemote,
|
|
253
|
+
...(displayName !== undefined ? { name: displayName } : {}),
|
|
254
|
+
onLine,
|
|
255
|
+
});
|
|
256
|
+
} catch (err) {
|
|
257
|
+
reportLocalOrSdkError(err);
|
|
258
|
+
return;
|
|
799
259
|
}
|
|
800
|
-
|
|
801
260
|
console.log(JSON.stringify(summary, null, 2));
|
|
802
261
|
}
|