@bolloon/bolloon-agent 0.5.2 → 0.5.4

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 (80) hide show
  1. package/README.md +4 -4
  2. package/dist/agents/chain/transfer-classify.js +99 -0
  3. package/dist/agents/chain/transfer-index.js +354 -0
  4. package/dist/agents/identity/address-binding.js +613 -0
  5. package/dist/agents/network-pulse.js +706 -27
  6. package/dist/agents/pi-sdk-tools.js +67 -15
  7. package/dist/agents/pi-sdk.js +7 -1
  8. package/dist/agents/session-store.js +144 -3
  9. package/dist/agents/wallet-tools.js +63 -0
  10. package/dist/agents/x402/direct-payment.js +689 -0
  11. package/dist/agents/x402/order-identity.js +408 -0
  12. package/dist/agents/x402/paid-info-store.js +17 -0
  13. package/dist/agents/x402/seller-signing.js +614 -0
  14. package/dist/agents/x402/seller-summary.js +398 -0
  15. package/dist/cli/identity-command.js +327 -2
  16. package/dist/cli/ink-app.js +79 -27
  17. package/dist/cli/input-history.js +119 -0
  18. package/dist/cli/loading-tui.js +66 -6
  19. package/dist/cli/log-gate.js +39 -1
  20. package/dist/cli/protocol-envelope.js +4 -0
  21. package/dist/cli/session-summary.js +129 -0
  22. package/dist/cli/session-tree.js +141 -0
  23. package/dist/cli/status-segments.js +70 -0
  24. package/dist/cli/stores.js +16 -0
  25. package/dist/cli/update-commands.js +41 -25
  26. package/dist/cli/wallet-store.js +159 -0
  27. package/dist/cli/wallet-table.js +46 -0
  28. package/dist/cli/x402-buyer-command.js +463 -0
  29. package/dist/cli/x402-seller-command.js +289 -0
  30. package/dist/cli-entry.js +43 -5
  31. package/dist/electron-build/agents/chain/transfer-classify.js +106 -0
  32. package/dist/electron-build/agents/chain/transfer-classify.js.map +1 -0
  33. package/dist/electron-build/agents/chain/transfer-index.js +400 -0
  34. package/dist/electron-build/agents/chain/transfer-index.js.map +1 -0
  35. package/dist/electron-build/agents/identity/address-binding.js +672 -0
  36. package/dist/electron-build/agents/identity/address-binding.js.map +1 -0
  37. package/dist/electron-build/agents/network-pulse.js +714 -28
  38. package/dist/electron-build/agents/network-pulse.js.map +1 -1
  39. package/dist/electron-build/agents/pi-sdk-tools.js +68 -15
  40. package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
  41. package/dist/electron-build/agents/pi-sdk.js +7 -1
  42. package/dist/electron-build/agents/pi-sdk.js.map +1 -1
  43. package/dist/electron-build/agents/session-store.js +144 -3
  44. package/dist/electron-build/agents/session-store.js.map +1 -1
  45. package/dist/electron-build/agents/wallet-tools.js +102 -0
  46. package/dist/electron-build/agents/wallet-tools.js.map +1 -0
  47. package/dist/electron-build/agents/x402/direct-payment.js +744 -0
  48. package/dist/electron-build/agents/x402/direct-payment.js.map +1 -0
  49. package/dist/electron-build/agents/x402/order-identity.js +458 -0
  50. package/dist/electron-build/agents/x402/order-identity.js.map +1 -0
  51. package/dist/electron-build/agents/x402/paid-info-protocol.js.map +1 -1
  52. package/dist/electron-build/agents/x402/paid-info-store.js +18 -0
  53. package/dist/electron-build/agents/x402/paid-info-store.js.map +1 -1
  54. package/dist/electron-build/agents/x402/seller-signing.js +682 -0
  55. package/dist/electron-build/agents/x402/seller-signing.js.map +1 -0
  56. package/dist/electron-build/cli/wallet-store.js +200 -0
  57. package/dist/electron-build/cli/wallet-store.js.map +1 -0
  58. package/dist/electron-build/llm/pi-ai.js +25 -0
  59. package/dist/electron-build/llm/pi-ai.js.map +1 -1
  60. package/dist/electron-build/utils/auto-update.js +2 -2
  61. package/dist/electron-build/utils/auto-update.js.map +1 -1
  62. package/dist/electron-build/utils/runtime-bootstrap.js +1 -1
  63. package/dist/electron-build/utils/runtime-bootstrap.js.map +1 -1
  64. package/dist/electron-build/utils/update-manager.js +3 -3
  65. package/dist/electron-build/utils/update-manager.js.map +1 -1
  66. package/dist/electron-build/utils/version-identity.js +1 -1
  67. package/dist/electron-build/utils/version-identity.js.map +1 -1
  68. package/dist/electron-build/web/routes-x402-info.js +28 -1
  69. package/dist/electron-build/web/routes-x402-info.js.map +1 -1
  70. package/dist/index.js +829 -104
  71. package/dist/llm/pi-ai.js +23 -0
  72. package/dist/utils/auto-update.js +2 -2
  73. package/dist/utils/runtime-bootstrap.js +1 -1
  74. package/dist/utils/update-health.js +2 -2
  75. package/dist/utils/update-manager.js +3 -3
  76. package/dist/utils/version-identity.js +1 -1
  77. package/dist/web/bolloon-web.json +3 -3
  78. package/dist/web/routes-x402-info.js +28 -1
  79. package/dist/web/sw.js +1 -1
  80. package/package.json +2 -1
@@ -0,0 +1,289 @@
1
+ /**
2
+ * x402-seller-command.ts — `bolloon x402 pending list|show|sign|auth-init|key`
3
+ *
4
+ * 薄包装: 业务逻辑一律在 `agents/x402/seller-signing.ts`, 本文件只做
5
+ * ① 参数解析 ② 人类可读输出 ③ 失败时的可操作提示。
6
+ *
7
+ * 红线 (与 docs/wiki/x402-seller-signing.md 一致):
8
+ * · 私钥**只在本进程内存里**用于签名; 绝不打印、绝不落日志、绝不进命令行、绝不上服务器
9
+ * · 共享密钥只出现在 0600 文件与请求头里; 输出里只有 keyId 与指纹
10
+ * · 签名只走既有 `buildSignedEnvelope` → `ed25519Sign` (不新写一套签名实现)
11
+ * · 卖方不在线 ⇒ 如实报 "已付款待签名", 不假装能交付
12
+ */
13
+ import { sellerApiPaths, sellerAuthPath, sellerAuthFingerprint, generateSellerAuth, loadSellerAuth, saveSellerAuth, sellerHome, sellerClientRequest, pendingView, signPending, resolveSellerKey, paidInfoRetrievePath, } from '../agents/x402/seller-signing.js';
14
+ import { canonicalize, ed25519Verify } from '../agents/x402/paid-info-protocol.js';
15
+ const USAGE = `
16
+ bolloon x402 pending —— 卖方本机签名交付 (私钥不离开本机)
17
+
18
+ bolloon x402 pending list [--endpoint <url>] [--status awaiting_signature] [--json]
19
+ bolloon x402 pending show <pendingId> [--endpoint <url>] [--json]
20
+ bolloon x402 pending sign <pendingId> [--endpoint <url>] [--agent <agentId>] [--json]
21
+ bolloon x402 pending auth-init [--endpoint <url>] [--force]
22
+ bolloon x402 pending key [--agent <agentId>] [--json]
23
+
24
+ auth-init 生成 0600 共享密钥 (~/.bolloon/x402-seller-auth.json), 打印 keyId/指纹/安装提示
25
+ key 打印本机卖方公钥 (给服务器钉住; **只有公钥**, 私钥永不出机)
26
+ list/show 带 HMAC 认证拉取待办 (服务器只持公钥, 签不了信封 ⇒ 必须本机签)
27
+ sign 读本机内容 → 既有 ed25519Sign 签信封 → 回传服务器 → 本机自检验签
28
+
29
+ 选项: --endpoint <url> (也可在 auth 文件里存 endpoint) · --agent <agentId> · --json
30
+ `;
31
+ function flagValue(args, name) {
32
+ const i = args.indexOf(name);
33
+ if (i >= 0 && i + 1 < args.length && !args[i + 1].startsWith('--'))
34
+ return args[i + 1];
35
+ const eq = args.find((a) => a.startsWith(`${name}=`));
36
+ return eq ? eq.slice(name.length + 1) : undefined;
37
+ }
38
+ function firstPositional(args) {
39
+ for (let i = 0; i < args.length; i++) {
40
+ const a = args[i];
41
+ if (a.startsWith('--')) {
42
+ if (!a.includes('='))
43
+ i++;
44
+ continue;
45
+ }
46
+ return a;
47
+ }
48
+ return undefined;
49
+ }
50
+ /** endpoint: --endpoint > auth 文件 > env */
51
+ async function resolveEndpoint(args) {
52
+ const explicit = flagValue(args, '--endpoint') || process.env.BOLLOON_X402_SELLER_ENDPOINT;
53
+ if (explicit)
54
+ return explicit;
55
+ const cfg = await loadSellerAuth();
56
+ if (cfg?.endpoint)
57
+ return cfg.endpoint;
58
+ throw new Error('没有端点: 给 --endpoint <url>, 或先跑 `bolloon x402 pending auth-init --endpoint <url>` 把它存进 auth 文件');
59
+ }
60
+ function short(s, n = 20) {
61
+ const v = String(s ?? '');
62
+ return v.length > n ? `${v.slice(0, n)}…` : v;
63
+ }
64
+ // ─────────────────────────────────────────────────────────── 子命令
65
+ async function cmdList(args) {
66
+ const json = args.includes('--json');
67
+ const endpoint = await resolveEndpoint(args);
68
+ const auth = await loadSellerAuth();
69
+ if (!auth) {
70
+ console.error('❌ 本机没有共享密钥 (~/.bolloon/x402-seller-auth.json) — 先跑 `bolloon x402 pending auth-init --endpoint <url>`');
71
+ return 1;
72
+ }
73
+ const status = flagValue(args, '--status');
74
+ const urlPath = status ? `${sellerApiPaths().list}?status=${encodeURIComponent(status)}` : sellerApiPaths().list;
75
+ // 被签的只有 pathname (查询串不参与)
76
+ const r = await sellerClientRequest({ endpoint, auth }, { method: 'GET', path: sellerApiPaths().list, urlPath });
77
+ if (!r.ok) {
78
+ console.error(`❌ 拉取失败 (HTTP ${r.status}): ${r.json?.code || ''} ${r.json?.error || r.text.slice(0, 200)}`);
79
+ if (r.json?.hint)
80
+ console.error(` ${r.json.hint}`);
81
+ return 1;
82
+ }
83
+ if (json) {
84
+ console.log(JSON.stringify(r.json, null, 2));
85
+ return 0;
86
+ }
87
+ const items = r.json.pending || [];
88
+ console.log(`卖方待办 (${endpoint}) — 共 ${r.json.total ?? items.length} 条, 待签名 ${r.json.awaitingSignature ?? '?'} 条`);
89
+ if (!items.length) {
90
+ console.log(' (空) 没有待签名的付款 —— 卖方不在线时买方只会拿到「已付款待签名」');
91
+ return 0;
92
+ }
93
+ for (const p of items) {
94
+ const v = pendingView(p);
95
+ console.log(` ${v.pendingId} [${v.status}] ${v.itemId} ${v.payment.amount} ${v.payment.currency}@${v.payment.network} ${v.createdAt}`);
96
+ console.log(` 内容哈希 ${v.contentHash} · 付款凭据哈希 ${v.payment.receiptHash} · mode=${v.payment.mode}`);
97
+ }
98
+ console.log(' 下一步: bolloon x402 pending show <pendingId> → sign <pendingId>');
99
+ return 0;
100
+ }
101
+ async function cmdShow(args) {
102
+ const json = args.includes('--json');
103
+ const id = firstPositional(args);
104
+ if (!id) {
105
+ console.error('用法: bolloon x402 pending show <pendingId>');
106
+ return 1;
107
+ }
108
+ const endpoint = await resolveEndpoint(args);
109
+ const auth = await loadSellerAuth();
110
+ if (!auth) {
111
+ console.error('❌ 本机没有共享密钥 — 先跑 auth-init');
112
+ return 1;
113
+ }
114
+ const r = await sellerClientRequest({ endpoint, auth }, { method: 'GET', path: sellerApiPaths().show(id) });
115
+ if (!r.ok) {
116
+ console.error(`❌ 取不到 (HTTP ${r.status}): ${r.json?.code || ''} ${r.json?.error || r.text.slice(0, 200)}`);
117
+ return 1;
118
+ }
119
+ if (json) {
120
+ console.log(JSON.stringify(r.json, null, 2));
121
+ return 0;
122
+ }
123
+ const p = r.json.pending;
124
+ console.log(`待办 ${p.pendingId} [${p.status}]`);
125
+ console.log(` item ${p.itemId} (${p.title})`);
126
+ console.log(` 卖方 DID ${p.providerDid}`);
127
+ console.log(` 内容哈希 ${p.contentHash}${p.contentCid ? ` cid=${p.contentCid}` : ''}`);
128
+ console.log(` 来源声明 kind=${p.source?.kind} refs=${(p.source?.refs || []).length}`);
129
+ console.log(` 价格 ${p.price.amount} ${p.price.currency}@${p.price.network} → ${p.price.payTo}`);
130
+ console.log(` 付款 mode=${p.payment.mode} ${p.payment.amount} ${p.payment.currency}@${p.payment.network}`);
131
+ console.log(` 付款凭据哈希 ${p.payment.receiptHash} ← 签名会绑定它`);
132
+ console.log(` 付款凭据原文 ${short(p.payment.receipt, 48)} (回执原文, 本机要用它重建哈希)`);
133
+ if (p.payment.txHash)
134
+ console.log(` 链上 txHash ${p.payment.txHash}`);
135
+ if (p.payment.payer)
136
+ console.log(` 付款方 ${p.payment.payer}`);
137
+ console.log(` 创建/过期 ${p.createdAt} → ${p.expiresAt}`);
138
+ if (p.envelopeHash)
139
+ console.log(` 已签信封哈希 ${p.envelopeHash}`);
140
+ console.log(` 下一步: bolloon x402 pending sign ${p.pendingId}`);
141
+ return 0;
142
+ }
143
+ async function cmdSign(args) {
144
+ const json = args.includes('--json');
145
+ const id = firstPositional(args);
146
+ if (!id) {
147
+ console.error('用法: bolloon x402 pending sign <pendingId> [--agent <agentId>]');
148
+ return 1;
149
+ }
150
+ const endpoint = await resolveEndpoint(args);
151
+ const auth = await loadSellerAuth();
152
+ if (!auth) {
153
+ console.error('❌ 本机没有共享密钥 — 先跑 auth-init');
154
+ return 1;
155
+ }
156
+ // ① 拉这条待办 (服务器是待办的事实来源)
157
+ const r = await sellerClientRequest({ endpoint, auth }, { method: 'GET', path: sellerApiPaths().show(id) });
158
+ if (!r.ok) {
159
+ console.error(`❌ 取不到待办 (HTTP ${r.status}): ${r.json?.code || ''} ${r.json?.error || r.text.slice(0, 200)}`);
160
+ return 1;
161
+ }
162
+ const pending = r.json.pending;
163
+ if (pending.status !== 'awaiting_signature') {
164
+ console.error(`❌ 这条待办状态是 ${pending.status} — 不能签 (已签过/已过期都不许重签)`);
165
+ return 1;
166
+ }
167
+ // ② 本机内容 (签名要绑内容哈希 ⇒ 没有内容就签不出来, 如实报错)
168
+ const { getStoredInfo } = await import('../agents/x402/paid-info-store.js');
169
+ const stored = await getStoredInfo(pending.itemId, sellerHome());
170
+ if (!stored) {
171
+ console.error(`❌ 本机没有 item ${pending.itemId} 的内容 (~/.bolloon/x402-info/) — 没法签 (签一个对不上的信封等于假交付)`);
172
+ return 1;
173
+ }
174
+ // ③ 本机钥匙: 必须是这条 item 的卖方 DID (**不新建钥匙**)
175
+ const agentId = flagValue(args, '--agent');
176
+ const key = await resolveSellerKey(pending.providerDid, { agentId });
177
+ if (!key) {
178
+ console.error(`❌ 本机找不到能给 ${pending.providerDid} 签名的私钥 (查过 ~/.bolloon/agent-keys/*.json 与 ~/.bolloon/identity.json)`);
179
+ return 1;
180
+ }
181
+ // ④ 签 (既有 ed25519Sign), ⑤ 本机自检验签 —— 不信"我签好了", 用公钥验一遍
182
+ const envelope = await signPending({ pending, item: stored.item, content: stored.content, keypair: { did: key.did, publicKey: key.publicKeyHex, privateKey: Buffer.from(key.privateKeyHex, 'hex') } });
183
+ const selfCheck = await ed25519Verify(envelope.proof.publicKeyHex, canonicalize(envelope.proof.payload), envelope.proof.signature);
184
+ // ⑥ 回传 (服务器会用钉住的公钥再验一次)
185
+ const post = await sellerClientRequest({ endpoint, auth }, { method: 'POST', path: sellerApiPaths().envelope(pending.pendingId), body: JSON.stringify({ envelope }) });
186
+ if (json) {
187
+ console.log(JSON.stringify({
188
+ ok: post.ok && selfCheck,
189
+ pendingId: pending.pendingId,
190
+ itemId: pending.itemId,
191
+ contentHash: envelope.proof.payload.contentHash,
192
+ receiptHash: envelope.proof.payload.receiptHash,
193
+ publicKeyHex: envelope.proof.publicKeyHex,
194
+ did: envelope.proof.did,
195
+ keySource: key.source,
196
+ selfCheckSignature: selfCheck,
197
+ server: { status: post.status, ok: post.ok, code: post.json?.code, envelopeHash: post.json?.envelopeHash },
198
+ envelope: post.ok ? undefined : envelope,
199
+ }, null, 2));
200
+ return post.ok && selfCheck ? 0 : 1;
201
+ }
202
+ console.log(`签名自检: ${selfCheck ? '✅ ed25519Verify 通过' : '❌ 验签不通过 (绝不回传这种信封)'}`);
203
+ console.log(` 签名 ${short(envelope.proof.signature, 28)} (base64, 完整值用 --json)`);
204
+ console.log(` 签名公钥 ${envelope.proof.publicKeyHex}`);
205
+ console.log(` 钥匙来源 ${key.source} (DID ${envelope.proof.did})`);
206
+ console.log(` 载荷绑定 itemId=${envelope.proof.payload.itemId} · contentHash=${short(envelope.proof.payload.contentHash, 24)} · receiptHash=${short(envelope.proof.payload.receiptHash, 24)}`);
207
+ if (!post.ok) {
208
+ console.error(`❌ 回传失败 (HTTP ${post.status}): ${post.json?.code || ''} ${post.json?.error || post.text.slice(0, 200)}`);
209
+ return 1;
210
+ }
211
+ if (!selfCheck)
212
+ return 1;
213
+ console.log(`✅ 已回传并收下 — pendingId=${pending.pendingId} envelopeHash=${post.json?.envelopeHash}`);
214
+ console.log(` 买方下一步: 用取件 token 取 (不要重放付款凭据 —— 会再跑一遍结算):`);
215
+ console.log(` GET ${paidInfoRetrievePath(pending.itemId, pending.pendingId)} → 200 + 信封, 然后 ed25519Verify / verifyEnvelope 离线验签`);
216
+ return 0;
217
+ }
218
+ async function cmdAuthInit(args) {
219
+ const endpoint = flagValue(args, '--endpoint') || process.env.BOLLOON_X402_SELLER_ENDPOINT;
220
+ const force = args.includes('--force');
221
+ const existing = await loadSellerAuth();
222
+ if (existing && !force) {
223
+ console.log(`已存在共享密钥 (keyId=${existing.keyId} 指纹 ${sellerAuthFingerprint(existing)}) — 换新密钥要显式 --force`);
224
+ console.log(` endpoint: ${existing.endpoint || '(未设置)'} · 文件 ${sellerAuthPath()}`);
225
+ return 0;
226
+ }
227
+ const cfg = generateSellerAuth(endpoint);
228
+ const file = await saveSellerAuth(cfg);
229
+ console.log('✅ 已生成共享密钥 (0600 文件, 值不打印)');
230
+ console.log(` keyId ${cfg.keyId}`);
231
+ console.log(` 指纹 ${sellerAuthFingerprint(cfg)} ← 核对两端是不是同一把就用它`);
232
+ console.log(` endpoint ${cfg.endpoint || '(未设置, 用 --endpoint 指定)'}`);
233
+ console.log(` 文件 ${file}`);
234
+ console.log('');
235
+ console.log('安装到服务器 (值只走 stdin, 不进命令行/不进日志/不进会话):');
236
+ console.log(` ssh <server> 'install -m600 -o bolloonpay -g bolloonpay /dev/stdin /opt/bolloon-pay/.bolloon/${'x402-seller-auth.json'}' < ${file}`);
237
+ console.log('卖方公钥钉钉子 (只有公钥):');
238
+ console.log(' bolloon x402 pending key > /tmp/seller-key.json # 再 scp 到服务器 /opt/bolloon-pay/.bolloon/seller-key.json');
239
+ return 0;
240
+ }
241
+ async function cmdKey(args) {
242
+ const json = args.includes('--json');
243
+ const agentId = flagValue(args, '--agent');
244
+ // 不指定 agent 时用身份文件 (与 routes-x402-info.ts 的 loadProviderKeypair 同源)
245
+ const did = process.env.BOLLOON_SELLER_DID || '';
246
+ const key = await resolveSellerKey(did, { agentId });
247
+ if (!key) {
248
+ console.error('❌ 本机找不到卖方身份 (~/.bolloon/identity.json 或 agent-keys/)');
249
+ return 1;
250
+ }
251
+ const out = { protocol: 'bolloon-x402-seller/1', did: key.did, publicKeyHex: key.publicKeyHex, note: '这是公钥 (可放服务器)。私钥不在此输出中。' };
252
+ if (json) {
253
+ console.log(JSON.stringify(out, null, 2));
254
+ return 0;
255
+ }
256
+ console.log(`卖方 DID ${out.did}`);
257
+ console.log(`公钥 (hex) ${out.publicKeyHex}`);
258
+ console.log(`来源 ${key.source}`);
259
+ console.log(`钉到服务器: /opt/bolloon-pay/.bolloon/seller-key.json`);
260
+ return 0;
261
+ }
262
+ // ─────────────────────────────────────────────────────────── 入口
263
+ export async function x402PendingCommand(args) {
264
+ const sub = args[0];
265
+ const rest = args.slice(1);
266
+ try {
267
+ switch (sub) {
268
+ case 'list': return await cmdList(args);
269
+ case 'show': return await cmdShow(rest);
270
+ case 'sign': return await cmdSign(rest);
271
+ case 'auth-init': return await cmdAuthInit(rest);
272
+ case 'key': return await cmdKey(rest);
273
+ case undefined:
274
+ case '--help':
275
+ case '-h':
276
+ console.log(USAGE.trim());
277
+ return sub === undefined ? 1 : 0;
278
+ default:
279
+ console.error(`未知 x402 pending 子命令: ${sub}`);
280
+ console.log(USAGE.trim());
281
+ return 1;
282
+ }
283
+ }
284
+ catch (e) {
285
+ console.error(`❌ ${String(e?.message || e).slice(0, 300)}`);
286
+ return 1;
287
+ }
288
+ }
289
+ export { USAGE as X402_PENDING_USAGE };
package/dist/cli-entry.js CHANGED
@@ -21,6 +21,8 @@ import { printBanner } from './cli/loading-tui.js';
21
21
  import { discoverEngines, delegateToEngine } from './external-engines/index.js';
22
22
  import { x402CheckBalance, x402Fetch } from './agents/x402/x402Pay.js';
23
23
  import { runVersionCommand, runUpdateCommand, runDoctorCommand, runRuntimeCommand, UPDATE_HELP } from './cli/update-commands.js';
24
+ import { installStartupLogGate } from './cli/log-gate.js';
25
+ import { waitForTermSize } from './cli/loading-tui.js';
24
26
  import { collectVersionInfo } from './utils/version-info.js';
25
27
  // 2026-09-27: 启动前言的默认静默 + 显式查询命令 (与 `src/index.ts` 同一套判断)
26
28
  import { startupPreambleVisible } from './cli/startup-notice.js';
@@ -68,7 +70,9 @@ ${BOLD}命令:${RESET}
68
70
  bolloon setup 初始化向导 (你的称呼 + 模型供应商 + API key + 连通性测试)
69
71
  bolloon identity init 非交互建本机身份 (~/.bolloon/identity.json, 0600, 幂等; 新机器/第二实例用)
70
72
  bolloon identity show 看本机身份 (只出 DID/指纹, 绝不打印私钥)
71
- bolloon update [plan|status|history|now] 检查更新 / 计划 / 状态 / 历史 / 执行 (默认只检查)
73
+ bolloon identity bind-address 登记「地址 ↔ DID」绑定 (链下登记·双侧签名·可离线验签)
74
+ bolloon identity bindings 绑定库: list | show <file> | verify <file> | publish --out <path>
75
+ bolloon update [wait|force|--dry-run|status|history] 直接执行更新 (先打印计划与风险检查; 只看计划用 --dry-run)
72
76
  bolloon doctor 安装入口 + 版本事实 + 更新状态自洽性诊断
73
77
  bolloon runtime [plan|install] 运行时 (Node/npm/Git/Python) 检查与安装
74
78
  bolloon model [name] [model] 列出 / 切换模型供应商 (如: bolloon model deepseek deepseek-v4-flash)
@@ -84,6 +88,7 @@ ${BOLD}命令:${RESET}
84
88
  bolloon engine run <prompt> 委派任务给智能体
85
89
  bolloon x402 fetch <url> x402 自动支付 HTTP 请求
86
90
  bolloon x402 balance <address> 查询 x402 钱包余额
91
+ bolloon x402 pay <endpoint> 买方自己发 EIP-3009 授权转账 (--with-memo 带订单标识)
87
92
  bolloon mcp serve MCP server (stdio) —— 给 MCP 客户端接本机 Agent 能力
88
93
  bolloon mcp tools 列出 MCP 暴露的 tools/resources (只读清单)
89
94
 
@@ -94,9 +99,8 @@ ${BOLD}示例:${RESET}
94
99
  bolloon --web # 启动 Web UI
95
100
  bolloon --cli # 命令行模式
96
101
  bolloon model # 查看当前模型供应商
97
- bolloon update # 检查更新 (只读, 不改任何东西)
98
- bolloon update plan # 看更新计划与风险检查
99
- bolloon update now # 真正执行更新
102
+ bolloon update # 直接执行更新 (先打印计划与风险检查)
103
+ bolloon update --dry-run # 只看计划, 不动任何东西
100
104
  bolloon doctor # 我这台机器的 Bolloon 是否自洽
101
105
  bolloon network join --json # 入网 (失败也结构化: ok:false + code + next_action)
102
106
  bolloon task --request-id <id> "…" # 用给定幂等键跑 M1 任务 (同一 requestId 不会付两次)
@@ -247,12 +251,14 @@ function firstPositional(args) {
247
251
  }
248
252
  return undefined;
249
253
  }
250
- /** x402 子命令: fetch / balance */
254
+ /** x402 子命令: fetch / balance / pending (卖方本机签名交付) */
251
255
  async function handleX402Command(x402Args) {
252
256
  if (x402Args.length === 0) {
253
257
  console.log(`${BOLD}用法:${RESET}`);
254
258
  console.log(' bolloon x402 fetch <url> [options] # 自动处理 402 Payment Required');
255
259
  console.log(' bolloon x402 balance <address> [options] # 查询钱包余额');
260
+ console.log(' bolloon x402 pay <endpoint> [--with-memo] # 买方自己发 EIP-3009 授权转账 (nonce 带订单标识 BOL1)');
261
+ console.log(' bolloon x402 pending list|show|sign # 卖方本机签名交付 (私钥不离开本机)');
256
262
  console.log('');
257
263
  console.log(`${BOLD}选项:${RESET}`);
258
264
  console.log(' --private-key <0x...> 自动支付钱包私钥,也可用 X402_PRIVATE_KEY');
@@ -266,6 +272,18 @@ async function handleX402Command(x402Args) {
266
272
  }
267
273
  const sub = x402Args[0];
268
274
  const rest = x402Args.slice(1);
275
+ // 2026-09-28: 卖方本机签名交付 —— 待办队列 (list/show/sign/auth-init/key)
276
+ if (sub === 'pending') {
277
+ const { x402PendingCommand } = await import('./cli/x402-seller-command.js');
278
+ process.exit(await x402PendingCommand(rest));
279
+ return;
280
+ }
281
+ // 2026-09-29: 买方自己发 EIP-3009 transferWithAuthorization (nonce 带订单标识 BOL1)
282
+ if (sub === 'pay') {
283
+ const { x402PayCommand } = await import('./cli/x402-buyer-command.js');
284
+ process.exit(await x402PayCommand(rest));
285
+ return;
286
+ }
269
287
  if (sub === 'fetch') {
270
288
  const url = firstPositional(rest);
271
289
  if (!url) {
@@ -949,6 +967,26 @@ async function main() {
949
967
  await startWebServer(args);
950
968
  break;
951
969
  case 'cli':
970
+ /**
971
+ * 2026-09-30 修复 (leo 报「开启过程的日志没去掉」): 启动日志门原先在 `index.ts` 很靠后的位置才装,
972
+ * 于是**早于它**发生的第三方输出会直接落到 tty, 出现在启动面板之前 (实测 SDK 的 ipfs 装配会打
973
+ * `[warn]: ipfs init 失败…` 两行 + 它自己的子进程输出, 共 4 行泄漏).
974
+ * 门覆盖 `process.stdout.write` / `process.stderr.write` / `console.*` 三层, 且**是单例**
975
+ * (index.ts 里那次 `installStartupLogGate` 会直接复用它) ⇒ 在这里提前装, 行为不变、只是更早生效.
976
+ * 与 index 侧同一套跳过条件: `--json` (stdout 是机器数据本体) / 一次性工具 / 命令式诊断 不装门.
977
+ */
978
+ {
979
+ const a = args;
980
+ const isOneShotTool = !!(a.tool || a.prompt);
981
+ const isCommandLike = !!(a.supervise || a.setupStatus || a.setupResume || a.setupRepair
982
+ || a.setupReconfigure || a.setupTest);
983
+ if (!a.json && !isOneShotTool && !isCommandLike) {
984
+ installStartupLogGate({ mode: 'cli-interactive', args: process.argv.slice(2) });
985
+ }
986
+ }
987
+ // 2026-09-30: 先等真实终端尺寸就位再出第一帧 —— 否则面板会先按兜底 80 列渲染一次,
988
+ // 拿到真尺寸后再渲染一次, 而静态输出覆盖不掉旧内容 ⇒ 顶框被重复打印 + 宽度乱变.
989
+ await waitForTermSize();
952
990
  await startCLI(args);
953
991
  break;
954
992
  case 'engine':
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ /**
3
+ * transfer-classify.ts — 转账索引的**纯**部分 (类型 + 分类 + 折算 + 汇总)
4
+ * =========================================================================
5
+ * 为什么单独一个文件: 这两半的**加载代价**完全不同 ——
6
+ * · 本文件: 零依赖 (不 import ethers / 不发 RPC / 不读盘) ⇒ 公开页/快照构建可以**静态**引入;
7
+ * · `transfer-index.ts`: 要 JSON-RPC provider (ethers) 与文件系统 ⇒ 只在**同步**路径上加载。
8
+ * 把纯函数放在这里, network-pulse 才不必为了 `formatUnits` 把 ethers 拖进网页进程。
9
+ *
10
+ * 分类口径 (leo 2026-09-29 实测拍板; **不许**只按"USDC 转到该地址"计数):
11
+ * · `escrow_settlement` from == escrow 合约 → 「escrow 退款/释放」(实测块 51685757 / 51686009
12
+ * 两笔 +0.001 与那两笔 Disputed→Refunded **逐块对应**) —— 钱退回, **不是收入**;
13
+ * · `self_transfer` from ∈ 自己地址集 (可配 ownAddresses) → 「转入(非销售)」;
14
+ * · `external_payment` 其余 → 「外部付款」(与卖方端点台账对上的加标「经 x402 流程」);
15
+ * · `outbound` 关注地址**付出** → 不进「链上转入」计数。
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.TRANSFER_ADDRESS_RE = exports.USDC_BY_CHAIN = exports.ERC20_TRANSFER_TOPIC = exports.TRANSFER_INDEX_SCHEMA_VERSION = void 0;
19
+ exports.formatUnits = formatUnits;
20
+ exports.classifyTransfer = classifyTransfer;
21
+ exports.summarizeTransfers = summarizeTransfers;
22
+ exports.TRANSFER_INDEX_SCHEMA_VERSION = 1;
23
+ /** Transfer(address indexed from, address indexed to, uint256 value) 的 topic0 */
24
+ exports.ERC20_TRANSFER_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';
25
+ /** Base 主网 USDC (缺省 token; 也可由 chain.json 的 tokenAddress / env 覆盖) */
26
+ exports.USDC_BY_CHAIN = {
27
+ 8453: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
28
+ 84532: '0x036cbd53842c5426634e7929541ec2318f3dcf7e',
29
+ };
30
+ /** 0x + 40 位十六进制 (地址形状; 与 explorer.ts 的同一套尺子) */
31
+ exports.TRANSFER_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
32
+ /** 原子单位 → 十进制字符串 (纯整数运算, 不经过浮点; 去掉末尾多余的 0) */
33
+ function formatUnits(atomic, decimals) {
34
+ const v = BigInt(typeof atomic === 'bigint' ? atomic : (String(atomic || '0').trim() || '0'));
35
+ const neg = v < 0n;
36
+ const abs = neg ? -v : v;
37
+ const d = Math.max(0, Math.floor(decimals));
38
+ const base = 10n ** BigInt(d);
39
+ const whole = (abs / base).toString();
40
+ const frac = (abs % base).toString().padStart(d, '0').replace(/0+$/, '');
41
+ const out = frac ? `${whole}.${frac}` : whole;
42
+ return neg ? `-${out}` : out;
43
+ }
44
+ /**
45
+ * 一条转账的**分类** (纯函数: 输入事实 + 配置, 输出分类)。
46
+ * 为什么在**读**的时候算而不是写索引时算: 分类规则 / 自己地址集是会变的
47
+ * (把 0xb4cb8009… 认成"自己钱包"之前, 它只是一个外部地址) ⇒ 历史行必须能被重新分类,
48
+ * 而不是把当时的判断固化进索引。索引里只存**链上事实** (from/to/value/块号)。
49
+ */
50
+ function classifyTransfer(e, cfg) {
51
+ const from = String(e.from || '').toLowerCase();
52
+ const to = String(e.to || '').toLowerCase();
53
+ const watch = new Set((cfg.watchAddresses || []).map((a) => String(a).toLowerCase()));
54
+ if (e.direction === 'out' || (watch.has(from) && !watch.has(to)))
55
+ return 'outbound';
56
+ const escrow = String(cfg.escrowAddress || '').toLowerCase();
57
+ if (escrow && from === escrow)
58
+ return 'escrow_settlement';
59
+ if ((cfg.ownAddresses || []).map((a) => String(a).toLowerCase()).includes(from))
60
+ return 'self_transfer';
61
+ return 'external_payment';
62
+ }
63
+ /** 已落盘转账索引 → 汇总 (纯函数; 不发网络、不读配置以外的东西) */
64
+ function summarizeTransfers(state, cfg) {
65
+ const live = (state.entries || []).filter((e) => !e.suspect);
66
+ const by_class = { escrow_settlement: 0, self_transfer: 0, external_payment: 0 };
67
+ const by_class_atomic = { escrow_settlement: '0', self_transfer: '0', external_payment: '0' };
68
+ let inbound = 0, inboundAtomic = 0n, outbound = 0, outboundAtomic = 0n, pending = 0;
69
+ for (const e of live) {
70
+ if (e.finality === 'observed')
71
+ pending++;
72
+ const v = BigInt(e.value || '0');
73
+ if (e.direction === 'out') {
74
+ outbound++;
75
+ outboundAtomic += v;
76
+ continue;
77
+ }
78
+ inbound++;
79
+ inboundAtomic += v;
80
+ const c = classifyTransfer(e, cfg);
81
+ if (c !== 'outbound') {
82
+ by_class[c]++;
83
+ by_class_atomic[c] = (BigInt(by_class_atomic[c]) + v).toString();
84
+ }
85
+ }
86
+ const decimals = Number.isInteger(state.tokenDecimals) ? state.tokenDecimals : (cfg.tokenDecimals ?? 6);
87
+ return {
88
+ configured: cfg.enabled !== false && !!((state.watchAddresses || cfg.watchAddresses || []).length),
89
+ reason: cfg.reason || '',
90
+ entries: live.length,
91
+ suspects: (state.entries || []).length - live.length,
92
+ inbound, inbound_atomic: inboundAtomic.toString(), inbound_display: formatUnits(inboundAtomic, decimals),
93
+ by_class, by_class_atomic,
94
+ outbound, outbound_atomic: outboundAtomic.toString(),
95
+ token_symbol: state.tokenSymbol || cfg.tokenSymbol || '',
96
+ token_decimals: decimals,
97
+ watch_addresses_count: (state.watchAddresses || cfg.watchAddresses || []).length,
98
+ own_addresses_count: (cfg.ownAddresses || []).length,
99
+ from_block: state.fromBlock,
100
+ last_synced_block: state.lastSyncedBlock,
101
+ last_synced_at: state.lastSyncedAt,
102
+ head_block: state.headBlock,
103
+ pending_observation: pending,
104
+ };
105
+ }
106
+ //# sourceMappingURL=transfer-classify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transfer-classify.js","sourceRoot":"","sources":["../../../../src/agents/chain/transfer-classify.ts"],"names":[],"mappings":"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"}