@defdone/annon 0.1.0-alpha.1 → 0.1.0-alpha.3

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.
Files changed (87) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/CLI.md +173 -13
  3. package/README.md +47 -10
  4. package/dist/api/annon-types.d.ts +2 -1
  5. package/dist/api/annon.js +9 -2
  6. package/dist/browser/index.d.ts +1 -1
  7. package/dist/browser/login-bundle.js +61 -60
  8. package/dist/browser/login-dom.d.ts +13 -0
  9. package/dist/browser/login-dom.js +71 -0
  10. package/dist/browser/login-dropdown.d.ts +32 -0
  11. package/dist/browser/login-dropdown.js +129 -0
  12. package/dist/browser/login-format.d.ts +16 -0
  13. package/dist/browser/login-format.js +52 -0
  14. package/dist/browser/login-status.d.ts +70 -0
  15. package/dist/browser/login-status.js +85 -0
  16. package/dist/browser/login-steps.d.ts +6 -0
  17. package/dist/browser/login-steps.js +35 -0
  18. package/dist/browser/login-terms.d.ts +10 -0
  19. package/dist/browser/login-terms.js +69 -0
  20. package/dist/browser/login-view.d.ts +34 -0
  21. package/dist/browser/login-view.js +227 -0
  22. package/dist/browser/login-wallet.d.ts +28 -0
  23. package/dist/browser/login-wallet.js +179 -0
  24. package/dist/browser/login.js +154 -142
  25. package/dist/browser/purchase-bundle.js +29 -29
  26. package/dist/browser/purchases.d.ts +4 -0
  27. package/dist/browser/purchases.js +14 -0
  28. package/dist/browser/reconcile.d.ts +41 -0
  29. package/dist/browser/reconcile.js +77 -0
  30. package/dist/node/adapter-checks.d.ts +3 -1
  31. package/dist/node/adapter-checks.js +3 -1
  32. package/dist/node/adapter.js +10 -3
  33. package/dist/node/agent.js +2 -1
  34. package/dist/node/approval-status.d.ts +34 -0
  35. package/dist/node/approval-status.js +71 -0
  36. package/dist/node/backup-secret-init.d.ts +9 -0
  37. package/dist/node/backup-secret-init.js +103 -0
  38. package/dist/node/backup-secret.d.ts +35 -0
  39. package/dist/node/backup-secret.js +104 -0
  40. package/dist/node/browser-login.d.ts +2 -0
  41. package/dist/node/browser-login.js +95 -16
  42. package/dist/node/buy-commands.js +17 -9
  43. package/dist/node/buy-flow.d.ts +18 -0
  44. package/dist/node/buy-flow.js +89 -0
  45. package/dist/node/cli-flags.d.ts +10 -0
  46. package/dist/node/cli-flags.js +68 -0
  47. package/dist/node/cli.d.ts +9 -1
  48. package/dist/node/cli.js +87 -95
  49. package/dist/node/command-context.js +1 -1
  50. package/dist/node/config.d.ts +4 -0
  51. package/dist/node/config.js +6 -2
  52. package/dist/node/core-adapter.js +4 -0
  53. package/dist/node/login-page.d.ts +7 -0
  54. package/dist/node/login-page.js +77 -0
  55. package/dist/node/main.js +20 -2
  56. package/dist/node/output/discovery.d.ts +23 -0
  57. package/dist/node/output/discovery.js +52 -0
  58. package/dist/node/output/human.d.ts +10 -0
  59. package/dist/node/output/human.js +46 -0
  60. package/dist/node/output/purchase.d.ts +10 -0
  61. package/dist/node/output/purchase.js +78 -0
  62. package/dist/node/output/text.d.ts +10 -0
  63. package/dist/node/output/text.js +43 -0
  64. package/dist/node/purchase-commands.d.ts +2 -2
  65. package/dist/node/purchase-commands.js +14 -6
  66. package/dist/node/purchase-ref.d.ts +15 -0
  67. package/dist/node/purchase-ref.js +93 -0
  68. package/dist/node/saved-state.d.ts +4 -0
  69. package/dist/node/saved-state.js +50 -0
  70. package/dist/node/shell/complete.d.ts +2 -0
  71. package/dist/node/shell/complete.js +17 -0
  72. package/dist/node/shell/session.d.ts +20 -0
  73. package/dist/node/shell/session.js +219 -0
  74. package/dist/node/shell/welcome.d.ts +9 -0
  75. package/dist/node/shell/welcome.js +39 -0
  76. package/dist/node/shell/words.d.ts +1 -0
  77. package/dist/node/shell/words.js +36 -0
  78. package/dist/node/shops.d.ts +5 -0
  79. package/dist/node/shops.js +22 -0
  80. package/dist/node/version.d.ts +1 -0
  81. package/dist/node/version.js +3 -0
  82. package/dist/node/wallets.d.ts +1 -1
  83. package/dist/node/wallets.js +2 -10
  84. package/dist/pay/purchase.d.ts +2 -1
  85. package/dist/wallet/uncertain.d.ts +6 -3
  86. package/dist/wallet/uncertain.js +4 -1
  87. package/package.json +1 -1
@@ -3,6 +3,8 @@ import { randomBytes, timingSafeEqual } from 'node:crypto';
3
3
  import { readFile } from 'node:fs/promises';
4
4
  import { createServer } from 'node:http';
5
5
  import { TransactionUncertain } from '../wallet/uncertain.js';
6
+ import { APPROVED_LINE, cancelledError, expiredError, failureLine, isPageReport, openingLine, sendUncertain, usableHash, waitingLine, } from './approval-status.js';
7
+ import { LOGIN_PAGE_CSS, LOGIN_PAGE_HTML } from './login-page.js';
6
8
  const equal = (a, b) => Buffer.byteLength(a) === Buffer.byteLength(b) && timingSafeEqual(Buffer.from(a), Buffer.from(b));
7
9
  /**
8
10
  * The approval page talks only to this loopback server, the NEAR RPC its Wallet Selector uses
@@ -12,6 +14,7 @@ const equal = (a, b) => Buffer.byteLength(a) === Buffer.byteLength(b) && timingS
12
14
  */
13
15
  export const LOGIN_PAGE_CSP = "default-src 'none'; script-src 'self'; connect-src 'self' https://free.rpc.fastnear.com https://test.rpc.fastnear.com https://h4n.app; frame-src https://hot-labs.org; style-src 'self'; img-src 'self' data:; frame-ancestors 'none'; base-uri 'none'; form-action 'none'";
14
16
  export async function startBrowserSession(o) {
17
+ const notify = o.notify ?? ((line) => void process.stderr.write(`${line}\n`));
15
18
  let asset = o.asset;
16
19
  if (asset === undefined) {
17
20
  try {
@@ -41,7 +44,22 @@ export async function startBrowserSession(o) {
41
44
  .map((k) => `${JSON.stringify(k)}:${canonical(v[k])}`)
42
45
  .join(',')}}`
43
46
  : JSON.stringify(v);
47
+ /** Ends a request whose transaction may be on chain, with the CLI's own warning. */
48
+ const uncertain = (p, txHash) => {
49
+ const { line, error } = sendUncertain(p.action, txHash);
50
+ notify(line);
51
+ return error;
52
+ };
53
+ /** A transaction request whose send started, or that returned transaction evidence at all. */
54
+ const sent = (p) => p.sending === true ||
55
+ p.action.operation === 'near.signTransaction';
44
56
  let pending;
57
+ const followers = new Set();
58
+ const advance = (action = null) => {
59
+ for (const response of followers)
60
+ response.end(JSON.stringify({ action }));
61
+ followers.clear();
62
+ };
45
63
  const server = createServer((req, res) => {
46
64
  void (async () => {
47
65
  const headers = {
@@ -67,8 +85,12 @@ export async function startBrowserSession(o) {
67
85
  !equal(req.headers.authorization ?? '', `Bearer ${capability}`))
68
86
  return void send(403, '{}');
69
87
  if (path === '/api/action' && ['GET', 'POST'].includes(req.method ?? ''))
70
- return void send(200, JSON.stringify({ pairingCode, action: pending?.action ?? null }));
71
- if (path !== '/api/result' || req.method !== 'POST')
88
+ return void send(200, JSON.stringify({
89
+ pairingCode,
90
+ action: pending?.action ?? null,
91
+ sendStarted: pending?.sending === true,
92
+ }));
93
+ if (!['/api/result', '/api/report'].includes(path) || req.method !== 'POST')
72
94
  return void send(404, '{}');
73
95
  let size = 0;
74
96
  const parts = [];
@@ -79,6 +101,31 @@ export async function startBrowserSession(o) {
79
101
  parts.push(chunk);
80
102
  }
81
103
  const b = JSON.parse(Buffer.concat(parts).toString());
104
+ if (path === '/api/report') {
105
+ // A fixed reason for the current, unconsumed request. Only `cancelled` and
106
+ // `send_uncertain` end it, and never as cancelled once the page started a send.
107
+ const p = pending;
108
+ if (!p || p.busy || b.id !== p.action.id || !isPageReport(b.reason))
109
+ return void send(409, '{}');
110
+ if (b.reason === 'send_started') {
111
+ // One send per request: a reloaded or second page cannot start another.
112
+ if (p.sending)
113
+ return void send(409, '{}');
114
+ p.sending = true;
115
+ }
116
+ else if (b.reason === 'cancelled' || b.reason === 'send_uncertain') {
117
+ clearTimeout(p.timer);
118
+ pending = undefined;
119
+ armIdle();
120
+ p.reject(p.sending || b.reason === 'send_uncertain' ? uncertain(p) : cancelledError());
121
+ }
122
+ else if (!p.sending && p.failure !== b.reason) {
123
+ // After a send started no failure can be retried, so none is announced as one.
124
+ p.failure = b.reason;
125
+ notify(failureLine(b.reason, p.action.expiresAt - Date.now()));
126
+ }
127
+ return void send(200, '{}');
128
+ }
82
129
  const key = canonical(b.result);
83
130
  const previous = results.get(b.id);
84
131
  if (previous && Date.now() < previous.expiresAt) {
@@ -104,9 +151,17 @@ export async function startBrowserSession(o) {
104
151
  pending = undefined;
105
152
  armIdle();
106
153
  }
107
- p.resolve(b.result);
154
+ notify(APPROVED_LINE);
108
155
  complete(200);
109
- send(200, '{}');
156
+ if (b.follow === true) {
157
+ res.writeHead(200, { ...headers, 'content-type': 'application/json' });
158
+ res.flushHeaders();
159
+ followers.add(res);
160
+ res.on('close', () => followers.delete(res));
161
+ }
162
+ else
163
+ send(200, '{}');
164
+ p.resolve(b.result);
110
165
  }
111
166
  catch (error) {
112
167
  clearTimeout(p.timer);
@@ -114,7 +169,13 @@ export async function startBrowserSession(o) {
114
169
  pending = undefined;
115
170
  armIdle();
116
171
  }
117
- p.reject(error instanceof TransactionUncertain ? error : new Error('wallet result rejected'));
172
+ // After a send, a result that cannot be verified is never evidence of success or of
173
+ // failure: the request ends as uncertain, with any usable hash, so it stays saved.
174
+ p.reject(sent(p)
175
+ ? uncertain(p, usableHash(error instanceof TransactionUncertain ? error.txHash : b?.result?.txHash))
176
+ : error instanceof TransactionUncertain
177
+ ? error
178
+ : new Error('wallet result rejected'));
118
179
  complete(400);
119
180
  send(400, '{}');
120
181
  }
@@ -124,8 +185,10 @@ export async function startBrowserSession(o) {
124
185
  return void send(405, '{}');
125
186
  if (path === '/app.js')
126
187
  return void send(200, asset, 'text/javascript');
188
+ if (path === '/app.css')
189
+ return void send(200, LOGIN_PAGE_CSS, 'text/css; charset=utf-8');
127
190
  if (path === '/' || path === '/callback')
128
- return void send(200, '<!doctype html><html><head><meta charset="utf-8"><title>Annon wallet approval</title><script src="/app.js" defer></script></head><body><h1>Annon wallet approval</h1><p id="pair"></p><pre id="action"></pre><p><label for="near-wallet">NEAR wallet</label> <select id="near-wallet"><option value="meteor-wallet">Meteor Wallet</option><option id="near-wallet-hot" value="hot-wallet">HOT Wallet (mainnet)</option></select></p><button id="approve">Review in wallet</button><p id="status"></p></body></html>', 'text/html; charset=utf-8');
191
+ return void send(200, LOGIN_PAGE_HTML, 'text/html; charset=utf-8');
129
192
  send(404, '{}');
130
193
  })().catch(() => {
131
194
  if (!res.headersSent)
@@ -148,8 +211,19 @@ export async function startBrowserSession(o) {
148
211
  pending.reject(new Error('session closed'));
149
212
  pending = undefined;
150
213
  }
214
+ const flushed = [...followers].map((response) => new Promise((resolve) => response.end(JSON.stringify({ action: null }), resolve)));
215
+ followers.clear();
216
+ const stopped = new Promise((resolve) => server.close(() => resolve()));
217
+ let flushTimer;
218
+ await Promise.race([
219
+ Promise.all(flushed),
220
+ new Promise((resolve) => {
221
+ flushTimer = setTimeout(resolve, 1000);
222
+ }),
223
+ ]);
224
+ clearTimeout(flushTimer);
151
225
  server.closeAllConnections();
152
- await new Promise((resolve) => server.close(() => resolve()));
226
+ await stopped;
153
227
  };
154
228
  let timeout;
155
229
  const armIdle = () => {
@@ -161,6 +235,9 @@ export async function startBrowserSession(o) {
161
235
  };
162
236
  armIdle();
163
237
  const url = `${origin}/#${capability}`;
238
+ // The capability-free page URL: in open mode the page bootstraps the capability once.
239
+ if (!o.noOpen)
240
+ notify(openingLine(`${origin}/`));
164
241
  if (!o.noOpen)
165
242
  (o.open ??
166
243
  ((target) => {
@@ -183,21 +260,23 @@ export async function startBrowserSession(o) {
183
260
  return;
184
261
  }
185
262
  clearTimeout(timeout);
186
- const timer = setTimeout(() => {
187
- if (pending?.timer === timer) {
188
- pending = undefined;
189
- armIdle();
190
- }
191
- reject(new Error('action expired'));
192
- }, action.expiresAt - Date.now());
193
- pending = {
263
+ const entry = {
194
264
  action: JSON.parse(JSON.stringify(action)),
195
265
  busy: false,
196
266
  resolve: resolve,
197
267
  reject,
198
268
  verify: verify,
199
- timer,
269
+ timer: setTimeout(() => {
270
+ if (pending === entry) {
271
+ pending = undefined;
272
+ armIdle();
273
+ }
274
+ reject(entry.sending ? uncertain(entry) : expiredError(entry.failure));
275
+ }, action.expiresAt - Date.now()),
200
276
  };
277
+ pending = entry;
278
+ advance(entry.action);
279
+ notify(waitingLine(action.expiresAt - Date.now()));
201
280
  }),
202
281
  };
203
282
  }
@@ -1,5 +1,5 @@
1
1
  /** Buyer commands before a purchase exists: `verify` a file and `buy` it. */
2
- import { mkdir, readdir, readFile } from 'node:fs/promises';
2
+ import { mkdir, readFile } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import { inspect, inspectHeader, MAX_HEADER, parseHeader } from '../envelope/pay2.js';
5
5
  import { AnnonError } from '../errors.js';
@@ -17,6 +17,7 @@ import { offer, resourceHeader, response } from './offers.js';
17
17
  import { originFor } from './origin.js';
18
18
  import { humanPaymentTerms, quotedSource, savedPayment, validatePaymentToken, } from './payment-terms.js';
19
19
  import { claimPurchase } from './purchase-guard.js';
20
+ import { rememberPurchase, savedRecordIds } from './purchase-ref.js';
20
21
  import { resourceAllowed } from './resources.js';
21
22
  /** `verify`: a local file's header and body, or a remote file's header, against its listing. */
22
23
  async function verifyFile(ctx) {
@@ -155,13 +156,10 @@ async function buy(ctx, preparedBuy) {
155
156
  const guardDir = join(c.stateDir, 'purchase-guards');
156
157
  await mkdir(guardDir, { recursive: true, mode: 0o700 });
157
158
  const known = [];
158
- for (const name of await readdir(storeDir)) {
159
- if (!/^[0-9a-f]{64}\.json$/.test(name))
159
+ for (const id of await savedRecordIds(storeDir)) {
160
+ if (!isId(id))
160
161
  continue;
161
- const envelope = JSON.parse(await readFile(join(storeDir, name), 'utf8'));
162
- if (!isId(envelope.id))
163
- continue;
164
- const saved = await sdk.store.load(purchaseKey(envelope.id));
162
+ const saved = await sdk.store.load(purchaseKey(id));
165
163
  if (saved?.purchase.file_id === found.fileId &&
166
164
  saved.purchase.contract === c.contractId &&
167
165
  saved.terms.network === (saved.purchase.route === 'base-usdc' ? 'eip155:8453' : c.network))
@@ -176,6 +174,10 @@ async function buy(ctx, preparedBuy) {
176
174
  return true;
177
175
  if (!saved)
178
176
  return Date.now() / 1000 < claimUntil;
177
+ // A NEAR send that may be on chain blocks another payment until reconciled or --force,
178
+ // even after the purchase expired: a late transfer still moves tokens.
179
+ if (saved.nearSubmission?.state === 'uncertain')
180
+ return true;
179
181
  return (Number(saved.purchase.expires_at) > Date.now() / 1000 ||
180
182
  (saved.purchase.route === 'base-usdc' && saved.attempts.length > 0));
181
183
  },
@@ -212,6 +214,7 @@ async function buy(ctx, preparedBuy) {
212
214
  if (renewalError)
213
215
  throw renewalError;
214
216
  await guard.saved(record.id);
217
+ await rememberPurchase(c, record.id);
215
218
  },
216
219
  recovery: buyer,
217
220
  ...(baseSponsorship ? { baseSponsorship } : {}),
@@ -277,11 +280,16 @@ async function buy(ctx, preparedBuy) {
277
280
  : bought.instructions
278
281
  ? 'deposit_required'
279
282
  : 'submitted',
280
- ...(bought.uncertain ? { txHash: bought.uncertain.txHash } : {}),
283
+ // A browser send that failed after the wallet's send call has no hash to report.
284
+ ...(bought.uncertain?.txHash ? { txHash: bought.uncertain.txHash } : {}),
281
285
  backup: backupPath(c, bought.record.id),
282
286
  ...(bought.instructions ? { instructions: bought.instructions } : {}),
283
287
  ...(source ? { source } : {}),
284
- next: 'Fund the deposit if instructed, then check status and open this purchase.',
288
+ next: bought.uncertain
289
+ ? `The wallet may have sent this payment. Do not pay again. Run annon status ${bought.record.id} until it is accepted.`
290
+ : bought.instructions
291
+ ? 'Fund the deposit as instructed, then check status and open this purchase.'
292
+ : `Payment sent. Run annon status ${bought.record.id}, then annon open ${bought.record.id} --output <file>.`,
285
293
  };
286
294
  }
287
295
  catch (error) {
@@ -0,0 +1,18 @@
1
+ import type { Input } from './cli.js';
2
+ export interface BuyFollow {
3
+ notify?: (message: string) => void;
4
+ /** Pause between status checks; resolves early when `signal` aborts. */
5
+ wait?: (ms: number, signal?: AbortSignal) => Promise<void>;
6
+ /** Aborting stops the waiting only. The saved purchase is left as it is. */
7
+ signal?: AbortSignal;
8
+ /** Asked once the purchase is accepted and no `--open` path was given. */
9
+ savePath?: () => Promise<string | undefined>;
10
+ onWaiting?: (waiting: boolean) => void;
11
+ }
12
+ type Execute = (input: Input) => Promise<unknown>;
13
+ /**
14
+ * Runs one command. A `buy` is followed when the caller is a person (`follow`) or asked for
15
+ * `--open`; a deposit route returns its instructions without waiting.
16
+ */
17
+ export declare function buyFlow(input: Input, execute: Execute, follow: boolean, o?: BuyFollow): Promise<unknown>;
18
+ export {};
@@ -0,0 +1,89 @@
1
+ import { commonInput } from './cli-flags.js';
2
+ import { publicError } from './errors.js';
3
+ import { newOutput } from './io.js';
4
+ const CHECKS = 60;
5
+ const INTERVAL_MS = 3000;
6
+ const pause = (ms, signal) => new Promise((resolve) => {
7
+ const done = () => {
8
+ clearTimeout(timer);
9
+ resolve();
10
+ };
11
+ const timer = setTimeout(done, ms);
12
+ signal?.addEventListener('abort', done, { once: true });
13
+ });
14
+ /** Status checks until the purchase is accepted, the checks run out or the wait is cancelled. */
15
+ async function accepted(execute, input, id, o) {
16
+ o.onWaiting?.(true);
17
+ try {
18
+ for (let check = 0; check < CHECKS && !o.signal?.aborted; check++) {
19
+ if (check > 0)
20
+ await (o.wait ?? pause)(INTERVAL_MS, o.signal);
21
+ if (o.signal?.aborted)
22
+ break;
23
+ const status = (await execute({
24
+ ...commonInput(input),
25
+ command: 'status',
26
+ target: id,
27
+ }));
28
+ if (status.state === 'accepted')
29
+ return true;
30
+ }
31
+ return false;
32
+ }
33
+ finally {
34
+ o.onWaiting?.(false);
35
+ }
36
+ }
37
+ /**
38
+ * Runs one command. A `buy` is followed when the caller is a person (`follow`) or asked for
39
+ * `--open`; a deposit route returns its instructions without waiting.
40
+ */
41
+ export async function buyFlow(input, execute, follow, o = {}) {
42
+ if (input.command !== 'buy' || (!follow && input.open === undefined))
43
+ return execute(input);
44
+ // Refuse a taken output name before any payment step.
45
+ if (input.open !== undefined)
46
+ await newOutput(input.open);
47
+ const bought = (await execute(input));
48
+ const id = bought.purchaseId;
49
+ if (typeof id !== 'string' || !['submitted', 'uncertain'].includes(String(bought.state)))
50
+ return bought;
51
+ o.notify?.(bought.state === 'uncertain'
52
+ ? 'The wallet may have sent this payment. Do not pay again. Checking NEAR...'
53
+ : 'Payment sent. Waiting for NEAR to accept...');
54
+ // From here the payment exists: a later failure is reported next to the purchase, never instead.
55
+ const pending = (statusError) => ({
56
+ ...bought,
57
+ settlement: 'pending',
58
+ ...(statusError ? { statusError } : {}),
59
+ next: `Not accepted yet. Do not pay again. Run annon status ${id} until it is accepted, then annon open ${id} --output <file>.`,
60
+ });
61
+ try {
62
+ if (!(await accepted(execute, input, id, o)))
63
+ return pending();
64
+ }
65
+ catch (error) {
66
+ return pending(publicError(error));
67
+ }
68
+ const settled = { ...bought, state: 'accepted', next: `annon open ${id} --output <file>` };
69
+ const output = input.open ?? (await o.savePath?.());
70
+ if (!output)
71
+ return settled;
72
+ try {
73
+ const opened = (await execute({
74
+ ...commonInput(input),
75
+ command: 'open',
76
+ target: id,
77
+ output,
78
+ }));
79
+ return {
80
+ ...bought,
81
+ state: 'opened',
82
+ opened: { output: opened.output, bytes: opened.bytes },
83
+ next: 'The file is saved.',
84
+ };
85
+ }
86
+ catch (error) {
87
+ return { ...settled, openError: publicError(error) };
88
+ }
89
+ }
@@ -0,0 +1,10 @@
1
+ /** The flag tables of the CLI: what every command accepts, for parsing, completion and follow-ups. */
2
+ import type { Command, Input } from './cli.js';
3
+ export declare const common: string[];
4
+ export declare const perCommand: Record<Command, string[]>;
5
+ export declare const camel: (flag: string) => string;
6
+ export declare const commandNames: Command[];
7
+ /** Every flag a command accepts, as typed on the command line. */
8
+ export declare const commandFlags: (command: Command) => string[];
9
+ /** The deployment, wallet and output options of one command, for a follow-up command it starts. */
10
+ export declare const commonInput: (input: Input) => Partial<Input>;
@@ -0,0 +1,68 @@
1
+ export const common = [
2
+ 'no-open',
3
+ 'json',
4
+ 'config',
5
+ 'contract-id',
6
+ 'near-rpc',
7
+ 'base-rpc',
8
+ 'network',
9
+ 'state-dir',
10
+ 'relayer',
11
+ 'trust-contract',
12
+ 'wallet',
13
+ 'evm-key-file',
14
+ 'solana-key-file',
15
+ 'near-key-file',
16
+ 'near-account',
17
+ 'near-public-key',
18
+ 'backup-secret-file',
19
+ ];
20
+ export const perCommand = {
21
+ shops: ['search', 'limit', 'offset'],
22
+ shop: ['id'],
23
+ catalog: ['shop', 'search', 'limit', 'offset'],
24
+ fund: [],
25
+ budget: [],
26
+ login: [],
27
+ init: [
28
+ 'signer',
29
+ 'credential-env',
30
+ 'credential-keychain',
31
+ 'agent',
32
+ 'recovery',
33
+ 'key-env',
34
+ 'keychain-entry',
35
+ 'new-key',
36
+ 'daily-budget',
37
+ ],
38
+ share: ['resume', 'price', 'shop', 'title', 'description', 'storage-budget', 'listing-storage'],
39
+ sell: ['resume', 'price', 'split', 'base', 'moderator', 'output', 'storage-budget'],
40
+ buy: [
41
+ 'force',
42
+ 'origin',
43
+ 'route',
44
+ 'from-chain',
45
+ 'from-asset',
46
+ 'refund-to',
47
+ 'recovery',
48
+ 'max-price',
49
+ 'moderator',
50
+ 'url',
51
+ 'open',
52
+ 'shop',
53
+ 'search',
54
+ ],
55
+ open: ['purchase', 'file', 'url', 'output'],
56
+ recover: ['recovery', 'purchase'],
57
+ stop: [],
58
+ verify: [],
59
+ status: [],
60
+ };
61
+ export const camel = (flag) => flag.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
62
+ export const commandNames = Object.keys(perCommand);
63
+ /** Every flag a command accepts, as typed on the command line. */
64
+ export const commandFlags = (command) => [...perCommand[command], ...common].map((flag) => `--${flag}`);
65
+ /** The deployment, wallet and output options of one command, for a follow-up command it starts. */
66
+ export const commonInput = (input) => Object.fromEntries(common
67
+ .map((flag) => [camel(flag), input[camel(flag)]])
68
+ .filter(([, v]) => v !== undefined));
@@ -1,4 +1,5 @@
1
1
  import type { BuyRoute } from '../routes.js';
2
+ import { type BuyFollow } from './buy-flow.js';
2
3
  export type Command = 'shops' | 'shop' | 'catalog' | 'fund' | 'budget' | 'share' | 'sell' | 'buy' | 'open' | 'recover' | 'stop' | 'verify' | 'status' | 'login' | 'init';
3
4
  export interface Input {
4
5
  command: Command;
@@ -55,6 +56,13 @@ export interface Input {
55
56
  file?: string;
56
57
  url?: string;
57
58
  output?: string;
59
+ /** `buy --open <path>`: wait for acceptance, then write the opened file there. */
60
+ open?: string;
58
61
  }
59
62
  export declare function parseCli(argv: string[]): Input;
60
- export declare function runCli(argv: string[], execute: (input: Input) => Promise<unknown>, write: (text: string) => void): Promise<number>;
63
+ /** How one command line is presented and followed up; the defaults are the script contract. */
64
+ export interface RunOptions extends BuyFollow {
65
+ /** Readable text in place of the JSON envelope. `--json` always wins. */
66
+ human?: boolean;
67
+ }
68
+ export declare function runCli(argv: string[], execute: (input: Input) => Promise<unknown>, write: (text: string) => void, options?: RunOptions): Promise<number>;