run402 1.69.5 → 1.69.7
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/lib/agent.mjs +37 -11
- package/lib/apps.mjs +92 -40
- package/lib/argparse.mjs +53 -1
- package/lib/billing.mjs +65 -24
- package/lib/blob.mjs +87 -49
- package/lib/cdn.mjs +21 -8
- package/lib/contracts.mjs +112 -33
- package/lib/deploy-v2.mjs +159 -62
- package/lib/domains.mjs +22 -11
- package/lib/functions.mjs +42 -6
- package/lib/image.mjs +17 -8
- package/lib/message.mjs +4 -1
- package/lib/secrets.mjs +29 -12
- package/lib/sender-domain.mjs +32 -14
- package/lib/sites.mjs +39 -16
- package/lib/subdomains.mjs +31 -35
- package/lib/tier.mjs +26 -4
- package/package.json +1 -1
- package/sdk/dist/namespaces/ai.d.ts.map +1 -1
- package/sdk/dist/namespaces/ai.js +7 -2
- package/sdk/dist/namespaces/ai.js.map +1 -1
- package/sdk/dist/namespaces/blobs.d.ts +3 -2
- package/sdk/dist/namespaces/blobs.d.ts.map +1 -1
- package/sdk/dist/namespaces/blobs.js +38 -7
- package/sdk/dist/namespaces/blobs.js.map +1 -1
- package/sdk/dist/namespaces/blobs.types.d.ts +5 -6
- package/sdk/dist/namespaces/blobs.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/contracts.d.ts.map +1 -1
- package/sdk/dist/namespaces/contracts.js +17 -4
- package/sdk/dist/namespaces/contracts.js.map +1 -1
- package/sdk/dist/namespaces/deploy.d.ts.map +1 -1
- package/sdk/dist/namespaces/deploy.js +13 -1
- package/sdk/dist/namespaces/deploy.js.map +1 -1
- package/sdk/dist/namespaces/functions.d.ts.map +1 -1
- package/sdk/dist/namespaces/functions.js +92 -1
- package/sdk/dist/namespaces/functions.js.map +1 -1
- package/sdk/dist/node/files.d.ts +11 -5
- package/sdk/dist/node/files.d.ts.map +1 -1
- package/sdk/dist/node/files.js +31 -7
- package/sdk/dist/node/files.js.map +1 -1
- package/sdk/dist/validation.d.ts +2 -0
- package/sdk/dist/validation.d.ts.map +1 -1
- package/sdk/dist/validation.js +10 -0
- package/sdk/dist/validation.js.map +1 -1
package/lib/blob.mjs
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
readFileSync,
|
|
25
25
|
writeFileSync,
|
|
26
26
|
mkdirSync,
|
|
27
|
+
chmodSync,
|
|
27
28
|
existsSync,
|
|
28
29
|
unlinkSync,
|
|
29
30
|
readdirSync,
|
|
@@ -61,7 +62,7 @@ Options:
|
|
|
61
62
|
--json NDJSON progress events (for agent consumption)
|
|
62
63
|
--prefix <p> Prefix filter (ls only)
|
|
63
64
|
--limit <n> Max results (ls only; default 100, max 1000)
|
|
64
|
-
--ttl <seconds> Signed-URL TTL (sign only; default 3600, max 604800)
|
|
65
|
+
--ttl <seconds> Signed-URL TTL (sign only; default 3600, min 60, max 604800)
|
|
65
66
|
|
|
66
67
|
Examples:
|
|
67
68
|
run402 blob put ./artifact.tgz --project prj_abc123
|
|
@@ -151,7 +152,7 @@ Arguments:
|
|
|
151
152
|
|
|
152
153
|
Options:
|
|
153
154
|
--project <id> Project ID (defaults to active project)
|
|
154
|
-
--ttl <seconds> Signed-URL TTL (default 3600, max 604800)
|
|
155
|
+
--ttl <seconds> Signed-URL TTL (default 3600, min 60, max 604800)
|
|
155
156
|
|
|
156
157
|
Examples:
|
|
157
158
|
run402 blob sign reports/2025-q4.pdf --project prj_abc123 --ttl 600
|
|
@@ -184,7 +185,9 @@ Examples:
|
|
|
184
185
|
`,
|
|
185
186
|
};
|
|
186
187
|
|
|
187
|
-
|
|
188
|
+
function uploadStateDir() {
|
|
189
|
+
return join(homedir(), ".run402", "uploads");
|
|
190
|
+
}
|
|
188
191
|
|
|
189
192
|
function die(msg, exit_code = 1) {
|
|
190
193
|
fail({ code: "BAD_USAGE", message: msg, exit_code });
|
|
@@ -226,7 +229,7 @@ function parseArgs(rawArgs) {
|
|
|
226
229
|
else if (a === "--prefix") out.prefix = args[++i];
|
|
227
230
|
else if (a === "--limit") out.limit = parseIntegerFlag("--limit", args[++i], { min: 1, max: 1000 });
|
|
228
231
|
else if (a === "--output" || a === "-o") out.output = args[++i];
|
|
229
|
-
else if (a === "--ttl") out.ttl = parseIntegerFlag("--ttl", args[++i], { min:
|
|
232
|
+
else if (a === "--ttl") out.ttl = parseIntegerFlag("--ttl", args[++i], { min: 60, max: 604800 });
|
|
230
233
|
else if (!a.startsWith("--")) out.positional.push(a);
|
|
231
234
|
}
|
|
232
235
|
return out;
|
|
@@ -259,30 +262,71 @@ async function sha256File(filePath) {
|
|
|
259
262
|
return h.digest("hex");
|
|
260
263
|
}
|
|
261
264
|
|
|
265
|
+
function sha256BufferHexAndBase64(body) {
|
|
266
|
+
const digest = createHash("sha256").update(body).digest();
|
|
267
|
+
return {
|
|
268
|
+
hex: digest.toString("hex"),
|
|
269
|
+
base64: digest.toString("base64"),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function checksumHeadersForPresignedUrl(url, checksumBase64) {
|
|
274
|
+
let urlHasChecksum = false;
|
|
275
|
+
try {
|
|
276
|
+
urlHasChecksum = new URL(url).searchParams.has("x-amz-checksum-sha256");
|
|
277
|
+
} catch {
|
|
278
|
+
urlHasChecksum = false;
|
|
279
|
+
}
|
|
280
|
+
return urlHasChecksum ? {} : { "x-amz-checksum-sha256": checksumBase64 };
|
|
281
|
+
}
|
|
282
|
+
|
|
262
283
|
function loadState(uploadId) {
|
|
263
|
-
const path = join(
|
|
284
|
+
const path = join(uploadStateDir(), `${uploadId}.json`);
|
|
264
285
|
if (!existsSync(path)) return null;
|
|
265
286
|
try { return JSON.parse(readFileSync(path, "utf8")); }
|
|
266
287
|
catch { return null; }
|
|
267
288
|
}
|
|
268
289
|
|
|
269
290
|
function saveState(state) {
|
|
270
|
-
|
|
271
|
-
|
|
291
|
+
const dir = uploadStateDir();
|
|
292
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
293
|
+
chmodSync(dir, 0o700);
|
|
294
|
+
const diskState = { ...state };
|
|
295
|
+
delete diskState.parts;
|
|
296
|
+
const path = join(dir, `${state.upload_id}.json`);
|
|
297
|
+
writeFileSync(path, JSON.stringify(diskState, null, 2), { mode: 0o600 });
|
|
298
|
+
chmodSync(path, 0o600);
|
|
272
299
|
}
|
|
273
300
|
|
|
274
301
|
function removeState(uploadId) {
|
|
275
|
-
const path = join(
|
|
302
|
+
const path = join(uploadStateDir(), `${uploadId}.json`);
|
|
276
303
|
if (existsSync(path)) unlinkSync(path);
|
|
277
304
|
}
|
|
278
305
|
|
|
279
|
-
function
|
|
280
|
-
|
|
281
|
-
|
|
306
|
+
function fileFingerprint(stat) {
|
|
307
|
+
return {
|
|
308
|
+
file_size: stat.size,
|
|
309
|
+
file_mtime_ms: stat.mtimeMs,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function stateMatchesFile(state, fingerprint) {
|
|
314
|
+
return state.file_size === fingerprint.file_size &&
|
|
315
|
+
typeof state.file_mtime_ms === "number" &&
|
|
316
|
+
state.file_mtime_ms === fingerprint.file_mtime_ms;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function findResumableStateForFile(projectId, localPath, key, fingerprint) {
|
|
320
|
+
const dir = uploadStateDir();
|
|
321
|
+
if (!existsSync(dir)) return null;
|
|
322
|
+
for (const f of readdirSync(dir)) {
|
|
282
323
|
if (!f.endsWith(".json")) continue;
|
|
283
324
|
try {
|
|
284
|
-
const s = JSON.parse(readFileSync(join(
|
|
285
|
-
if (s.project_id === projectId && s.local_path === localPath && s.key === key)
|
|
325
|
+
const s = JSON.parse(readFileSync(join(dir, f), "utf8"));
|
|
326
|
+
if (s.project_id === projectId && s.local_path === localPath && s.key === key) {
|
|
327
|
+
if (stateMatchesFile(s, fingerprint)) return s;
|
|
328
|
+
removeState(s.upload_id);
|
|
329
|
+
}
|
|
286
330
|
} catch { /* ignore */ }
|
|
287
331
|
}
|
|
288
332
|
return null;
|
|
@@ -295,16 +339,15 @@ function findResumableStateForFile(projectId, localPath, key) {
|
|
|
295
339
|
async function putOne(projectId, filePath, opts) {
|
|
296
340
|
const stat = statSync(filePath);
|
|
297
341
|
const size = stat.size;
|
|
342
|
+
const fingerprint = fileFingerprint(stat);
|
|
298
343
|
const destKey = computeDestKey(filePath, opts.key);
|
|
299
344
|
const absLocal = resolvePath(filePath);
|
|
300
345
|
|
|
301
|
-
|
|
302
|
-
const needSha = opts.immutable;
|
|
303
|
-
const sha256 = needSha ? await sha256File(filePath) : undefined;
|
|
346
|
+
const sha256 = await sha256File(filePath);
|
|
304
347
|
|
|
305
348
|
// Attempt to resume
|
|
306
349
|
let state = opts.resume
|
|
307
|
-
? findResumableStateForFile(projectId, absLocal, destKey)
|
|
350
|
+
? findResumableStateForFile(projectId, absLocal, destKey, fingerprint)
|
|
308
351
|
: null;
|
|
309
352
|
let initRes;
|
|
310
353
|
if (state) {
|
|
@@ -315,7 +358,7 @@ async function putOne(projectId, filePath, opts) {
|
|
|
315
358
|
initRes = {
|
|
316
359
|
upload_id: state.upload_id,
|
|
317
360
|
mode: poll.mode ?? state.mode,
|
|
318
|
-
parts: poll.parts ?? state.parts,
|
|
361
|
+
parts: poll.parts ?? state.parts ?? [],
|
|
319
362
|
part_count: poll.part_count ?? state.part_count,
|
|
320
363
|
part_size_bytes: poll.part_size_bytes ?? state.part_size_bytes,
|
|
321
364
|
};
|
|
@@ -345,6 +388,7 @@ async function putOne(projectId, filePath, opts) {
|
|
|
345
388
|
parts: initRes.parts,
|
|
346
389
|
parts_done: {},
|
|
347
390
|
sha256,
|
|
391
|
+
...fingerprint,
|
|
348
392
|
started_at: new Date().toISOString(),
|
|
349
393
|
};
|
|
350
394
|
if (opts.resume) saveState(state);
|
|
@@ -360,22 +404,18 @@ async function putOne(projectId, filePath, opts) {
|
|
|
360
404
|
etags[parseInt(pn, 10) - 1] = typeof pd === "string" ? { etag: pd, sha256: undefined } : pd;
|
|
361
405
|
}
|
|
362
406
|
|
|
363
|
-
// Presigned URLs are signed WITHOUT ChecksumAlgorithm (see gateway
|
|
364
|
-
// s3-presign.ts). The client-asserted sha256 declared at init is the
|
|
365
|
-
// integrity attestation — no x-amz-checksum-sha256 header on PUTs, and
|
|
366
|
-
// the gateway trusts the declared value at complete when S3 has none.
|
|
367
407
|
const todo = initRes.parts.filter((p) => !(state.parts_done || {})[String(p.part_number)]);
|
|
368
408
|
await withConcurrency(todo, opts.concurrency, async (part) => {
|
|
369
|
-
const { etag } = await putPart(filePath, part);
|
|
370
|
-
etags[part.part_number - 1] = { etag };
|
|
371
|
-
state.parts_done[String(part.part_number)] = { etag };
|
|
409
|
+
const { etag, sha256: partSha256 } = await putPart(filePath, part);
|
|
410
|
+
etags[part.part_number - 1] = { etag, sha256: partSha256 };
|
|
411
|
+
state.parts_done[String(part.part_number)] = { etag, sha256: partSha256 };
|
|
372
412
|
if (opts.resume) saveState(state);
|
|
373
|
-
log(opts, { event: "part", upload_id: state.upload_id, part_number: part.part_number, etag });
|
|
413
|
+
log(opts, { event: "part", upload_id: state.upload_id, part_number: part.part_number, etag, sha256: partSha256 });
|
|
374
414
|
});
|
|
375
415
|
|
|
376
416
|
// Complete
|
|
377
417
|
const body = initRes.mode === "multipart"
|
|
378
|
-
? { parts: etags.map((e, i) => ({ part_number: i + 1, etag: e.etag })) }
|
|
418
|
+
? { parts: etags.map((e, i) => ({ part_number: i + 1, etag: e.etag, sha256: e.sha256 })) }
|
|
379
419
|
: {};
|
|
380
420
|
const result = await getSdk().blobs.completeUploadSession(projectId, state.upload_id, body, {
|
|
381
421
|
contentType: opts.contentType ?? guessContentType(destKey),
|
|
@@ -399,37 +439,31 @@ async function putPart(filePath, part) {
|
|
|
399
439
|
const chunks = [];
|
|
400
440
|
for await (const c of stream) chunks.push(c);
|
|
401
441
|
const body = Buffer.concat(chunks);
|
|
442
|
+
const checksum = sha256BufferHexAndBase64(body);
|
|
402
443
|
|
|
403
|
-
const res = await fetch(part.url, {
|
|
444
|
+
const res = await fetch(part.url, {
|
|
445
|
+
method: "PUT",
|
|
446
|
+
headers: checksumHeadersForPresignedUrl(part.url, checksum.base64),
|
|
447
|
+
body,
|
|
448
|
+
});
|
|
404
449
|
if (!res.ok) {
|
|
405
450
|
const errBody = await res.text().catch(() => "");
|
|
406
451
|
throw new Error(`Part ${part.part_number} PUT failed: ${res.status} ${res.statusText}${errBody ? " — " + errBody.slice(0, 200) : ""}`);
|
|
407
452
|
}
|
|
408
453
|
const etag = res.headers.get("etag") ?? "";
|
|
409
|
-
return { etag };
|
|
454
|
+
return { etag, sha256: checksum.hex };
|
|
410
455
|
}
|
|
411
456
|
|
|
412
457
|
async function withConcurrency(items, limit, worker) {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
await
|
|
419
|
-
for (let i = running.length - 1; i >= 0; i--) {
|
|
420
|
-
if (isSettled(running[i])) running.splice(i, 1);
|
|
421
|
-
}
|
|
458
|
+
let index = 0;
|
|
459
|
+
const workerCount = Math.min(limit, items.length);
|
|
460
|
+
async function runWorker() {
|
|
461
|
+
while (index < items.length) {
|
|
462
|
+
const item = items[index++];
|
|
463
|
+
await worker(item);
|
|
422
464
|
}
|
|
423
465
|
}
|
|
424
|
-
await Promise.all(
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
function isSettled(p) {
|
|
428
|
-
const marker = {};
|
|
429
|
-
return Promise.race([p, marker]).then(
|
|
430
|
-
(v) => v !== marker,
|
|
431
|
-
() => true,
|
|
432
|
-
);
|
|
466
|
+
await Promise.all(Array.from({ length: workerCount }, runWorker));
|
|
433
467
|
}
|
|
434
468
|
|
|
435
469
|
async function put(projectId, argv) {
|
|
@@ -438,8 +472,8 @@ async function put(projectId, argv) {
|
|
|
438
472
|
const resolvedId = resolveProjectId(opts.project);
|
|
439
473
|
|
|
440
474
|
if (opts.positional.length === 0) die("At least one file path is required");
|
|
441
|
-
if (opts.
|
|
442
|
-
die("--key
|
|
475
|
+
if (opts.positional.length > 1 && opts.key && !opts.key.endsWith("/")) {
|
|
476
|
+
die("--key across multiple files requires a directory prefix (ending with /)");
|
|
443
477
|
}
|
|
444
478
|
|
|
445
479
|
const results = [];
|
|
@@ -464,6 +498,7 @@ async function get(projectId, argv) {
|
|
|
464
498
|
opts.project = opts.project || projectId;
|
|
465
499
|
const resolvedId = resolveProjectId(opts.project);
|
|
466
500
|
if (opts.positional.length === 0) die("Key required");
|
|
501
|
+
if (opts.positional.length > 1) die("blob get expects exactly one key");
|
|
467
502
|
if (!opts.output) die("--output <file> required");
|
|
468
503
|
const key = opts.positional[0];
|
|
469
504
|
|
|
@@ -510,6 +545,7 @@ async function rm(projectId, argv) {
|
|
|
510
545
|
opts.project = opts.project || projectId;
|
|
511
546
|
const resolvedId = resolveProjectId(opts.project);
|
|
512
547
|
if (opts.positional.length === 0) die("Key required");
|
|
548
|
+
if (opts.positional.length > 1) die("blob rm expects exactly one key");
|
|
513
549
|
const key = opts.positional[0];
|
|
514
550
|
|
|
515
551
|
try {
|
|
@@ -529,6 +565,7 @@ async function diagnose(projectId, argv) {
|
|
|
529
565
|
opts.project = opts.project || projectId;
|
|
530
566
|
const resolvedId = resolveProjectId(opts.project);
|
|
531
567
|
if (opts.positional.length === 0) die("URL required");
|
|
568
|
+
if (opts.positional.length > 1) die("blob diagnose expects exactly one URL");
|
|
532
569
|
const url = opts.positional[0];
|
|
533
570
|
|
|
534
571
|
try {
|
|
@@ -556,6 +593,7 @@ async function sign(projectId, argv) {
|
|
|
556
593
|
opts.project = opts.project || projectId;
|
|
557
594
|
const resolvedId = resolveProjectId(opts.project);
|
|
558
595
|
if (opts.positional.length === 0) die("Key required");
|
|
596
|
+
if (opts.positional.length > 1) die("blob sign expects exactly one key");
|
|
559
597
|
const key = opts.positional[0];
|
|
560
598
|
|
|
561
599
|
try {
|
package/lib/cdn.mjs
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { resolveProjectId } from "./config.mjs";
|
|
17
17
|
import { getSdk } from "./sdk.mjs";
|
|
18
18
|
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
19
|
+
import { assertKnownFlags, flagValue, normalizeArgv, parseIntegerFlag, positionalArgs } from "./argparse.mjs";
|
|
19
20
|
|
|
20
21
|
const HELP = `run402 cdn — CloudFront CDN diagnostics for public blob URLs
|
|
21
22
|
|
|
@@ -73,14 +74,19 @@ function die(msg, exit_code = 1) {
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
function parseArgs(args) {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
const normalized = normalizeArgv(args);
|
|
78
|
+
const valueFlags = ["--sha", "--timeout", "--project"];
|
|
79
|
+
assertKnownFlags(normalized, [...valueFlags, "--help", "-h"], valueFlags);
|
|
80
|
+
const opts = {
|
|
81
|
+
positional: positionalArgs(normalized, valueFlags),
|
|
82
|
+
sha: flagValue(normalized, "--sha"),
|
|
83
|
+
timeout: normalized.includes("--timeout")
|
|
84
|
+
? parseIntegerFlag("--timeout", flagValue(normalized, "--timeout"), { min: 1 })
|
|
85
|
+
: undefined,
|
|
86
|
+
project: flagValue(normalized, "--project"),
|
|
87
|
+
};
|
|
88
|
+
if (opts.positional.length > 1) {
|
|
89
|
+
die(`Unexpected argument for cdn wait-fresh: ${opts.positional[1]}`);
|
|
84
90
|
}
|
|
85
91
|
return opts;
|
|
86
92
|
}
|
|
@@ -92,6 +98,13 @@ async function waitFresh(projectId, argv) {
|
|
|
92
98
|
if (opts.positional.length === 0) die("URL required");
|
|
93
99
|
const url = opts.positional[0];
|
|
94
100
|
if (!opts.sha) die("--sha is required");
|
|
101
|
+
if (!/^[a-fA-F0-9]{64}$/.test(opts.sha)) {
|
|
102
|
+
fail({
|
|
103
|
+
code: "BAD_FLAG",
|
|
104
|
+
message: "--sha must be a 64-character hex SHA-256 digest",
|
|
105
|
+
details: { flag: "--sha", value: opts.sha },
|
|
106
|
+
});
|
|
107
|
+
}
|
|
95
108
|
|
|
96
109
|
const timeoutMs = (opts.timeout ?? 60) * 1000;
|
|
97
110
|
try {
|
package/lib/contracts.mjs
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { getSdk } from "./sdk.mjs";
|
|
2
2
|
import { reportSdkError, fail, parseFlagJson } from "./sdk-errors.mjs";
|
|
3
|
+
import {
|
|
4
|
+
assertAllowedValue,
|
|
5
|
+
assertKnownFlags,
|
|
6
|
+
flagValue,
|
|
7
|
+
normalizeArgv,
|
|
8
|
+
positionalArgs,
|
|
9
|
+
validateEvmAddress,
|
|
10
|
+
} from "./argparse.mjs";
|
|
3
11
|
|
|
4
12
|
const HELP = `run402 contracts — KMS-backed Ethereum wallets for smart-contract calls
|
|
5
13
|
|
|
@@ -126,12 +134,6 @@ Examples:
|
|
|
126
134
|
`,
|
|
127
135
|
};
|
|
128
136
|
|
|
129
|
-
function parseFlag(args, flag) {
|
|
130
|
-
for (let i = 0; i < args.length; i++) {
|
|
131
|
-
if (args[i] === flag && args[i + 1]) return args[i + 1];
|
|
132
|
-
}
|
|
133
|
-
return null;
|
|
134
|
-
}
|
|
135
137
|
function hasFlag(args, flag) {
|
|
136
138
|
return args.includes(flag);
|
|
137
139
|
}
|
|
@@ -146,21 +148,30 @@ function validateWeiFlag(flag, value) {
|
|
|
146
148
|
}
|
|
147
149
|
|
|
148
150
|
async function provisionWallet(projectId, args) {
|
|
149
|
-
const
|
|
151
|
+
const parsedArgs = normalizeArgv(args);
|
|
152
|
+
const valueFlags = ["--chain", "--recovery"];
|
|
153
|
+
assertKnownFlags(parsedArgs, [...valueFlags, "--yes", "--help", "-h"], valueFlags);
|
|
154
|
+
const extra = positionalArgs(parsedArgs, valueFlags);
|
|
155
|
+
if (extra.length > 0) {
|
|
156
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for contracts provision-wallet: ${extra[0]}` });
|
|
157
|
+
}
|
|
158
|
+
const chain = flagValue(parsedArgs, "--chain");
|
|
150
159
|
if (!chain) {
|
|
151
160
|
fail({
|
|
152
161
|
code: "BAD_USAGE",
|
|
153
162
|
message: "Missing --chain (base-mainnet or base-sepolia)",
|
|
154
163
|
});
|
|
155
164
|
}
|
|
156
|
-
|
|
165
|
+
assertAllowedValue(chain, ["base-mainnet", "base-sepolia"], "--chain");
|
|
166
|
+
const recovery = flagValue(parsedArgs, "--recovery");
|
|
167
|
+
if (recovery) validateEvmAddress(recovery, "--recovery");
|
|
157
168
|
// Soft default of one wallet — confirm if project already has one.
|
|
158
169
|
let activeWallets = null;
|
|
159
170
|
try {
|
|
160
171
|
const list = await getSdk().contracts.listWallets(projectId);
|
|
161
172
|
activeWallets = (list.wallets || []).filter((w) => w.status === "active").length;
|
|
162
173
|
} catch { /* best-effort */ }
|
|
163
|
-
if (activeWallets !== null && activeWallets >= 1 && !hasFlag(
|
|
174
|
+
if (activeWallets !== null && activeWallets >= 1 && !hasFlag(parsedArgs, "--yes")) {
|
|
164
175
|
fail({
|
|
165
176
|
code: "CONFIRMATION_REQUIRED",
|
|
166
177
|
message: `This project already has ${activeWallets} active wallet(s). Adding another costs $0.04/day each ($1.20/month). Re-run with --yes to confirm.`,
|
|
@@ -179,7 +190,13 @@ async function provisionWallet(projectId, args) {
|
|
|
179
190
|
}
|
|
180
191
|
}
|
|
181
192
|
|
|
182
|
-
async function getWallet(projectId, walletId) {
|
|
193
|
+
async function getWallet(projectId, walletId, args = []) {
|
|
194
|
+
const parsedArgs = normalizeArgv(args);
|
|
195
|
+
assertKnownFlags(parsedArgs, ["--help", "-h"]);
|
|
196
|
+
const extra = positionalArgs(parsedArgs);
|
|
197
|
+
if (extra.length > 0) {
|
|
198
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for contracts get-wallet: ${extra[0]}` });
|
|
199
|
+
}
|
|
183
200
|
try {
|
|
184
201
|
const data = await getSdk().contracts.getWallet(projectId, walletId);
|
|
185
202
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -188,7 +205,13 @@ async function getWallet(projectId, walletId) {
|
|
|
188
205
|
}
|
|
189
206
|
}
|
|
190
207
|
|
|
191
|
-
async function listWallets(projectId) {
|
|
208
|
+
async function listWallets(projectId, args = []) {
|
|
209
|
+
const parsedArgs = normalizeArgv(args);
|
|
210
|
+
assertKnownFlags(parsedArgs, ["--help", "-h"]);
|
|
211
|
+
const extra = positionalArgs(parsedArgs);
|
|
212
|
+
if (extra.length > 0) {
|
|
213
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for contracts list-wallets: ${extra[0]}` });
|
|
214
|
+
}
|
|
192
215
|
try {
|
|
193
216
|
const data = await getSdk().contracts.listWallets(projectId);
|
|
194
217
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -198,14 +221,25 @@ async function listWallets(projectId) {
|
|
|
198
221
|
}
|
|
199
222
|
|
|
200
223
|
async function setRecovery(projectId, walletId, args) {
|
|
201
|
-
const
|
|
202
|
-
const
|
|
224
|
+
const parsedArgs = normalizeArgv(args);
|
|
225
|
+
const valueFlags = ["--address"];
|
|
226
|
+
assertKnownFlags(parsedArgs, [...valueFlags, "--clear", "--help", "-h"], valueFlags);
|
|
227
|
+
const extra = positionalArgs(parsedArgs, valueFlags);
|
|
228
|
+
if (extra.length > 0) {
|
|
229
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for contracts set-recovery: ${extra[0]}` });
|
|
230
|
+
}
|
|
231
|
+
const clear = hasFlag(parsedArgs, "--clear");
|
|
232
|
+
const address = flagValue(parsedArgs, "--address");
|
|
233
|
+
if (clear && address) {
|
|
234
|
+
fail({ code: "BAD_USAGE", message: "Provide either --address or --clear, not both." });
|
|
235
|
+
}
|
|
203
236
|
if (!clear && !address) {
|
|
204
237
|
fail({
|
|
205
238
|
code: "BAD_USAGE",
|
|
206
239
|
message: "Provide --address 0x... or --clear",
|
|
207
240
|
});
|
|
208
241
|
}
|
|
242
|
+
if (address) validateEvmAddress(address, "--address");
|
|
209
243
|
try {
|
|
210
244
|
await getSdk().contracts.setRecovery(projectId, walletId, clear ? null : address);
|
|
211
245
|
console.log(JSON.stringify({ status: "ok", wallet_id: walletId, recovery_address: clear ? null : address }));
|
|
@@ -215,7 +249,14 @@ async function setRecovery(projectId, walletId, args) {
|
|
|
215
249
|
}
|
|
216
250
|
|
|
217
251
|
async function setAlert(projectId, walletId, args) {
|
|
218
|
-
const
|
|
252
|
+
const parsedArgs = normalizeArgv(args);
|
|
253
|
+
const valueFlags = ["--threshold-wei"];
|
|
254
|
+
assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
|
|
255
|
+
const extra = positionalArgs(parsedArgs, valueFlags);
|
|
256
|
+
if (extra.length > 0) {
|
|
257
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for contracts set-alert: ${extra[0]}` });
|
|
258
|
+
}
|
|
259
|
+
const threshold = flagValue(parsedArgs, "--threshold-wei");
|
|
219
260
|
if (!threshold) {
|
|
220
261
|
fail({ code: "BAD_USAGE", message: "Missing --threshold-wei <n>" });
|
|
221
262
|
}
|
|
@@ -229,13 +270,20 @@ async function setAlert(projectId, walletId, args) {
|
|
|
229
270
|
}
|
|
230
271
|
|
|
231
272
|
async function call(projectId, walletId, args) {
|
|
232
|
-
const
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
273
|
+
const parsedArgs = normalizeArgv(args);
|
|
274
|
+
const valueFlags = ["--to", "--abi", "--fn", "--args", "--value-wei", "--chain", "--idempotency-key"];
|
|
275
|
+
assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
|
|
276
|
+
const extra = positionalArgs(parsedArgs, valueFlags);
|
|
277
|
+
if (extra.length > 0) {
|
|
278
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for contracts call: ${extra[0]}` });
|
|
279
|
+
}
|
|
280
|
+
const to = flagValue(parsedArgs, "--to");
|
|
281
|
+
const abi = flagValue(parsedArgs, "--abi");
|
|
282
|
+
const fn = flagValue(parsedArgs, "--fn");
|
|
283
|
+
const argsJson = flagValue(parsedArgs, "--args");
|
|
284
|
+
const value = flagValue(parsedArgs, "--value-wei");
|
|
285
|
+
const chain = flagValue(parsedArgs, "--chain") || "base-mainnet";
|
|
286
|
+
const idempotency = flagValue(parsedArgs, "--idempotency-key");
|
|
239
287
|
if (!to || !abi || !fn || !argsJson) {
|
|
240
288
|
fail({
|
|
241
289
|
code: "BAD_USAGE",
|
|
@@ -243,9 +291,11 @@ async function call(projectId, walletId, args) {
|
|
|
243
291
|
hint: "Cost: chain gas + $0.000005 KMS sign fee.",
|
|
244
292
|
});
|
|
245
293
|
}
|
|
294
|
+
assertAllowedValue(chain, ["base-mainnet", "base-sepolia"], "--chain");
|
|
246
295
|
if (value !== null) validateWeiFlag("--value-wei", value);
|
|
247
296
|
const abiFragment = parseFlagJson("--abi", abi);
|
|
248
297
|
const callArgs = parseFlagJson("--args", argsJson);
|
|
298
|
+
validateEvmAddress(to, "--to");
|
|
249
299
|
try {
|
|
250
300
|
const data = await getSdk().contracts.call(projectId, {
|
|
251
301
|
walletId,
|
|
@@ -264,19 +314,28 @@ async function call(projectId, walletId, args) {
|
|
|
264
314
|
}
|
|
265
315
|
|
|
266
316
|
async function read(args) {
|
|
267
|
-
const
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
|
|
317
|
+
const parsedArgs = normalizeArgv(args);
|
|
318
|
+
const valueFlags = ["--chain", "--to", "--abi", "--fn", "--args"];
|
|
319
|
+
assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
|
|
320
|
+
const extra = positionalArgs(parsedArgs, valueFlags);
|
|
321
|
+
if (extra.length > 0) {
|
|
322
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for contracts read: ${extra[0]}` });
|
|
323
|
+
}
|
|
324
|
+
const chain = flagValue(parsedArgs, "--chain");
|
|
325
|
+
const to = flagValue(parsedArgs, "--to");
|
|
326
|
+
const abi = flagValue(parsedArgs, "--abi");
|
|
327
|
+
const fn = flagValue(parsedArgs, "--fn");
|
|
328
|
+
const argsJson = flagValue(parsedArgs, "--args");
|
|
272
329
|
if (!chain || !to || !abi || !fn || !argsJson) {
|
|
273
330
|
fail({
|
|
274
331
|
code: "BAD_USAGE",
|
|
275
332
|
message: "Required flags: --chain, --to, --abi, --fn, --args",
|
|
276
333
|
});
|
|
277
334
|
}
|
|
335
|
+
assertAllowedValue(chain, ["base-mainnet", "base-sepolia"], "--chain");
|
|
278
336
|
const abiFragment = parseFlagJson("--abi", abi);
|
|
279
337
|
const callArgs = parseFlagJson("--args", argsJson);
|
|
338
|
+
validateEvmAddress(to, "--to");
|
|
280
339
|
try {
|
|
281
340
|
const data = await getSdk().contracts.read({
|
|
282
341
|
chain,
|
|
@@ -291,7 +350,13 @@ async function read(args) {
|
|
|
291
350
|
}
|
|
292
351
|
}
|
|
293
352
|
|
|
294
|
-
async function status(projectId, callId) {
|
|
353
|
+
async function status(projectId, callId, args = []) {
|
|
354
|
+
const parsedArgs = normalizeArgv(args);
|
|
355
|
+
assertKnownFlags(parsedArgs, ["--help", "-h"]);
|
|
356
|
+
const extra = positionalArgs(parsedArgs);
|
|
357
|
+
if (extra.length > 0) {
|
|
358
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for contracts status: ${extra[0]}` });
|
|
359
|
+
}
|
|
295
360
|
try {
|
|
296
361
|
const data = await getSdk().contracts.callStatus(projectId, callId);
|
|
297
362
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -301,14 +366,22 @@ async function status(projectId, callId) {
|
|
|
301
366
|
}
|
|
302
367
|
|
|
303
368
|
async function drain(projectId, walletId, args) {
|
|
304
|
-
const
|
|
305
|
-
|
|
369
|
+
const parsedArgs = normalizeArgv(args);
|
|
370
|
+
const valueFlags = ["--to"];
|
|
371
|
+
assertKnownFlags(parsedArgs, [...valueFlags, "--confirm", "--help", "-h"], valueFlags);
|
|
372
|
+
const extra = positionalArgs(parsedArgs, valueFlags);
|
|
373
|
+
if (extra.length > 0) {
|
|
374
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for contracts drain: ${extra[0]}` });
|
|
375
|
+
}
|
|
376
|
+
const to = flagValue(parsedArgs, "--to");
|
|
377
|
+
if (!to || !hasFlag(parsedArgs, "--confirm")) {
|
|
306
378
|
fail({
|
|
307
379
|
code: "BAD_USAGE",
|
|
308
380
|
message: "Required: --to 0x... and --confirm.",
|
|
309
381
|
hint: "Cost: chain gas + $0.000005 KMS sign fee.",
|
|
310
382
|
});
|
|
311
383
|
}
|
|
384
|
+
validateEvmAddress(to, "--to");
|
|
312
385
|
try {
|
|
313
386
|
const data = await getSdk().contracts.drain(projectId, walletId, to);
|
|
314
387
|
console.log(JSON.stringify(data, null, 2));
|
|
@@ -318,7 +391,13 @@ async function drain(projectId, walletId, args) {
|
|
|
318
391
|
}
|
|
319
392
|
|
|
320
393
|
async function deleteWallet(projectId, walletId, args) {
|
|
321
|
-
|
|
394
|
+
const parsedArgs = normalizeArgv(args);
|
|
395
|
+
assertKnownFlags(parsedArgs, ["--confirm", "--help", "-h"]);
|
|
396
|
+
const extra = positionalArgs(parsedArgs);
|
|
397
|
+
if (extra.length > 0) {
|
|
398
|
+
fail({ code: "BAD_USAGE", message: `Unexpected argument for contracts delete: ${extra[0]}` });
|
|
399
|
+
}
|
|
400
|
+
if (!hasFlag(parsedArgs, "--confirm")) {
|
|
322
401
|
fail({ code: "BAD_USAGE", message: "Required: --confirm" });
|
|
323
402
|
}
|
|
324
403
|
try {
|
|
@@ -334,13 +413,13 @@ export async function run(sub, args) {
|
|
|
334
413
|
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) { console.log(SUB_HELP[sub] || HELP); process.exit(0); }
|
|
335
414
|
switch (sub) {
|
|
336
415
|
case "provision-wallet": await provisionWallet(args[0], args.slice(1)); break;
|
|
337
|
-
case "get-wallet": await getWallet(args[0], args[1]); break;
|
|
338
|
-
case "list-wallets": await listWallets(args[0]); break;
|
|
416
|
+
case "get-wallet": await getWallet(args[0], args[1], args.slice(2)); break;
|
|
417
|
+
case "list-wallets": await listWallets(args[0], args.slice(1)); break;
|
|
339
418
|
case "set-recovery": await setRecovery(args[0], args[1], args.slice(2)); break;
|
|
340
419
|
case "set-alert": await setAlert(args[0], args[1], args.slice(2)); break;
|
|
341
420
|
case "call": await call(args[0], args[1], args.slice(2)); break;
|
|
342
421
|
case "read": await read(args); break;
|
|
343
|
-
case "status": await status(args[0], args[1]); break;
|
|
422
|
+
case "status": await status(args[0], args[1], args.slice(2)); break;
|
|
344
423
|
case "drain": await drain(args[0], args[1], args.slice(2)); break;
|
|
345
424
|
case "delete": await deleteWallet(args[0], args[1], args.slice(2)); break;
|
|
346
425
|
default:
|