run402 2.48.0 → 3.0.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 CHANGED
@@ -175,11 +175,11 @@ run402 image generate "a serif logo" --aspect square --output logo.png
175
175
 
176
176
  $0.03 per image via x402.
177
177
 
178
- ### On-chain (KMS contract wallets)
178
+ ### On-chain (KMS signers)
179
179
 
180
180
  ```bash
181
- run402 contracts provision-wallet --chain base-mainnet
182
- run402 contracts call --wallet <id> --to 0x… --abi @abi.json --fn transfer --args '["0x…","1000000"]'
181
+ run402 contracts provision-signer --chain base-mainnet
182
+ run402 contracts call <project_id> <signer_id> --to 0x… --abi @abi.json --fn transfer --args '["0x…","1000000"]'
183
183
  ```
184
184
 
185
185
  Private keys never leave AWS KMS. $0.04/day rental + $0.000005/call.
package/cli.mjs CHANGED
@@ -48,7 +48,7 @@ Commands:
48
48
  auth Manage project user authentication (magic link, passwords, settings)
49
49
  sender-domain Manage custom email sender domain (register, status, remove)
50
50
  billing Email organizations, Stripe tier checkout, email packs
51
- contracts KMS contract wallets ($0.04/day rental + $0.000005/sign)
51
+ contracts KMS signers ($0.04/day rental + $0.000005/sign)
52
52
  agent Manage agent identity (contact info)
53
53
  operator Operator (human/email) session — login, then overview across your wallets
54
54
  service Run402 service health and availability (status, health)
package/lib/contracts.mjs CHANGED
@@ -9,49 +9,49 @@ import {
9
9
  validateEvmAddress,
10
10
  } from "./argparse.mjs";
11
11
 
12
- const HELP = `run402 contracts — KMS-backed Ethereum wallets for smart-contract calls
12
+ const HELP = `run402 contracts — KMS-backed Ethereum signers for smart-contract calls
13
13
 
14
- Pricing: $0.04/day per wallet ($1.20/month) plus $0.000005 per contract call.
15
- Wallet creation requires $1.20 in cash credit (30 days of rent).
14
+ Pricing: $0.04/day per signer ($1.20/month) plus $0.000005 per contract call.
15
+ Signer creation requires $1.20 in cash credit (30 days of rent).
16
16
  Non-custodial: see https://run402.com/humans/terms.html#non-custodial-kms-wallets
17
17
 
18
18
  Usage:
19
19
  run402 contracts <subcommand> [args...]
20
20
 
21
21
  Subcommands:
22
- provision-wallet <project_id> --chain <base-mainnet|base-sepolia> [--recovery 0x...]
23
- Provision a KMS wallet ($0.04/day, requires $1.20 prepay).
24
- get-wallet <project_id> <wallet_id>
25
- Get wallet metadata + live native balance.
26
- list-wallets <project_id>
27
- List all KMS wallets for the project (includes deleted).
28
- set-recovery <project_id> <wallet_id> [--address 0x... | --clear]
22
+ provision-signer <project_id> --chain <base-mainnet|base-sepolia> [--recovery 0x...]
23
+ Provision a KMS signer ($0.04/day, requires $1.20 prepay).
24
+ get-signer <project_id> <signer_id>
25
+ Get signer metadata + live native balance.
26
+ list-signers <project_id>
27
+ List all KMS signers for the project (includes deleted).
28
+ set-recovery <project_id> <signer_id> [--address 0x... | --clear]
29
29
  Set/clear the optional recovery address.
30
- set-alert <project_id> <wallet_id> --threshold-wei <n>
30
+ set-alert <project_id> <signer_id> --threshold-wei <n>
31
31
  Set the low-balance alert threshold (in wei).
32
- call <project_id> <wallet_id> --to 0x... --abi <json> --fn <name> --args <json> [--value-wei <n>] [--idempotency-key <k>]
32
+ call <project_id> <signer_id> --to 0x... --abi <json> --fn <name> --args <json> [--value-wei <n>] [--idempotency-key <k>]
33
33
  Submit a contract write call (chain gas + $0.000005 KMS sign fee).
34
- deploy <project_id> <wallet_id> --bytecode 0x... [--chain <base-mainnet|base-sepolia>] [--value-wei <n>] [--idempotency-key <k>]
34
+ deploy <project_id> <signer_id> --bytecode 0x... [--chain <base-mainnet|base-sepolia>] [--value-wei <n>] [--idempotency-key <k>]
35
35
  Deploy a contract (chain gas + $0.000005 KMS sign fee). Returns deterministic CREATE address synchronously.
36
36
  read --chain <chain> --to 0x... --abi <json> --fn <name> --args <json>
37
37
  Read-only contract call (free).
38
38
  status <project_id> <call_id>
39
39
  Get call status, gas used, gas cost USD-micros, receipt.
40
- drain <project_id> <wallet_id> --to 0x... --confirm
41
- Drain native balance to a destination address. Works on suspended wallets.
42
- delete <project_id> <wallet_id> --confirm
40
+ drain <project_id> <signer_id> --to 0x... --confirm
41
+ Drain native balance to a destination address. Works on suspended signers.
42
+ delete <project_id> <signer_id> --confirm
43
43
  Schedule the KMS key for deletion (refused if balance >= dust).
44
44
 
45
45
  Examples:
46
- run402 contracts provision-wallet proj_abc --chain base-mainnet
46
+ run402 contracts provision-signer proj_abc --chain base-mainnet
47
47
  run402 contracts call proj_abc cwlt_xyz --to 0x1234... --abi '[{"type":"function","name":"ping","inputs":[],"outputs":[]}]' --fn ping --args '[]'
48
48
  `;
49
49
 
50
50
  const SUB_HELP = {
51
- "provision-wallet": `run402 contracts provision-wallet — Provision a KMS-backed wallet
51
+ "provision-signer": `run402 contracts provision-signer — Provision a KMS-backed signer
52
52
 
53
53
  Usage:
54
- run402 contracts provision-wallet <project_id> --chain <chain> [options]
54
+ run402 contracts provision-signer <project_id> --chain <chain> [options]
55
55
 
56
56
  Arguments:
57
57
  <project_id> Target project ID
@@ -59,39 +59,39 @@ Arguments:
59
59
  Options:
60
60
  --chain <chain> Required: base-mainnet or base-sepolia
61
61
  --recovery 0x... Optional recovery address (can be set later)
62
- --yes Skip confirmation when project already has a wallet
62
+ --yes Skip confirmation when project already has a signer
63
63
  `,
64
- "set-recovery": `run402 contracts set-recovery — Set or clear the wallet recovery address
64
+ "set-recovery": `run402 contracts set-recovery — Set or clear the signer recovery address
65
65
 
66
66
  Usage:
67
- run402 contracts set-recovery <project_id> <wallet_id> [options]
67
+ run402 contracts set-recovery <project_id> <signer_id> [options]
68
68
  `,
69
69
  "set-alert": `run402 contracts set-alert — Set the low-balance alert threshold
70
70
 
71
71
  Usage:
72
- run402 contracts set-alert <project_id> <wallet_id> --threshold-wei <n>
72
+ run402 contracts set-alert <project_id> <signer_id> --threshold-wei <n>
73
73
  `,
74
74
  call: `run402 contracts call — Submit a contract write call
75
75
 
76
76
  Usage:
77
- run402 contracts call <project_id> <wallet_id> --to 0x... --abi <json>
77
+ run402 contracts call <project_id> <signer_id> --to 0x... --abi <json>
78
78
  --fn <name> --args <json> [options]
79
79
  `,
80
- deploy: `run402 contracts deploy — Deploy a smart contract from a KMS wallet
80
+ deploy: `run402 contracts deploy — Deploy a smart contract from a KMS signer
81
81
 
82
82
  KMS-signs a contract-creation transaction (to: null + data: bytecode) and broadcasts.
83
- Returns the deterministic CREATE address synchronously — known from (wallet, nonce)
83
+ Returns the deterministic CREATE address synchronously — known from (signer, nonce)
84
84
  before the tx confirms. Cost: chain gas at-cost + $0.000005 KMS sign fee.
85
85
 
86
86
  Usage:
87
- run402 contracts deploy <project_id> <wallet_id> --bytecode 0x... [options]
87
+ run402 contracts deploy <project_id> <signer_id> --bytecode 0x... [options]
88
88
 
89
89
  Options:
90
90
  --bytecode 0x... Full creation calldata as 0x-prefixed hex (creation
91
91
  bytecode + ABI-encoded constructor args, concatenated
92
92
  client-side via viem/ethers). Required. ≤ 128 KB.
93
93
  --chain <chain> base-mainnet (default) or base-sepolia. Must match
94
- the wallet's chain.
94
+ the signer's chain.
95
95
  --value-wei <n> Optional native-token value in wei to attach.
96
96
  --idempotency-key <k> Optional. Same key + same bytecode returns the
97
97
  existing call without re-broadcasting.
@@ -108,38 +108,38 @@ Usage:
108
108
  drain: `run402 contracts drain — Drain native balance to a destination address
109
109
 
110
110
  Usage:
111
- run402 contracts drain <project_id> <wallet_id> --to 0x... --confirm
111
+ run402 contracts drain <project_id> <signer_id> --to 0x... --confirm
112
112
  `,
113
113
  delete: `run402 contracts delete — Schedule the KMS key for deletion
114
114
 
115
115
  Usage:
116
- run402 contracts delete <project_id> <wallet_id> --confirm
116
+ run402 contracts delete <project_id> <signer_id> --confirm
117
117
  `,
118
- "get-wallet": `run402 contracts get-wallet — Get wallet metadata + live balance
118
+ "get-signer": `run402 contracts get-signer — Get signer metadata + live balance
119
119
 
120
120
  Usage:
121
- run402 contracts get-wallet <project_id> <wallet_id>
121
+ run402 contracts get-signer <project_id> <signer_id>
122
122
 
123
123
  Arguments:
124
- <project_id> Project ID that owns the wallet
125
- <wallet_id> Wallet ID (e.g. cwlt_abc123)
124
+ <project_id> Project ID that owns the signer
125
+ <signer_id> Signer ID (e.g. cwlt_abc123)
126
126
 
127
127
  Examples:
128
- run402 contracts get-wallet prj_abc123 cwlt_abc123
128
+ run402 contracts get-signer prj_abc123 cwlt_abc123
129
129
  `,
130
- "list-wallets": `run402 contracts list-wallets — List all KMS wallets for a project
130
+ "list-signers": `run402 contracts list-signers — List all KMS signers for a project
131
131
 
132
132
  Usage:
133
- run402 contracts list-wallets <project_id>
133
+ run402 contracts list-signers <project_id>
134
134
 
135
135
  Arguments:
136
- <project_id> Project ID to list wallets for
136
+ <project_id> Project ID to list signers for
137
137
 
138
138
  Notes:
139
- - Includes deleted wallets
139
+ - Includes deleted signers
140
140
 
141
141
  Examples:
142
- run402 contracts list-wallets prj_abc123
142
+ run402 contracts list-signers prj_abc123
143
143
  `,
144
144
  status: `run402 contracts status — Get a contract call's status and receipt
145
145
 
@@ -171,13 +171,13 @@ function validateWeiFlag(flag, value) {
171
171
  }
172
172
  }
173
173
 
174
- async function provisionWallet(projectId, args) {
174
+ async function provisionSigner(projectId, args) {
175
175
  const parsedArgs = normalizeArgv(args);
176
176
  const valueFlags = ["--chain", "--recovery"];
177
177
  assertKnownFlags(parsedArgs, [...valueFlags, "--yes", "--help", "-h"], valueFlags);
178
178
  const extra = positionalArgs(parsedArgs, valueFlags);
179
179
  if (extra.length > 0) {
180
- fail({ code: "BAD_USAGE", message: `Unexpected argument for contracts provision-wallet: ${extra[0]}` });
180
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for contracts provision-signer: ${extra[0]}` });
181
181
  }
182
182
  const chain = flagValue(parsedArgs, "--chain");
183
183
  if (!chain) {
@@ -189,22 +189,22 @@ async function provisionWallet(projectId, args) {
189
189
  assertAllowedValue(chain, ["base-mainnet", "base-sepolia"], "--chain");
190
190
  const recovery = flagValue(parsedArgs, "--recovery");
191
191
  if (recovery) validateEvmAddress(recovery, "--recovery");
192
- // Soft default of one wallet — confirm if project already has one.
193
- let activeWallets = null;
192
+ // Soft default of one signer — confirm if project already has one.
193
+ let activeSigners = null;
194
194
  try {
195
- const list = await getSdk().contracts.listWallets(projectId);
196
- activeWallets = (list.wallets || []).filter((w) => w.status === "active").length;
195
+ const list = await getSdk().contracts.listSigners(projectId);
196
+ activeSigners = (list.signers || []).filter((s) => s.status === "active").length;
197
197
  } catch { /* best-effort */ }
198
- if (activeWallets !== null && activeWallets >= 1 && !hasFlag(parsedArgs, "--yes")) {
198
+ if (activeSigners !== null && activeSigners >= 1 && !hasFlag(parsedArgs, "--yes")) {
199
199
  fail({
200
200
  code: "CONFIRMATION_REQUIRED",
201
- 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.`,
202
- details: { active_wallets: activeWallets },
201
+ message: `This project already has ${activeSigners} active signer(s). Adding another costs $0.04/day each ($1.20/month). Re-run with --yes to confirm.`,
202
+ details: { active_signers: activeSigners },
203
203
  });
204
204
  }
205
205
 
206
206
  try {
207
- const data = await getSdk().contracts.provisionWallet(projectId, {
207
+ const data = await getSdk().contracts.provisionSigner(projectId, {
208
208
  chain,
209
209
  recoveryAddress: recovery ?? undefined,
210
210
  });
@@ -214,37 +214,37 @@ async function provisionWallet(projectId, args) {
214
214
  }
215
215
  }
216
216
 
217
- async function getWallet(projectId, walletId, args = []) {
217
+ async function getSigner(projectId, signerId, args = []) {
218
218
  const parsedArgs = normalizeArgv(args);
219
219
  assertKnownFlags(parsedArgs, ["--help", "-h"]);
220
220
  const extra = positionalArgs(parsedArgs);
221
221
  if (extra.length > 0) {
222
- fail({ code: "BAD_USAGE", message: `Unexpected argument for contracts get-wallet: ${extra[0]}` });
222
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for contracts get-signer: ${extra[0]}` });
223
223
  }
224
224
  try {
225
- const data = await getSdk().contracts.getWallet(projectId, walletId);
225
+ const data = await getSdk().contracts.getSigner(projectId, signerId);
226
226
  console.log(JSON.stringify(data, null, 2));
227
227
  } catch (err) {
228
228
  reportSdkError(err);
229
229
  }
230
230
  }
231
231
 
232
- async function listWallets(projectId, args = []) {
232
+ async function listSigners(projectId, args = []) {
233
233
  const parsedArgs = normalizeArgv(args);
234
234
  assertKnownFlags(parsedArgs, ["--help", "-h"]);
235
235
  const extra = positionalArgs(parsedArgs);
236
236
  if (extra.length > 0) {
237
- fail({ code: "BAD_USAGE", message: `Unexpected argument for contracts list-wallets: ${extra[0]}` });
237
+ fail({ code: "BAD_USAGE", message: `Unexpected argument for contracts list-signers: ${extra[0]}` });
238
238
  }
239
239
  try {
240
- const data = await getSdk().contracts.listWallets(projectId);
240
+ const data = await getSdk().contracts.listSigners(projectId);
241
241
  console.log(JSON.stringify(data, null, 2));
242
242
  } catch (err) {
243
243
  reportSdkError(err);
244
244
  }
245
245
  }
246
246
 
247
- async function setRecovery(projectId, walletId, args) {
247
+ async function setRecovery(projectId, signerId, args) {
248
248
  const parsedArgs = normalizeArgv(args);
249
249
  const valueFlags = ["--address"];
250
250
  assertKnownFlags(parsedArgs, [...valueFlags, "--clear", "--help", "-h"], valueFlags);
@@ -265,14 +265,14 @@ async function setRecovery(projectId, walletId, args) {
265
265
  }
266
266
  if (address) validateEvmAddress(address, "--address");
267
267
  try {
268
- await getSdk().contracts.setRecovery(projectId, walletId, clear ? null : address);
269
- console.log(JSON.stringify({ wallet_id: walletId, recovery_address: clear ? null : address, updated: true }));
268
+ await getSdk().contracts.setRecovery(projectId, signerId, clear ? null : address);
269
+ console.log(JSON.stringify({ signer_id: signerId, recovery_address: clear ? null : address, updated: true }));
270
270
  } catch (err) {
271
271
  reportSdkError(err);
272
272
  }
273
273
  }
274
274
 
275
- async function setAlert(projectId, walletId, args) {
275
+ async function setAlert(projectId, signerId, args) {
276
276
  const parsedArgs = normalizeArgv(args);
277
277
  const valueFlags = ["--threshold-wei"];
278
278
  assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
@@ -286,14 +286,14 @@ async function setAlert(projectId, walletId, args) {
286
286
  }
287
287
  validateWeiFlag("--threshold-wei", threshold);
288
288
  try {
289
- await getSdk().contracts.setLowBalanceAlert(projectId, walletId, threshold);
290
- console.log(JSON.stringify({ wallet_id: walletId, threshold_wei: threshold, updated: true }));
289
+ await getSdk().contracts.setLowBalanceAlert(projectId, signerId, threshold);
290
+ console.log(JSON.stringify({ signer_id: signerId, threshold_wei: threshold, updated: true }));
291
291
  } catch (err) {
292
292
  reportSdkError(err);
293
293
  }
294
294
  }
295
295
 
296
- async function call(projectId, walletId, args) {
296
+ async function call(projectId, signerId, args) {
297
297
  const parsedArgs = normalizeArgv(args);
298
298
  const valueFlags = ["--to", "--abi", "--fn", "--args", "--value-wei", "--chain", "--idempotency-key"];
299
299
  assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
@@ -322,7 +322,7 @@ async function call(projectId, walletId, args) {
322
322
  validateEvmAddress(to, "--to");
323
323
  try {
324
324
  const data = await getSdk().contracts.call(projectId, {
325
- walletId,
325
+ signerId,
326
326
  chain,
327
327
  contractAddress: to,
328
328
  abiFragment,
@@ -337,7 +337,7 @@ async function call(projectId, walletId, args) {
337
337
  }
338
338
  }
339
339
 
340
- async function deploy(projectId, walletId, args) {
340
+ async function deploy(projectId, signerId, args) {
341
341
  const parsedArgs = normalizeArgv(args);
342
342
  const valueFlags = ["--bytecode", "--value-wei", "--chain", "--idempotency-key"];
343
343
  assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
@@ -360,7 +360,7 @@ async function deploy(projectId, walletId, args) {
360
360
  if (value !== null) validateWeiFlag("--value-wei", value);
361
361
  try {
362
362
  const data = await getSdk().contracts.deploy(projectId, {
363
- walletId,
363
+ signerId,
364
364
  chain,
365
365
  bytecode,
366
366
  value: value ?? undefined,
@@ -424,7 +424,7 @@ async function status(projectId, callId, args = []) {
424
424
  }
425
425
  }
426
426
 
427
- async function drain(projectId, walletId, args) {
427
+ async function drain(projectId, signerId, args) {
428
428
  const parsedArgs = normalizeArgv(args);
429
429
  const valueFlags = ["--to"];
430
430
  assertKnownFlags(parsedArgs, [...valueFlags, "--confirm", "--help", "-h"], valueFlags);
@@ -442,14 +442,14 @@ async function drain(projectId, walletId, args) {
442
442
  }
443
443
  validateEvmAddress(to, "--to");
444
444
  try {
445
- const data = await getSdk().contracts.drain(projectId, walletId, to);
445
+ const data = await getSdk().contracts.drain(projectId, signerId, to);
446
446
  console.log(JSON.stringify(data, null, 2));
447
447
  } catch (err) {
448
448
  reportSdkError(err);
449
449
  }
450
450
  }
451
451
 
452
- async function deleteWallet(projectId, walletId, args) {
452
+ async function deleteSigner(projectId, signerId, args) {
453
453
  const parsedArgs = normalizeArgv(args);
454
454
  assertKnownFlags(parsedArgs, ["--confirm", "--help", "-h"]);
455
455
  const extra = positionalArgs(parsedArgs);
@@ -460,7 +460,7 @@ async function deleteWallet(projectId, walletId, args) {
460
460
  fail({ code: "BAD_USAGE", message: "Required: --confirm" });
461
461
  }
462
462
  try {
463
- const data = await getSdk().contracts.deleteWallet(projectId, walletId);
463
+ const data = await getSdk().contracts.deleteSigner(projectId, signerId);
464
464
  console.log(JSON.stringify(data, null, 2));
465
465
  } catch (err) {
466
466
  reportSdkError(err);
@@ -471,9 +471,9 @@ export async function run(sub, args) {
471
471
  if (!sub || sub === "--help" || sub === "-h") { console.log(HELP); process.exit(0); }
472
472
  if (Array.isArray(args) && (args.includes("--help") || args.includes("-h"))) { console.log(SUB_HELP[sub] || HELP); process.exit(0); }
473
473
  switch (sub) {
474
- case "provision-wallet": await provisionWallet(args[0], args.slice(1)); break;
475
- case "get-wallet": await getWallet(args[0], args[1], args.slice(2)); break;
476
- case "list-wallets": await listWallets(args[0], args.slice(1)); break;
474
+ case "provision-signer": await provisionSigner(args[0], args.slice(1)); break;
475
+ case "get-signer": await getSigner(args[0], args[1], args.slice(2)); break;
476
+ case "list-signers": await listSigners(args[0], args.slice(1)); break;
477
477
  case "set-recovery": await setRecovery(args[0], args[1], args.slice(2)); break;
478
478
  case "set-alert": await setAlert(args[0], args[1], args.slice(2)); break;
479
479
  case "call": await call(args[0], args[1], args.slice(2)); break;
@@ -481,7 +481,7 @@ export async function run(sub, args) {
481
481
  case "read": await read(args); break;
482
482
  case "status": await status(args[0], args[1], args.slice(2)); break;
483
483
  case "drain": await drain(args[0], args[1], args.slice(2)); break;
484
- case "delete": await deleteWallet(args[0], args[1], args.slice(2)); break;
484
+ case "delete": await deleteSigner(args[0], args[1], args.slice(2)); break;
485
485
  default:
486
486
  console.error(`Unknown subcommand: ${sub}\n`);
487
487
  console.log(HELP);
package/lib/projects.mjs CHANGED
@@ -16,8 +16,9 @@ Subcommands:
16
16
  use <id> Set the active project (used as default for other commands)
17
17
  list [--org <id>] [--all] List your projects from the server (name, site_url, custom domains, org_id, active marker)
18
18
  rename <id> --name <label> Rename a project (fix an auto-generated name)
19
- info [id] Show project details: REST URL, keys
20
- keys [id] Print anon_key and service_key as JSON
19
+ get [id] Authoritative server read: status, org, tier, active deploy, mailbox, usage vs limits (live; no keys)
20
+ info [id] Show local project details: REST URL, keys (local keystore only)
21
+ keys [id] Print anon_key and service_key as JSON (local keystore only)
21
22
  sql [id] "<query>" [--file <path>] [--params '<json>'] Run a SQL query (supports parameterized queries)
22
23
  rest [id] <table> [params] Query a table via the REST API (PostgREST)
23
24
  usage [id] Show compute/storage usage for a project
@@ -41,6 +42,7 @@ Examples:
41
42
  run402 projects list --org 11111111-2222-3333-4444-555555555555
42
43
  run402 projects list --all
43
44
  run402 projects rename prj_abc123 --name "My Site"
45
+ run402 projects get prj_abc123
44
46
  run402 projects info prj_abc123
45
47
  run402 projects sql prj_abc123 "SELECT * FROM users LIMIT 5"
46
48
  run402 projects sql prj_abc123 "SELECT * FROM users WHERE id = $1" --params '[42]'
@@ -508,6 +510,15 @@ async function rename(projectId, args = []) {
508
510
  }
509
511
  }
510
512
 
513
+ async function get(projectId) {
514
+ try {
515
+ const data = await getSdk().projects.get(projectId);
516
+ console.log(JSON.stringify(data, null, 2));
517
+ } catch (err) {
518
+ reportSdkError(err);
519
+ }
520
+ }
521
+
511
522
  async function info(projectId) {
512
523
  try {
513
524
  const data = await getSdk().projects.info(projectId);
@@ -755,6 +766,7 @@ export async function run(sub, args) {
755
766
  case "use": await use(args[0]); break;
756
767
  case "list": await list(args); break;
757
768
  case "rename": { const { projectId, rest } = resolvePositionalProject(args, { rejectBareFirst: true, valueFlags: FLAGS_BY_SUB.rename.values }); await rename(projectId, rest); break; }
769
+ case "get": { const { projectId } = resolvePositionalProject(args, { rejectBareFirst: true }); await get(projectId); break; }
758
770
  case "info": { const { projectId } = resolvePositionalProject(args, { rejectBareFirst: true }); await info(projectId); break; }
759
771
  case "keys": { const { projectId } = resolvePositionalProject(args, { rejectBareFirst: true }); await keys(projectId); break; }
760
772
  case "sql": { const { projectId, rest } = resolvePositionalProject(args, { maxBarePositionals: 1, valueFlags: FLAGS_BY_SUB.sql.values, rejectBareFirstWhenFlagPresent: ["--file"] }); await sqlCmd(projectId, rest); break; }
package/lib/transfer.mjs CHANGED
@@ -13,11 +13,11 @@ import {
13
13
  const HELP = `run402 transfer — Two-party project transfer (v1.59)
14
14
 
15
15
  Usage:
16
- run402 transfer init --to <wallet|email> [--project <id>] [--billing-policy migrate] [--message <text>] [--kysigned <record_id>]
16
+ run402 transfer init --to <wallet|email> [--project <id>] [--billing-policy migrate] [--message <text>] [--kysigned <record_id>] [--retain-collaborator developer]
17
17
  run402 transfer preview <transfer_id>
18
18
  run402 transfer list [--incoming | --outgoing] [--limit N] [--offset N]
19
19
  run402 transfer accept <transfer_id>
20
- run402 transfer claim <transfer_id> [--into <organization_id>]
20
+ run402 transfer claim <transfer_id> [--into <organization_id>] [--accept-retained-collaborator]
21
21
  run402 transfer cancel <transfer_id> [--reason <text>] [--handoff]
22
22
 
23
23
  Subcommands:
@@ -41,7 +41,7 @@ const SUB_HELP = {
41
41
  init: `run402 transfer init — Initiate a project transfer
42
42
 
43
43
  Usage:
44
- run402 transfer init --to <wallet|email> [--project <id>] [--billing-policy migrate] [--message <text>] [--kysigned <record_id>]
44
+ run402 transfer init --to <wallet|email> [--project <id>] [--billing-policy migrate] [--message <text>] [--kysigned <record_id>] [--retain-collaborator developer]
45
45
 
46
46
  Options:
47
47
  --project <id> Project id (defaults to the active project)
@@ -49,6 +49,9 @@ Options:
49
49
  --billing-policy <p> Billing policy. Phase 1A only allows 'migrate' (default).
50
50
  --message <text> Optional note shown to the recipient in preview + emails.
51
51
  --kysigned <record_id> Optional KySigned record id (Phase 1A: informational only).
52
+ --retain-collaborator <role> Email handoffs only (v1.91): keep a 'developer' membership in
53
+ the recipient's org after handoff. The recipient must accept it at
54
+ claim (--accept-retained-collaborator); omit for full severance.
52
55
 
53
56
  Notes:
54
57
  - Caller's wallet must currently own the project (gateway re-checks fresh DB).
@@ -97,18 +100,24 @@ email->org handoff instead of a wallet transfer.
97
100
  claim: `run402 transfer claim — Claim an incoming email handoff
98
101
 
99
102
  Usage:
100
- run402 transfer claim <transfer_id> [--into <organization_id>]
103
+ run402 transfer claim <transfer_id> [--into <organization_id>] [--accept-retained-collaborator]
101
104
 
102
105
  Claims a handoff addressed to your email into an org you own. Omit --into to
103
106
  claim into a brand-new org. This is the email-handoff analog of 'accept'.
107
+
108
+ Options:
109
+ --into <organization_id> Org to claim into (omit = brand-new org).
110
+ --accept-retained-collaborator Accept the sender's v1.91 retained-developer-membership offer
111
+ (see 'transfer preview' retain_collaborator). Omit = full severance.
104
112
  `,
105
113
  };
106
114
 
107
115
  const BILLING_POLICIES = new Set(["migrate"]);
116
+ const RETAIN_ROLES = new Set(["developer"]);
108
117
 
109
118
  async function init(args) {
110
119
  const parsedArgs = normalizeArgv(args);
111
- const valueFlags = ["--project", "--to", "--billing-policy", "--message", "--kysigned"];
120
+ const valueFlags = ["--project", "--to", "--billing-policy", "--message", "--kysigned", "--retain-collaborator"];
112
121
  assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
113
122
  const extra = positionalArgs(parsedArgs, valueFlags);
114
123
  if (extra.length > 0) {
@@ -133,10 +142,31 @@ async function init(args) {
133
142
  }
134
143
  const message = flagValue(parsedArgs, "--message");
135
144
  const kysigned = flagValue(parsedArgs, "--kysigned");
145
+ const retainCollaborator = flagValue(parsedArgs, "--retain-collaborator");
136
146
 
137
147
  // One noun, two rails: an email recipient routes to the email->org handoff;
138
148
  // a wallet recipient routes to the two-party wallet transfer.
139
149
  const isEmail = toWallet.includes("@");
150
+
151
+ // --retain-collaborator (v1.91) is a handoff-only opt-in: the sender keeps a
152
+ // developer membership in the recipient's org (recipient must accept at claim).
153
+ if (retainCollaborator !== null) {
154
+ if (!isEmail) {
155
+ fail({
156
+ code: "BAD_FLAG",
157
+ message: "--retain-collaborator applies only to email handoffs; a wallet --to uses the two-party transfer rail.",
158
+ details: { flag: "--retain-collaborator" },
159
+ });
160
+ }
161
+ if (!RETAIN_ROLES.has(retainCollaborator)) {
162
+ fail({
163
+ code: "BAD_FLAG",
164
+ message: `Unsupported --retain-collaborator role: ${retainCollaborator}. Allowed: ${[...RETAIN_ROLES].join(", ")}.`,
165
+ details: { flag: "--retain-collaborator", value: retainCollaborator, allowed: [...RETAIN_ROLES] },
166
+ });
167
+ }
168
+ }
169
+
140
170
  allowanceAuthHeaders(
141
171
  isEmail ? `/projects/v1/${projectId}/handoffs` : `/projects/v1/${projectId}/transfers`,
142
172
  );
@@ -147,6 +177,7 @@ async function init(args) {
147
177
  projectId,
148
178
  toEmail: toWallet,
149
179
  message: message ?? undefined,
180
+ retainCollaborator: retainCollaborator ? { role: retainCollaborator } : undefined,
150
181
  })
151
182
  : await getSdk().admin.transfers.initiate({
152
183
  projectId,
@@ -278,18 +309,22 @@ async function cancel(args) {
278
309
  async function claim(args) {
279
310
  const parsedArgs = normalizeArgv(args);
280
311
  const valueFlags = ["--into"];
281
- assertKnownFlags(parsedArgs, [...valueFlags, "--help", "-h"], valueFlags);
312
+ assertKnownFlags(parsedArgs, [...valueFlags, "--accept-retained-collaborator", "--help", "-h"], valueFlags);
282
313
  const positionals = positionalArgs(parsedArgs, valueFlags);
283
314
  if (positionals.length !== 1) {
284
- fail({ code: "BAD_USAGE", message: "Usage: run402 transfer claim <transfer_id> [--into <organization_id>]" });
315
+ fail({ code: "BAD_USAGE", message: "Usage: run402 transfer claim <transfer_id> [--into <organization_id>] [--accept-retained-collaborator]" });
285
316
  }
286
317
  const transferId = positionals[0];
287
318
  const into = flagValue(parsedArgs, "--into");
319
+ // v1.91: accept the sender's retained-developer-membership offer (see the
320
+ // preview's `retain_collaborator` block). Absent = full severance (default).
321
+ const acceptRetain = parsedArgs.includes("--accept-retained-collaborator");
288
322
  allowanceAuthHeaders(`/agent/v1/handoffs/${transferId}/claim`);
289
323
 
290
324
  try {
291
325
  const data = await getSdk().admin.transfers.claimHandoff(transferId, {
292
326
  organizationId: into ?? undefined,
327
+ acceptRetainedCollaborator: acceptRetain || undefined,
293
328
  });
294
329
  console.log(JSON.stringify(data, null, 2));
295
330
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "run402",
3
- "version": "2.48.0",
3
+ "version": "3.0.0",
4
4
  "description": "CLI for Run402 — provision Postgres databases, deploy static sites, generate images, and manage wallets via x402 and MPP micropayments.",
5
5
  "type": "module",
6
6
  "bin": {