@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.
- package/CHANGELOG.md +26 -0
- package/CLI.md +173 -13
- package/README.md +47 -10
- package/dist/api/annon-types.d.ts +2 -1
- package/dist/api/annon.js +9 -2
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/login-bundle.js +61 -60
- package/dist/browser/login-dom.d.ts +13 -0
- package/dist/browser/login-dom.js +71 -0
- package/dist/browser/login-dropdown.d.ts +32 -0
- package/dist/browser/login-dropdown.js +129 -0
- package/dist/browser/login-format.d.ts +16 -0
- package/dist/browser/login-format.js +52 -0
- package/dist/browser/login-status.d.ts +70 -0
- package/dist/browser/login-status.js +85 -0
- package/dist/browser/login-steps.d.ts +6 -0
- package/dist/browser/login-steps.js +35 -0
- package/dist/browser/login-terms.d.ts +10 -0
- package/dist/browser/login-terms.js +69 -0
- package/dist/browser/login-view.d.ts +34 -0
- package/dist/browser/login-view.js +227 -0
- package/dist/browser/login-wallet.d.ts +28 -0
- package/dist/browser/login-wallet.js +179 -0
- package/dist/browser/login.js +154 -142
- package/dist/browser/purchase-bundle.js +29 -29
- package/dist/browser/purchases.d.ts +4 -0
- package/dist/browser/purchases.js +14 -0
- package/dist/browser/reconcile.d.ts +41 -0
- package/dist/browser/reconcile.js +77 -0
- package/dist/node/adapter-checks.d.ts +3 -1
- package/dist/node/adapter-checks.js +3 -1
- package/dist/node/adapter.js +10 -3
- package/dist/node/agent.js +2 -1
- package/dist/node/approval-status.d.ts +34 -0
- package/dist/node/approval-status.js +71 -0
- package/dist/node/backup-secret-init.d.ts +9 -0
- package/dist/node/backup-secret-init.js +103 -0
- package/dist/node/backup-secret.d.ts +35 -0
- package/dist/node/backup-secret.js +104 -0
- package/dist/node/browser-login.d.ts +2 -0
- package/dist/node/browser-login.js +95 -16
- package/dist/node/buy-commands.js +17 -9
- package/dist/node/buy-flow.d.ts +18 -0
- package/dist/node/buy-flow.js +89 -0
- package/dist/node/cli-flags.d.ts +10 -0
- package/dist/node/cli-flags.js +68 -0
- package/dist/node/cli.d.ts +9 -1
- package/dist/node/cli.js +87 -95
- package/dist/node/command-context.js +1 -1
- package/dist/node/config.d.ts +4 -0
- package/dist/node/config.js +6 -2
- package/dist/node/core-adapter.js +4 -0
- package/dist/node/login-page.d.ts +7 -0
- package/dist/node/login-page.js +77 -0
- package/dist/node/main.js +20 -2
- package/dist/node/output/discovery.d.ts +23 -0
- package/dist/node/output/discovery.js +52 -0
- package/dist/node/output/human.d.ts +10 -0
- package/dist/node/output/human.js +46 -0
- package/dist/node/output/purchase.d.ts +10 -0
- package/dist/node/output/purchase.js +78 -0
- package/dist/node/output/text.d.ts +10 -0
- package/dist/node/output/text.js +43 -0
- package/dist/node/purchase-commands.d.ts +2 -2
- package/dist/node/purchase-commands.js +14 -6
- package/dist/node/purchase-ref.d.ts +15 -0
- package/dist/node/purchase-ref.js +93 -0
- package/dist/node/saved-state.d.ts +4 -0
- package/dist/node/saved-state.js +50 -0
- package/dist/node/shell/complete.d.ts +2 -0
- package/dist/node/shell/complete.js +17 -0
- package/dist/node/shell/session.d.ts +20 -0
- package/dist/node/shell/session.js +219 -0
- package/dist/node/shell/welcome.d.ts +9 -0
- package/dist/node/shell/welcome.js +39 -0
- package/dist/node/shell/words.d.ts +1 -0
- package/dist/node/shell/words.js +36 -0
- package/dist/node/shops.d.ts +5 -0
- package/dist/node/shops.js +22 -0
- package/dist/node/version.d.ts +1 -0
- package/dist/node/version.js +3 -0
- package/dist/node/wallets.d.ts +1 -1
- package/dist/node/wallets.js +2 -10
- package/dist/pay/purchase.d.ts +2 -1
- package/dist/wallet/uncertain.d.ts +6 -3
- package/dist/wallet/uncertain.js +4 -1
- package/package.json +1 -1
package/dist/node/cli.js
CHANGED
|
@@ -1,62 +1,14 @@
|
|
|
1
1
|
import { parseArgs } from 'node:util';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
'relayer',
|
|
13
|
-
'trust-contract',
|
|
14
|
-
'wallet',
|
|
15
|
-
'evm-key-file',
|
|
16
|
-
'solana-key-file',
|
|
17
|
-
'near-key-file',
|
|
18
|
-
'near-account',
|
|
19
|
-
'near-public-key',
|
|
20
|
-
'backup-secret-file',
|
|
21
|
-
];
|
|
22
|
-
const perCommand = {
|
|
23
|
-
shops: ['search', 'limit', 'offset'],
|
|
24
|
-
shop: ['id'],
|
|
25
|
-
catalog: ['shop', 'search', 'limit', 'offset'],
|
|
26
|
-
fund: [],
|
|
27
|
-
budget: [],
|
|
28
|
-
login: [],
|
|
29
|
-
init: [
|
|
30
|
-
'signer',
|
|
31
|
-
'credential-env',
|
|
32
|
-
'credential-keychain',
|
|
33
|
-
'agent',
|
|
34
|
-
'recovery',
|
|
35
|
-
'key-env',
|
|
36
|
-
'keychain-entry',
|
|
37
|
-
'new-key',
|
|
38
|
-
'daily-budget',
|
|
39
|
-
],
|
|
40
|
-
share: ['resume', 'price', 'shop', 'title', 'description', 'storage-budget', 'listing-storage'],
|
|
41
|
-
sell: ['resume', 'price', 'split', 'base', 'moderator', 'output', 'storage-budget'],
|
|
42
|
-
buy: [
|
|
43
|
-
'force',
|
|
44
|
-
'origin',
|
|
45
|
-
'route',
|
|
46
|
-
'from-chain',
|
|
47
|
-
'from-asset',
|
|
48
|
-
'refund-to',
|
|
49
|
-
'recovery',
|
|
50
|
-
'max-price',
|
|
51
|
-
'moderator',
|
|
52
|
-
'url',
|
|
53
|
-
],
|
|
54
|
-
open: ['purchase', 'file', 'url', 'output'],
|
|
55
|
-
recover: ['recovery', 'purchase'],
|
|
56
|
-
stop: [],
|
|
57
|
-
verify: [],
|
|
58
|
-
status: [],
|
|
59
|
-
};
|
|
2
|
+
import { isRow } from './adapter-checks.js';
|
|
3
|
+
import { buyFlow } from './buy-flow.js';
|
|
4
|
+
import { camel, common, perCommand } from './cli-flags.js';
|
|
5
|
+
import { AdapterError, publicError, usage } from './errors.js';
|
|
6
|
+
import { humanError, humanResult } from './output/human.js';
|
|
7
|
+
import { version } from './version.js';
|
|
8
|
+
const bareInit = ['json', 'config', 'stateDir', 'backupSecretFile'];
|
|
9
|
+
/** Without a target `status` and `open` use the latest purchase; these take none at all. */
|
|
10
|
+
const optionalTarget = ['status', 'open'];
|
|
11
|
+
const noTarget = ['recover', 'login', 'init', 'shops', 'catalog'];
|
|
60
12
|
export function parseCli(argv) {
|
|
61
13
|
try {
|
|
62
14
|
const keys = [...new Set([...common, ...Object.values(perCommand).flat()])];
|
|
@@ -73,18 +25,15 @@ export function parseCli(argv) {
|
|
|
73
25
|
(command === 'fund' || command === 'shop' ? extra.length !== 1 : extra.length !== 0))
|
|
74
26
|
throw usage();
|
|
75
27
|
const cmd = command;
|
|
76
|
-
if ((
|
|
77
|
-
(![
|
|
28
|
+
if ((noTarget.includes(cmd) && target !== undefined) ||
|
|
29
|
+
(![...noTarget, ...optionalTarget].includes(cmd) && !target))
|
|
78
30
|
throw usage();
|
|
79
31
|
if (cmd === 'shop' && target !== 'add')
|
|
80
32
|
throw usage();
|
|
81
33
|
const allowed = [...common, ...perCommand[cmd]];
|
|
82
34
|
if (Object.keys(parsed.values).some((k) => !allowed.includes(k)))
|
|
83
35
|
throw usage();
|
|
84
|
-
const values = Object.fromEntries(Object.entries(parsed.values).map(([k, v]) => [
|
|
85
|
-
k.replace(/-([a-z])/g, (_, c) => c.toUpperCase()),
|
|
86
|
-
v,
|
|
87
|
-
]));
|
|
36
|
+
const values = Object.fromEntries(Object.entries(parsed.values).map(([k, v]) => [camel(k), v]));
|
|
88
37
|
for (const key of ['limit', 'offset']) {
|
|
89
38
|
if (values[key] !== undefined) {
|
|
90
39
|
if (typeof values[key] !== 'string' || !/^(0|[1-9][0-9]*)$/.test(values[key]))
|
|
@@ -117,10 +66,18 @@ export function parseCli(argv) {
|
|
|
117
66
|
else if (!values.price || !values.split)
|
|
118
67
|
throw usage();
|
|
119
68
|
}
|
|
120
|
-
|
|
69
|
+
// Bare init only sets up the backup secret; signer and deployment options need --agent.
|
|
70
|
+
if (cmd === 'init' &&
|
|
71
|
+
values.agent !== true &&
|
|
72
|
+
Object.keys(values).some((k) => !bareInit.includes(k)))
|
|
121
73
|
throw usage();
|
|
122
74
|
if (cmd === 'recover' && !values.recovery)
|
|
123
75
|
throw usage();
|
|
76
|
+
// --shop and --search only select the catalog listing a row number refers to.
|
|
77
|
+
if (cmd === 'buy' &&
|
|
78
|
+
(values.shop !== undefined || values.search !== undefined) &&
|
|
79
|
+
!isRow(target))
|
|
80
|
+
throw usage();
|
|
124
81
|
if (values.route && !['near', 'base', '1click'].includes(String(values.route)))
|
|
125
82
|
throw usage();
|
|
126
83
|
if (values.wallet && !['evm', 'solana', 'near'].includes(String(values.wallet)))
|
|
@@ -136,10 +93,13 @@ export function parseCli(argv) {
|
|
|
136
93
|
throw usage();
|
|
137
94
|
}
|
|
138
95
|
}
|
|
96
|
+
const versionFlags = ['--version', '-v'];
|
|
139
97
|
const HELP = `annon <command> [options]
|
|
98
|
+
annon (no arguments, in a terminal) or annon shell: interactive session; type help inside
|
|
140
99
|
shops [--search <q>] [--limit <n>] [--offset <n>]
|
|
141
100
|
shop add <url> [--id <alias>]
|
|
142
101
|
catalog [--shop <id>] [--search <q>] [--limit <n>] [--offset <n>]
|
|
102
|
+
init [--backup-secret-file <file>] [--state-dir <dir>] [--config <file>]
|
|
143
103
|
login [--wallet evm|near] [--no-open]
|
|
144
104
|
init --agent --daily-budget <USDC> [--recovery <kind:id>] [--key-env <name> | --keychain-entry <name> | --new-key | --signer <https origin> (--credential-env <name> | --credential-keychain <name>)]
|
|
145
105
|
share <file> --price <USDC> [--shop <id>] [--title <title>] [--description <text>] [--listing-storage <NEAR>] [--storage-budget <NEAR>]
|
|
@@ -148,54 +108,86 @@ sell <file> --resume <file-id> [--output <PAY2>]
|
|
|
148
108
|
fund <file-id> <NEAR>
|
|
149
109
|
budget <file-id>
|
|
150
110
|
sell <file> [--storage-budget <NEAR>] --price <USDC> --split <kind:id:bps,...> [--base] [--moderator <account>] [--output <PAY2>]
|
|
151
|
-
buy <url|offer.json> [--route near|base|1click] [--origin <configured name>] [--from-chain <chain>] [--from-asset <1Click asset>] [--refund-to <address>] [--recovery <kind:id>] [--max-price <USDC> or configured cap] [--url <expected resource for offer.json>] [--moderator <trusted account>]
|
|
152
|
-
open
|
|
111
|
+
buy <url|offer.json|catalog row number --shop <id> [--search <q>]> [--open <file>] [--route near|base|1click] [--origin <configured name>] [--from-chain <chain>] [--from-asset <1Click asset>] [--refund-to <address>] [--recovery <kind:id>] [--max-price <USDC> or configured cap] [--url <expected resource for offer.json>] [--moderator <trusted account>]
|
|
112
|
+
open [purchase-id|id prefix|backup|PAY2] [--purchase <id|id prefix|backup>] [--file <PAY2> | --url <url>] --output <file>
|
|
153
113
|
recover --recovery <kind:id> [--purchase <id>]
|
|
154
114
|
stop <file-id>
|
|
155
115
|
verify <PAY2|url>
|
|
156
|
-
status
|
|
116
|
+
status [purchase-id|id prefix]
|
|
117
|
+
status and open without an id use this contract's latest purchase; an id prefix needs 8 or more
|
|
118
|
+
hex digits and a purchase saved for this contract and network.
|
|
157
119
|
Common: --json --config <file> --contract-id <account> --near-rpc <url> --base-rpc <url>
|
|
158
120
|
--network <near:mainnet> --state-dir <dir> --relayer <url> --trust-contract
|
|
159
121
|
--wallet evm|solana|near --evm-key-file <file> --solana-key-file <file> --near-key-file <file>
|
|
160
122
|
--near-account <account> --near-public-key <ed25519:key> --backup-secret-file <file>
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
123
|
+
Backup secret: run init once; it creates ~/.config/annon/backup-secret (owner-only) and never
|
|
124
|
+
overwrites. --backup-secret-file or ANNON_BACKUP_SECRET_FILE, then ANNON_BACKUP_SECRET, override it.
|
|
125
|
+
Secrets: env ANNON_EVM_PRIVATE_KEY, ANNON_SOLANA_KEYPAIR, ANNON_NEAR_CREDENTIALS; or explicitly
|
|
126
|
+
named files. See packages/annon/CLI.md. --version prints the installed version.
|
|
127
|
+
Exit: 0 success (buy may need deposit), 1 operation failed, 2 arguments/config, 3 trust required, 4 purchase pending or uncertain.
|
|
164
128
|
`;
|
|
165
|
-
|
|
129
|
+
/** 4: not settled. A pending status, or a buy whose payment may be on chain or is not accepted yet. */
|
|
130
|
+
function exitCode(input, result) {
|
|
131
|
+
if (!result)
|
|
132
|
+
return 0;
|
|
133
|
+
if (input.command === 'status' && result.state === 'pending')
|
|
134
|
+
return 4;
|
|
135
|
+
if (input.command !== 'buy')
|
|
136
|
+
return 0;
|
|
137
|
+
// A payment that may be on chain outranks a failed status check or open after it.
|
|
138
|
+
if (result.state === 'uncertain')
|
|
139
|
+
return 4;
|
|
140
|
+
const failed = (result.openError ?? result.statusError);
|
|
141
|
+
if (failed)
|
|
142
|
+
return failed.exitCode;
|
|
143
|
+
return result.state === 'uncertain' || result.settlement === 'pending' ? 4 : 0;
|
|
144
|
+
}
|
|
145
|
+
/** The script contract: shop rules as text in pretty mode, then one JSON envelope. */
|
|
146
|
+
function writeJson(input, result, compact, write) {
|
|
147
|
+
if (!compact && ['shops', 'catalog', 'share'].includes(input.command)) {
|
|
148
|
+
const show = (value) => {
|
|
149
|
+
const r = value;
|
|
150
|
+
const rules = r.info?.rules ?? r.rules;
|
|
151
|
+
if (rules)
|
|
152
|
+
write(`Shop rules (${r.shop?.manifest ?? r.manifest}):\n${rules.text}`);
|
|
153
|
+
};
|
|
154
|
+
show(result);
|
|
155
|
+
const items = result.items;
|
|
156
|
+
if (Array.isArray(items))
|
|
157
|
+
for (const item of items)
|
|
158
|
+
show(item);
|
|
159
|
+
}
|
|
160
|
+
write(JSON.stringify({ ok: true, result }, null, compact ? undefined : 2));
|
|
161
|
+
}
|
|
162
|
+
export async function runCli(argv, execute, write, options = {}) {
|
|
166
163
|
const json = argv.includes('--json') || argv.includes('--json=true');
|
|
164
|
+
const human = options.human === true && !json;
|
|
167
165
|
if (argv.length === 0 || argv.includes('--help')) {
|
|
168
166
|
write(HELP);
|
|
169
167
|
return 0;
|
|
170
168
|
}
|
|
169
|
+
// Answered before any config, agent policy, wallet, network or backup-secret step.
|
|
170
|
+
if (argv.some((a) => versionFlags.includes(a)) &&
|
|
171
|
+
argv.every((a) => [...versionFlags, '--json'].includes(a))) {
|
|
172
|
+
write(json ? JSON.stringify({ ok: true, result: { version } }) : version);
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
171
175
|
try {
|
|
176
|
+
if (argv[0] === 'shell')
|
|
177
|
+
throw new AdapterError('invalid_arguments', 'The interactive session needs a terminal, takes no options and is not available in agent mode.', 2);
|
|
172
178
|
const input = parseCli(argv);
|
|
173
|
-
const result = await
|
|
174
|
-
if (
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
write(`Shop rules (${r.shop?.manifest ?? r.manifest}):\n${rules.text}`);
|
|
180
|
-
};
|
|
181
|
-
show(result);
|
|
182
|
-
const items = result.items;
|
|
183
|
-
if (Array.isArray(items))
|
|
184
|
-
for (const item of items)
|
|
185
|
-
show(item);
|
|
186
|
-
}
|
|
187
|
-
write(JSON.stringify({ ok: true, result }, null, json ? undefined : 2));
|
|
188
|
-
return input.command === 'status' &&
|
|
189
|
-
result &&
|
|
190
|
-
typeof result === 'object' &&
|
|
191
|
-
'state' in result &&
|
|
192
|
-
result.state === 'pending'
|
|
193
|
-
? 4
|
|
194
|
-
: 0;
|
|
179
|
+
const result = await buyFlow(input, execute, human, options);
|
|
180
|
+
if (human)
|
|
181
|
+
write(humanResult(input, result));
|
|
182
|
+
else
|
|
183
|
+
writeJson(input, result, json, write);
|
|
184
|
+
return exitCode(input, result);
|
|
195
185
|
}
|
|
196
186
|
catch (error) {
|
|
197
187
|
const safe = publicError(error);
|
|
198
|
-
write(
|
|
188
|
+
write(human
|
|
189
|
+
? humanError(safe)
|
|
190
|
+
: JSON.stringify({ ok: false, error: safe }, null, json ? undefined : 2));
|
|
199
191
|
return safe.exitCode;
|
|
200
192
|
}
|
|
201
193
|
}
|
|
@@ -65,7 +65,7 @@ async function pendingSetup(ctx, purchaseId) {
|
|
|
65
65
|
return {
|
|
66
66
|
purchaseId,
|
|
67
67
|
state: 'pending',
|
|
68
|
-
next: 'Check the payment
|
|
68
|
+
next: 'Check the payment. To resume Base unlock, keep the saved state readable and restore its matching backup secret (or select its existing copy with ANNON_BACKUP_SECRET_FILE). 1Click SUCCESS alone is not acceptance.',
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
/** Loads the command's wallet once; later calls reuse it. */
|
package/dist/node/config.d.ts
CHANGED
|
@@ -32,6 +32,10 @@ export interface Config extends DiscoveryConfig {
|
|
|
32
32
|
bps: number;
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
+
/** Human default for saved records and backups; agent and explicit settings override it. */
|
|
36
|
+
export declare const defaultStateDir: () => string;
|
|
35
37
|
export declare function loadConfig(o: Input, env: Environment, login?: boolean, forcedAgent?: boolean): Promise<Config>;
|
|
36
38
|
export type TrustPrompt = (description: string) => Promise<boolean>;
|
|
39
|
+
/** Trust is recorded per network, NEAR RPC and contract. */
|
|
40
|
+
export declare const trustId: (c: Config) => string;
|
|
37
41
|
export declare function trustContract(c: Config, explicit: boolean, prompt?: TrustPrompt): Promise<void>;
|
package/dist/node/config.js
CHANGED
|
@@ -8,6 +8,8 @@ import { validateDiscoveryConfig } from '../shops/discovery.js';
|
|
|
8
8
|
import { agentPolicy, configPath, validateSigner } from './agent.js';
|
|
9
9
|
import { AdapterError } from './errors.js';
|
|
10
10
|
import { resourceAllowed } from './resources.js';
|
|
11
|
+
/** Human default for saved records and backups; agent and explicit settings override it. */
|
|
12
|
+
export const defaultStateDir = () => join(homedir(), '.local', 'share', 'annon');
|
|
11
13
|
export async function loadConfig(o, env, login = false, forcedAgent = false) {
|
|
12
14
|
const pinnedAgent = await agentPolicy(o, env, forcedAgent);
|
|
13
15
|
try {
|
|
@@ -41,7 +43,7 @@ export async function loadConfig(o, env, login = false, forcedAgent = false) {
|
|
|
41
43
|
stateDir: o.stateDir ??
|
|
42
44
|
(agent ? undefined : env.ANNON_STATE_DIR) ??
|
|
43
45
|
file.stateDir ??
|
|
44
|
-
|
|
46
|
+
defaultStateDir(),
|
|
45
47
|
};
|
|
46
48
|
Object.assign(c, validateDiscoveryConfig(file));
|
|
47
49
|
const workDir = (env.ANNON_AGENT === 'true' ? env.ANNON_WORK_DIR : undefined) ?? file.workDir;
|
|
@@ -175,11 +177,13 @@ export async function loadConfig(o, env, login = false, forcedAgent = false) {
|
|
|
175
177
|
throw new AdapterError('invalid_config', 'Set contractId, network and nearRpc (plus baseRpc for Base operations) in the config file, environment or flags. Endpoints must use HTTPS (HTTP only on localhost).', 2);
|
|
176
178
|
}
|
|
177
179
|
}
|
|
180
|
+
/** Trust is recorded per network, NEAR RPC and contract. */
|
|
181
|
+
export const trustId = (c) => JSON.stringify([c.network, c.nearRpc, c.contractId]);
|
|
178
182
|
export async function trustContract(c, explicit, prompt) {
|
|
179
183
|
const dir = join(c.stateDir, 'trust');
|
|
180
184
|
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
181
185
|
const store = fileStore(dir);
|
|
182
|
-
const id =
|
|
186
|
+
const id = trustId(c);
|
|
183
187
|
if (await store.load(id))
|
|
184
188
|
return;
|
|
185
189
|
if (!explicit &&
|
|
@@ -46,6 +46,7 @@ import './origin.js';
|
|
|
46
46
|
import { buy, prepareBuy, verifyFile } from './buy-commands.js';
|
|
47
47
|
import { requestContext, } from './command-context.js';
|
|
48
48
|
import { open, recover, status, statusOnChain } from './purchase-commands.js';
|
|
49
|
+
import { withPurchaseRef } from './purchase-ref.js';
|
|
49
50
|
import { createSdk } from './relay-transport.js';
|
|
50
51
|
import { sell, share, stop } from './seller-commands.js';
|
|
51
52
|
import { fileBudget, fund, fundWithNearWallet } from './storage-commands.js';
|
|
@@ -59,6 +60,9 @@ function createCoreAdapter(env = process.env, d = {}) {
|
|
|
59
60
|
return async (o) => {
|
|
60
61
|
const c = await loadConfig(o, env, false, d.agent);
|
|
61
62
|
await trustContract(c, o.trustContract === true || c.trustContract === true, o.json || d.agent ? undefined : d.prompt);
|
|
63
|
+
// Agents always name the full purchase ID.
|
|
64
|
+
if ((o.command === 'status' || o.command === 'open') && !d.agent)
|
|
65
|
+
o = await withPurchaseRef(o, c);
|
|
62
66
|
const ctx = requestContext(env, d, fetcher, wait, o, c);
|
|
63
67
|
if (o.command === 'budget')
|
|
64
68
|
return fileBudget(ctx);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The approval page shell and its stylesheet. Both are static: the page script fills in every
|
|
3
|
+
* request value as text. The stylesheet is served from the page's own origin (`style-src 'self'`),
|
|
4
|
+
* with no inline styles, web fonts or other resources.
|
|
5
|
+
*/
|
|
6
|
+
export declare const LOGIN_PAGE_HTML = "<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta name=\"color-scheme\" content=\"light\"><title>Annon wallet approval</title><link rel=\"stylesheet\" href=\"/app.css\"><script src=\"/app.js\" defer></script></head><body><main class=\"card\">\n<header class=\"brand\"><svg class=\"mark\" viewBox=\"0 0 24 28\" width=\"22\" height=\"26\" aria-hidden=\"true\"><path d=\"M2 27V12a10 10 0 0 1 20 0v15h-5V12a5 5 0 0 0-10 0v15z\"/></svg><p class=\"eyebrow\">ANNON CLI</p></header>\n<h1 id=\"title\">Waiting for the CLI</h1>\n<section class=\"pair\" aria-labelledby=\"pair-label\"><p class=\"label\" id=\"pair-label\">Pairing code</p><p id=\"pair\" class=\"code\">------</p><p class=\"hint\">Check that your terminal shows the same code.</p></section>\n<div id=\"steps\" aria-label=\"Finished approvals\"></div>\n<p class=\"label\" id=\"step-state\">In progress</p>\n<section id=\"summary\" class=\"summary\" hidden><p id=\"amount\" class=\"amount\" hidden></p><dl id=\"rows\" class=\"rows\"></dl><div id=\"message-box\" class=\"message\" hidden><p id=\"message-label\" class=\"label\"></p><pre id=\"message\"></pre></div><ul id=\"warnings\" class=\"warnings\" hidden></ul><div id=\"terms-box\" class=\"terms\" hidden><p class=\"label\">Terms from the CLI</p><p class=\"hint\">For review. Your wallet signs only the request above.</p><dl id=\"terms\" class=\"rows\"></dl></div><p id=\"note\" class=\"note\"></p></section>\n<section class=\"act\"><div id=\"wallet-field\" class=\"field\" hidden><span class=\"label\" id=\"near-wallet-label\">NEAR wallet</span><div class=\"picker\"><div id=\"near-wallet\" class=\"picker-button\" role=\"combobox\" tabindex=\"0\" aria-haspopup=\"listbox\" aria-expanded=\"false\" aria-controls=\"near-wallet-list\" aria-labelledby=\"near-wallet-label near-wallet-value\"><span id=\"near-wallet-value\">Meteor Wallet</span></div><ul id=\"near-wallet-list\" class=\"picker-list\" role=\"listbox\" aria-labelledby=\"near-wallet-label\" hidden><li id=\"near-wallet-meteor\" class=\"option\" role=\"option\" aria-selected=\"true\">Meteor Wallet<span class=\"option-note\">Extension or web popup</span></li><li id=\"near-wallet-hot\" class=\"option\" role=\"option\" aria-selected=\"false\">HOT Wallet<span class=\"option-note\">Mainnet only</span></li></ul></div></div><div class=\"buttons\"><button id=\"approve\" class=\"primary\" disabled>Review in wallet</button><button id=\"cancel\" class=\"secondary\" hidden>Cancel</button></div><p id=\"status\" class=\"status\" role=\"status\" aria-live=\"polite\"></p></section>\n<details id=\"current-technical\" class=\"details\"><summary>Technical details</summary><pre id=\"action\"></pre></details>\n</main></body></html>";
|
|
7
|
+
export declare const LOGIN_PAGE_CSS = ":root{--paper:#FAF8F2;--alt:#F1EDE3;--ink:#111111;--soft:#2E2C28;--muted:#4A4741;--line:#D9D3C7;--red:#B3261E;--serif:Newsreader,\"Iowan Old Style\",Georgia,serif;--mono:\"IBM Plex Mono\",ui-monospace,\"SF Mono\",Menlo,monospace}\n*{box-sizing:border-box;border-radius:0}\nhtml{background:var(--paper)}\nbody{margin:0;background:var(--paper);color:var(--ink);font:16px/1.5 var(--serif);-webkit-text-size-adjust:100%}\n.card{max-width:640px;margin:0 auto;padding:40px 16px 56px}\n.brand{display:flex;align-items:center;gap:10px;margin-bottom:20px}\n.mark{fill:var(--ink);flex:none}\n.eyebrow,.label,dt,.details summary,button{font-family:var(--mono);letter-spacing:.08em;text-transform:uppercase;font-size:12px}\n.eyebrow{margin:0;color:var(--soft)}\nh1{font-weight:500;font-size:clamp(28px,6vw,36px);line-height:1.15;margin:0 0 24px;letter-spacing:-.01em}\n.label{margin:0 0 6px;color:var(--muted)}\n.hint{margin:6px 0 0;color:var(--muted);font-size:15px}\n.pair{border:1px solid var(--ink);background:var(--alt);padding:16px 20px;margin-bottom:28px}\n.code{margin:0;font:600 clamp(30px,9vw,40px)/1.1 var(--mono);letter-spacing:.18em;word-break:break-all}\n.step{border-top:1px solid var(--line);padding:12px 0;margin-bottom:16px}\n.step>summary{cursor:pointer;display:flex;gap:16px;align-items:baseline}\n.done-mark{color:#23643B;font:12px var(--mono);white-space:nowrap}\n.step[open]>summary{margin-bottom:16px}\n.summary{border-top:1px solid var(--line);padding-top:20px}\n.amount{margin:0 0 12px;font-size:clamp(30px,8vw,42px);line-height:1.1}\n.rows{margin:0}\n.row{display:grid;grid-template-columns:minmax(0,11em) minmax(0,1fr);gap:4px 16px;padding:9px 0;border-bottom:1px solid var(--line)}\ndt{color:var(--muted);padding-top:3px}\ndd{margin:0;color:var(--soft);overflow-wrap:anywhere}\ndd.mono{font:14px/1.5 var(--mono)}\n.message,.terms{margin-top:20px}\npre{margin:0;padding:14px 16px;background:var(--alt);border:1px solid var(--line);font:13px/1.55 var(--mono);color:var(--soft);white-space:pre-wrap;overflow-wrap:anywhere;max-height:22em;overflow:auto}\n.warnings{margin:20px 0 0;padding:12px 16px 12px 32px;border:1px solid var(--red);color:var(--red)}\n.note{margin:20px 0 0;color:var(--soft)}\n.act{margin-top:28px;border-top:1px solid var(--line);padding-top:20px}\n.field{margin:0 0 16px;display:flex;flex-wrap:wrap;align-items:center;gap:8px 16px}\n.picker{position:relative;flex:0 1 20em;min-width:0}\n.picker-button{display:flex;align-items:center;justify-content:space-between;gap:12px;background:var(--paper);color:var(--ink);border:1px solid var(--ink);padding:10px 12px;cursor:pointer;font:14px/1.4 var(--mono);user-select:none}\n.picker-button::after{content:\"\";width:0;height:0;border:5px solid transparent;border-top-color:var(--ink);border-bottom:0;flex:none}\n.picker-button[aria-expanded=\"true\"]::after{border-top:0;border-bottom:5px solid var(--ink)}\n.picker-button[aria-disabled=\"true\"]{opacity:.4;cursor:not-allowed}\n.picker-list{position:absolute;z-index:2;left:0;right:0;top:100%;margin:0;padding:0;list-style:none;background:var(--paper);border:1px solid var(--ink);border-top:0}\n.option{display:flex;justify-content:space-between;align-items:baseline;gap:12px;padding:10px 12px;cursor:pointer;font:14px/1.4 var(--mono);color:var(--soft)}\n.option+.option{border-top:1px solid var(--line)}\n.option.active{background:var(--alt);color:var(--ink)}\n.option[aria-selected=\"true\"]{color:var(--ink);font-weight:600}\n.option[aria-selected=\"true\"]::before{content:\"\";width:6px;height:6px;background:var(--ink);flex:none;align-self:center}\n.option[aria-disabled=\"true\"]{color:var(--muted);cursor:not-allowed;opacity:.6}\n.option-note{margin-left:auto;font-size:11px;font-weight:400;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}\n.buttons{display:flex;flex-wrap:wrap;gap:12px}\nbutton{padding:14px 22px;border:1px solid var(--ink);cursor:pointer}\n.primary{background:var(--ink);color:var(--paper)}\n.secondary{background:transparent;color:var(--ink)}\nbutton:disabled{cursor:not-allowed;opacity:.4}\nbutton:focus-visible,.picker-button:focus-visible,summary:focus-visible{outline:2px solid var(--red);outline-offset:2px}\n.status{min-height:1.5em;margin:16px 0 0;color:var(--soft)}\n.status.wait{color:var(--muted)}\n.status.ok{color:var(--ink);border-left:3px solid var(--ink);padding-left:12px}\n.status.error{color:var(--red);border-left:3px solid var(--red);padding-left:12px}\n.details{margin-top:32px;border-top:1px solid var(--line);padding-top:12px}\n.details summary{cursor:pointer;color:var(--muted);padding:6px 0}\n.details pre{margin-top:10px}\n[hidden]{display:none!important}\n@media (max-width:480px){.row{grid-template-columns:1fr}.card{padding-top:28px}button{flex:1 1 100%}.picker{flex-basis:100%}}\n";
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The approval page shell and its stylesheet. Both are static: the page script fills in every
|
|
3
|
+
* request value as text. The stylesheet is served from the page's own origin (`style-src 'self'`),
|
|
4
|
+
* with no inline styles, web fonts or other resources.
|
|
5
|
+
*/
|
|
6
|
+
/** A small arch: the Annon mark. Inline SVG, so it needs no image source. */
|
|
7
|
+
const MARK = '<svg class="mark" viewBox="0 0 24 28" width="22" height="26" aria-hidden="true"><path d="M2 27V12a10 10 0 0 1 20 0v15h-5V12a5 5 0 0 0-10 0v15z"/></svg>';
|
|
8
|
+
export const LOGIN_PAGE_HTML = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="color-scheme" content="light"><title>Annon wallet approval</title><link rel="stylesheet" href="/app.css"><script src="/app.js" defer></script></head><body><main class="card">
|
|
9
|
+
<header class="brand">${MARK}<p class="eyebrow">ANNON CLI</p></header>
|
|
10
|
+
<h1 id="title">Waiting for the CLI</h1>
|
|
11
|
+
<section class="pair" aria-labelledby="pair-label"><p class="label" id="pair-label">Pairing code</p><p id="pair" class="code">------</p><p class="hint">Check that your terminal shows the same code.</p></section>
|
|
12
|
+
<div id="steps" aria-label="Finished approvals"></div>
|
|
13
|
+
<p class="label" id="step-state">In progress</p>
|
|
14
|
+
<section id="summary" class="summary" hidden><p id="amount" class="amount" hidden></p><dl id="rows" class="rows"></dl><div id="message-box" class="message" hidden><p id="message-label" class="label"></p><pre id="message"></pre></div><ul id="warnings" class="warnings" hidden></ul><div id="terms-box" class="terms" hidden><p class="label">Terms from the CLI</p><p class="hint">For review. Your wallet signs only the request above.</p><dl id="terms" class="rows"></dl></div><p id="note" class="note"></p></section>
|
|
15
|
+
<section class="act"><div id="wallet-field" class="field" hidden><span class="label" id="near-wallet-label">NEAR wallet</span><div class="picker"><div id="near-wallet" class="picker-button" role="combobox" tabindex="0" aria-haspopup="listbox" aria-expanded="false" aria-controls="near-wallet-list" aria-labelledby="near-wallet-label near-wallet-value"><span id="near-wallet-value">Meteor Wallet</span></div><ul id="near-wallet-list" class="picker-list" role="listbox" aria-labelledby="near-wallet-label" hidden><li id="near-wallet-meteor" class="option" role="option" aria-selected="true">Meteor Wallet<span class="option-note">Extension or web popup</span></li><li id="near-wallet-hot" class="option" role="option" aria-selected="false">HOT Wallet<span class="option-note">Mainnet only</span></li></ul></div></div><div class="buttons"><button id="approve" class="primary" disabled>Review in wallet</button><button id="cancel" class="secondary" hidden>Cancel</button></div><p id="status" class="status" role="status" aria-live="polite"></p></section>
|
|
16
|
+
<details id="current-technical" class="details"><summary>Technical details</summary><pre id="action"></pre></details>
|
|
17
|
+
</main></body></html>`;
|
|
18
|
+
export const LOGIN_PAGE_CSS = `:root{--paper:#FAF8F2;--alt:#F1EDE3;--ink:#111111;--soft:#2E2C28;--muted:#4A4741;--line:#D9D3C7;--red:#B3261E;--serif:Newsreader,"Iowan Old Style",Georgia,serif;--mono:"IBM Plex Mono",ui-monospace,"SF Mono",Menlo,monospace}
|
|
19
|
+
*{box-sizing:border-box;border-radius:0}
|
|
20
|
+
html{background:var(--paper)}
|
|
21
|
+
body{margin:0;background:var(--paper);color:var(--ink);font:16px/1.5 var(--serif);-webkit-text-size-adjust:100%}
|
|
22
|
+
.card{max-width:640px;margin:0 auto;padding:40px 16px 56px}
|
|
23
|
+
.brand{display:flex;align-items:center;gap:10px;margin-bottom:20px}
|
|
24
|
+
.mark{fill:var(--ink);flex:none}
|
|
25
|
+
.eyebrow,.label,dt,.details summary,button{font-family:var(--mono);letter-spacing:.08em;text-transform:uppercase;font-size:12px}
|
|
26
|
+
.eyebrow{margin:0;color:var(--soft)}
|
|
27
|
+
h1{font-weight:500;font-size:clamp(28px,6vw,36px);line-height:1.15;margin:0 0 24px;letter-spacing:-.01em}
|
|
28
|
+
.label{margin:0 0 6px;color:var(--muted)}
|
|
29
|
+
.hint{margin:6px 0 0;color:var(--muted);font-size:15px}
|
|
30
|
+
.pair{border:1px solid var(--ink);background:var(--alt);padding:16px 20px;margin-bottom:28px}
|
|
31
|
+
.code{margin:0;font:600 clamp(30px,9vw,40px)/1.1 var(--mono);letter-spacing:.18em;word-break:break-all}
|
|
32
|
+
.step{border-top:1px solid var(--line);padding:12px 0;margin-bottom:16px}
|
|
33
|
+
.step>summary{cursor:pointer;display:flex;gap:16px;align-items:baseline}
|
|
34
|
+
.done-mark{color:#23643B;font:12px var(--mono);white-space:nowrap}
|
|
35
|
+
.step[open]>summary{margin-bottom:16px}
|
|
36
|
+
.summary{border-top:1px solid var(--line);padding-top:20px}
|
|
37
|
+
.amount{margin:0 0 12px;font-size:clamp(30px,8vw,42px);line-height:1.1}
|
|
38
|
+
.rows{margin:0}
|
|
39
|
+
.row{display:grid;grid-template-columns:minmax(0,11em) minmax(0,1fr);gap:4px 16px;padding:9px 0;border-bottom:1px solid var(--line)}
|
|
40
|
+
dt{color:var(--muted);padding-top:3px}
|
|
41
|
+
dd{margin:0;color:var(--soft);overflow-wrap:anywhere}
|
|
42
|
+
dd.mono{font:14px/1.5 var(--mono)}
|
|
43
|
+
.message,.terms{margin-top:20px}
|
|
44
|
+
pre{margin:0;padding:14px 16px;background:var(--alt);border:1px solid var(--line);font:13px/1.55 var(--mono);color:var(--soft);white-space:pre-wrap;overflow-wrap:anywhere;max-height:22em;overflow:auto}
|
|
45
|
+
.warnings{margin:20px 0 0;padding:12px 16px 12px 32px;border:1px solid var(--red);color:var(--red)}
|
|
46
|
+
.note{margin:20px 0 0;color:var(--soft)}
|
|
47
|
+
.act{margin-top:28px;border-top:1px solid var(--line);padding-top:20px}
|
|
48
|
+
.field{margin:0 0 16px;display:flex;flex-wrap:wrap;align-items:center;gap:8px 16px}
|
|
49
|
+
.picker{position:relative;flex:0 1 20em;min-width:0}
|
|
50
|
+
.picker-button{display:flex;align-items:center;justify-content:space-between;gap:12px;background:var(--paper);color:var(--ink);border:1px solid var(--ink);padding:10px 12px;cursor:pointer;font:14px/1.4 var(--mono);user-select:none}
|
|
51
|
+
.picker-button::after{content:"";width:0;height:0;border:5px solid transparent;border-top-color:var(--ink);border-bottom:0;flex:none}
|
|
52
|
+
.picker-button[aria-expanded="true"]::after{border-top:0;border-bottom:5px solid var(--ink)}
|
|
53
|
+
.picker-button[aria-disabled="true"]{opacity:.4;cursor:not-allowed}
|
|
54
|
+
.picker-list{position:absolute;z-index:2;left:0;right:0;top:100%;margin:0;padding:0;list-style:none;background:var(--paper);border:1px solid var(--ink);border-top:0}
|
|
55
|
+
.option{display:flex;justify-content:space-between;align-items:baseline;gap:12px;padding:10px 12px;cursor:pointer;font:14px/1.4 var(--mono);color:var(--soft)}
|
|
56
|
+
.option+.option{border-top:1px solid var(--line)}
|
|
57
|
+
.option.active{background:var(--alt);color:var(--ink)}
|
|
58
|
+
.option[aria-selected="true"]{color:var(--ink);font-weight:600}
|
|
59
|
+
.option[aria-selected="true"]::before{content:"";width:6px;height:6px;background:var(--ink);flex:none;align-self:center}
|
|
60
|
+
.option[aria-disabled="true"]{color:var(--muted);cursor:not-allowed;opacity:.6}
|
|
61
|
+
.option-note{margin-left:auto;font-size:11px;font-weight:400;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
|
|
62
|
+
.buttons{display:flex;flex-wrap:wrap;gap:12px}
|
|
63
|
+
button{padding:14px 22px;border:1px solid var(--ink);cursor:pointer}
|
|
64
|
+
.primary{background:var(--ink);color:var(--paper)}
|
|
65
|
+
.secondary{background:transparent;color:var(--ink)}
|
|
66
|
+
button:disabled{cursor:not-allowed;opacity:.4}
|
|
67
|
+
button:focus-visible,.picker-button:focus-visible,summary:focus-visible{outline:2px solid var(--red);outline-offset:2px}
|
|
68
|
+
.status{min-height:1.5em;margin:16px 0 0;color:var(--soft)}
|
|
69
|
+
.status.wait{color:var(--muted)}
|
|
70
|
+
.status.ok{color:var(--ink);border-left:3px solid var(--ink);padding-left:12px}
|
|
71
|
+
.status.error{color:var(--red);border-left:3px solid var(--red);padding-left:12px}
|
|
72
|
+
.details{margin-top:32px;border-top:1px solid var(--line);padding-top:12px}
|
|
73
|
+
.details summary{cursor:pointer;color:var(--muted);padding:6px 0}
|
|
74
|
+
.details pre{margin-top:10px}
|
|
75
|
+
[hidden]{display:none!important}
|
|
76
|
+
@media (max-width:480px){.row{grid-template-columns:1fr}.card{padding-top:28px}button{flex:1 1 100%}.picker{flex-basis:100%}}
|
|
77
|
+
`;
|
package/dist/node/main.js
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
import { createInterface } from 'node:readline/promises';
|
|
3
3
|
import { createAdapter } from './adapter.js';
|
|
4
4
|
import { runCli } from './cli.js';
|
|
5
|
+
import { createSession } from './shell/session.js';
|
|
6
|
+
import { humanOutput, sessionAllowed, welcome } from './shell/welcome.js';
|
|
7
|
+
const argv = process.argv.slice(2);
|
|
8
|
+
const notify = (message) => process.stderr.write(`${message}\n`);
|
|
9
|
+
if (await sessionAllowed(argv, process.stdin.isTTY === true && process.stdout.isTTY === true, process.env)) {
|
|
10
|
+
const session = createSession({
|
|
11
|
+
input: process.stdin,
|
|
12
|
+
output: process.stdout,
|
|
13
|
+
terminal: true,
|
|
14
|
+
welcome: await welcome(process.env),
|
|
15
|
+
adapter: (hooks) => createAdapter(process.env, hooks),
|
|
16
|
+
});
|
|
17
|
+
// A confirmed Ctrl-C leaves a command running; exiting ends it as it would end a one-shot run.
|
|
18
|
+
process.exit(await session.run());
|
|
19
|
+
}
|
|
5
20
|
const adapter = createAdapter(process.env, {
|
|
6
21
|
...(process.stdin.isTTY && process.stderr.isTTY
|
|
7
22
|
? {
|
|
@@ -16,6 +31,9 @@ const adapter = createAdapter(process.env, {
|
|
|
16
31
|
},
|
|
17
32
|
}
|
|
18
33
|
: {}),
|
|
19
|
-
notify
|
|
34
|
+
notify,
|
|
35
|
+
});
|
|
36
|
+
process.exitCode = await runCli(argv, adapter, (text) => process.stdout.write(`${text}\n`), {
|
|
37
|
+
human: await humanOutput(process.stdout.isTTY === true, process.env),
|
|
38
|
+
notify,
|
|
20
39
|
});
|
|
21
|
-
process.exitCode = await runCli(process.argv.slice(2), adapter, (text) => process.stdout.write(`${text}\n`));
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CatalogEntry, Shop, ShopFailure, ShopRules } from '../../shops/discovery.js';
|
|
2
|
+
import type { Input } from '../cli.js';
|
|
3
|
+
type Page<T> = {
|
|
4
|
+
items: T[];
|
|
5
|
+
total: number;
|
|
6
|
+
nextOffset: number | null;
|
|
7
|
+
};
|
|
8
|
+
export declare function shopsText(page: Page<Shop | ShopFailure>): string;
|
|
9
|
+
export declare const shopAddedText: (r: {
|
|
10
|
+
manifest: string;
|
|
11
|
+
id?: string;
|
|
12
|
+
trusted: boolean;
|
|
13
|
+
}) => string;
|
|
14
|
+
/** Row numbers count from the start of the listing, so `buy <n>` means the same row on any page. */
|
|
15
|
+
export declare const rowNumber: (input: Input, index: number) => number;
|
|
16
|
+
export declare function catalogText(input: Input, page: Page<CatalogEntry> & {
|
|
17
|
+
shop?: {
|
|
18
|
+
manifest: string;
|
|
19
|
+
name: string;
|
|
20
|
+
};
|
|
21
|
+
rules?: ShopRules;
|
|
22
|
+
}): string;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Human output for `shops`, `shop add` and `catalog`. */
|
|
2
|
+
import { usdc } from '../../settle-near/codec.js';
|
|
3
|
+
import { cleanText, short, table, truncate } from './text.js';
|
|
4
|
+
const more = (page) => page.nextOffset === null ? [] : [`More: add --offset ${page.nextOffset}`];
|
|
5
|
+
export function shopsText(page) {
|
|
6
|
+
if (page.items.length === 0)
|
|
7
|
+
return 'No shops configured. Add one with: annon shop add <shop url> --id <name>';
|
|
8
|
+
return [
|
|
9
|
+
`Shops (${page.total}):`,
|
|
10
|
+
table(page.items.map((shop) => [
|
|
11
|
+
` ${cleanText(shop.id ?? '-')}`,
|
|
12
|
+
'info' in shop ? cleanText(shop.info.name) : `unavailable (${shop.error})`,
|
|
13
|
+
'info' in shop ? shop.info.shopUrl : shop.manifest,
|
|
14
|
+
shop.trusted ? 'trusted profile' : '',
|
|
15
|
+
])),
|
|
16
|
+
...more(page),
|
|
17
|
+
].join('\n');
|
|
18
|
+
}
|
|
19
|
+
export const shopAddedText = (r) => `Shop added${r.id ? ` as ${cleanText(r.id)}` : ''}: ${r.manifest}\nList its files with: annon catalog --shop ${cleanText(r.id ?? r.manifest)}`;
|
|
20
|
+
/** Row numbers count from the start of the listing, so `buy <n>` means the same row on any page. */
|
|
21
|
+
export const rowNumber = (input, index) => (input.offset ?? 0) + index + 1;
|
|
22
|
+
const routes = (e) => e.takenDown
|
|
23
|
+
? 'taken down'
|
|
24
|
+
: e.salesStopped
|
|
25
|
+
? 'sales stopped'
|
|
26
|
+
: ['near', ...(e.terms.base ? ['base'] : []), '1click'].join(', ');
|
|
27
|
+
const day = (seconds) => seconds === null ? '-' : new Date(seconds * 1000).toISOString().slice(0, 10);
|
|
28
|
+
/** The shop's rules in one line; the full text stays in `--json` and is shown by `share`. */
|
|
29
|
+
const rulesLine = (shop, rules) => `Shop rules${shop ? ` (${cleanText(shop.name)})` : ''}: ${truncate(cleanText(rules.text).replace(/\s+/g, ' ').trim(), 160)}`;
|
|
30
|
+
export function catalogText(input, page) {
|
|
31
|
+
if (page.items.length === 0)
|
|
32
|
+
return page.total === 0 ? 'No files.' : 'No files on this page.';
|
|
33
|
+
const first = rowNumber(input, 0);
|
|
34
|
+
const shop = input.shop === undefined ? '' : ` --shop ${input.shop}`;
|
|
35
|
+
return [
|
|
36
|
+
`Files ${first} to ${first + page.items.length - 1} of ${page.total}:`,
|
|
37
|
+
table([
|
|
38
|
+
[' #', 'Title', 'Price', 'Routes', 'Seller', 'Expires'],
|
|
39
|
+
...page.items.map((e, i) => [
|
|
40
|
+
String(rowNumber(input, i)).padStart(2),
|
|
41
|
+
truncate(cleanText(e.title || e.name), 40),
|
|
42
|
+
`${usdc(e.terms.near.price)} USDC`,
|
|
43
|
+
routes(e),
|
|
44
|
+
short(cleanText(`${e.terms.seller.kind}:${e.terms.seller.id}`)),
|
|
45
|
+
day(e.expiresAt),
|
|
46
|
+
]),
|
|
47
|
+
]),
|
|
48
|
+
...(page.rules ? [rulesLine(page.shop, page.rules)] : []),
|
|
49
|
+
...more(page),
|
|
50
|
+
`Buy a row with: annon buy <#>${shop}${input.search === undefined ? '' : ` --search ${JSON.stringify(input.search)}`}`,
|
|
51
|
+
].join('\n');
|
|
52
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a person reads in a terminal in place of the JSON envelope. `--json`, pipes and agent mode
|
|
3
|
+
* never come here, so scripts keep the envelope.
|
|
4
|
+
*/
|
|
5
|
+
import type { PublicError } from '../../errors.js';
|
|
6
|
+
import type { Input } from '../cli.js';
|
|
7
|
+
type Result = Record<string, unknown>;
|
|
8
|
+
export declare function humanResult(input: Input, result: Result | undefined): string;
|
|
9
|
+
export declare function humanError(error: PublicError): string;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { catalogText, shopAddedText, shopsText } from './discovery.js';
|
|
2
|
+
import { buyText, failure, loginText, openText, recoverText, statusText } from './purchase.js';
|
|
3
|
+
import { cleanText, fields } from './text.js';
|
|
4
|
+
/** A seller accepts the shop's rules by sharing, so `share` shows them whole, with their source. */
|
|
5
|
+
function shareText(r) {
|
|
6
|
+
const { rules, shop, ...rest } = r;
|
|
7
|
+
return [
|
|
8
|
+
...(rules ? [`Shop rules (${shop?.manifest}):\n${cleanText(rules.text, true)}`] : []),
|
|
9
|
+
fields(rest),
|
|
10
|
+
].join('\n');
|
|
11
|
+
}
|
|
12
|
+
export function humanResult(input, result) {
|
|
13
|
+
const r = result ?? {};
|
|
14
|
+
switch (input.command) {
|
|
15
|
+
case 'login':
|
|
16
|
+
return loginText(r);
|
|
17
|
+
case 'shops':
|
|
18
|
+
return shopsText(r);
|
|
19
|
+
case 'shop':
|
|
20
|
+
return shopAddedText(r);
|
|
21
|
+
case 'catalog':
|
|
22
|
+
return catalogText(input, r);
|
|
23
|
+
case 'buy':
|
|
24
|
+
return buyText(r);
|
|
25
|
+
case 'status':
|
|
26
|
+
return statusText(r);
|
|
27
|
+
case 'open':
|
|
28
|
+
return openText(r);
|
|
29
|
+
case 'recover':
|
|
30
|
+
return recoverText(r);
|
|
31
|
+
case 'share':
|
|
32
|
+
return shareText(r);
|
|
33
|
+
case 'verify':
|
|
34
|
+
return `Verified file ${r.fileId} (${r.checked}) against ${r.contractId}.`;
|
|
35
|
+
default:
|
|
36
|
+
return fields(r);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function humanError(error) {
|
|
40
|
+
const { code: _code, message: _message, exitCode: _exitCode, retryAfterSeconds, ...facts } = error;
|
|
41
|
+
return [
|
|
42
|
+
`Error: ${failure(error)}`,
|
|
43
|
+
...(Object.keys(facts).length > 0 ? [fields(facts)] : []),
|
|
44
|
+
...(retryAfterSeconds === undefined ? [] : [`Try again in ${retryAfterSeconds} seconds.`]),
|
|
45
|
+
].join('\n');
|
|
46
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Human output for `login`, `buy`, `status`, `open` and `recover`. */
|
|
2
|
+
import type { PublicError } from '../../errors.js';
|
|
3
|
+
type Result = Record<string, unknown>;
|
|
4
|
+
export declare const loginText: (r: Result) => string;
|
|
5
|
+
export declare const failure: (e: PublicError) => string;
|
|
6
|
+
export declare function buyText(r: Result): string;
|
|
7
|
+
export declare function statusText(r: Result): string;
|
|
8
|
+
export declare const openText: (r: Result) => string;
|
|
9
|
+
export declare function recoverText(r: Result): string;
|
|
10
|
+
export {};
|