@bolloon/bolloon-agent 0.5.1 → 0.5.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/README.md +39 -0
- package/dist/agents/agent-lang.js +101 -0
- package/dist/agents/agent-manifest-protocol.js +4 -0
- package/dist/agents/chain/transfer-classify.js +99 -0
- package/dist/agents/chain/transfer-index.js +354 -0
- package/dist/agents/gateway-group.js +44 -3
- package/dist/agents/identity/address-binding.js +613 -0
- package/dist/agents/network-pulse.js +706 -27
- package/dist/agents/pi-sdk.js +101 -8
- package/dist/agents/workflow-pivot-loop.js +3 -1
- package/dist/agents/x402/direct-payment.js +689 -0
- package/dist/agents/x402/order-identity.js +408 -0
- package/dist/agents/x402/paid-info-store.js +17 -0
- package/dist/agents/x402/seller-signing.js +614 -0
- package/dist/agents/x402/seller-summary.js +398 -0
- package/dist/cli/identity-command.js +327 -2
- package/dist/cli/protocol-envelope.js +4 -0
- package/dist/cli/update-commands.js +41 -25
- package/dist/cli/x402-buyer-command.js +463 -0
- package/dist/cli/x402-seller-command.js +289 -0
- package/dist/cli-entry.js +21 -5
- package/dist/efficode/compress.js +78 -0
- package/dist/efficode/crc32.js +38 -0
- package/dist/efficode/did.js +66 -0
- package/dist/efficode/index.js +24 -0
- package/dist/efficode/lz77.js +170 -0
- package/dist/efficode/negotiate.js +187 -0
- package/dist/efficode/ops.js +87 -0
- package/dist/efficode/packet.js +271 -0
- package/dist/efficode/types.js +45 -0
- package/dist/electron-build/agents/agent-manifest-protocol.js +4 -0
- package/dist/electron-build/agents/agent-manifest-protocol.js.map +1 -1
- package/dist/electron-build/agents/chain/transfer-classify.js +106 -0
- package/dist/electron-build/agents/chain/transfer-classify.js.map +1 -0
- package/dist/electron-build/agents/chain/transfer-index.js +400 -0
- package/dist/electron-build/agents/chain/transfer-index.js.map +1 -0
- package/dist/electron-build/agents/identity/address-binding.js +672 -0
- package/dist/electron-build/agents/identity/address-binding.js.map +1 -0
- package/dist/electron-build/agents/network-pulse.js +714 -28
- package/dist/electron-build/agents/network-pulse.js.map +1 -1
- package/dist/electron-build/agents/pi-sdk.js +102 -8
- package/dist/electron-build/agents/pi-sdk.js.map +1 -1
- package/dist/electron-build/agents/workflow-pivot-loop.js +3 -1
- package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -1
- package/dist/electron-build/agents/x402/direct-payment.js +744 -0
- package/dist/electron-build/agents/x402/direct-payment.js.map +1 -0
- package/dist/electron-build/agents/x402/order-identity.js +458 -0
- package/dist/electron-build/agents/x402/order-identity.js.map +1 -0
- package/dist/electron-build/agents/x402/paid-info-protocol.js.map +1 -1
- package/dist/electron-build/agents/x402/paid-info-store.js +18 -0
- package/dist/electron-build/agents/x402/paid-info-store.js.map +1 -1
- package/dist/electron-build/agents/x402/seller-signing.js +682 -0
- package/dist/electron-build/agents/x402/seller-signing.js.map +1 -0
- package/dist/electron-build/llm/pi-ai.js +685 -48
- package/dist/electron-build/llm/pi-ai.js.map +1 -1
- package/dist/electron-build/utils/auto-update.js +2 -2
- package/dist/electron-build/utils/auto-update.js.map +1 -1
- package/dist/electron-build/utils/runtime-bootstrap.js +1 -1
- package/dist/electron-build/utils/runtime-bootstrap.js.map +1 -1
- package/dist/electron-build/utils/update-manager.js +3 -3
- package/dist/electron-build/utils/update-manager.js.map +1 -1
- package/dist/electron-build/utils/version-identity.js +1 -1
- package/dist/electron-build/utils/version-identity.js.map +1 -1
- package/dist/electron-build/web/routes-x402-info.js +28 -1
- package/dist/electron-build/web/routes-x402-info.js.map +1 -1
- package/dist/index.js +3 -3
- package/dist/llm/pi-ai.js +666 -47
- package/dist/utils/auto-update.js +2 -2
- package/dist/utils/runtime-bootstrap.js +1 -1
- package/dist/utils/update-health.js +2 -2
- package/dist/utils/update-manager.js +3 -3
- package/dist/utils/version-identity.js +1 -1
- package/dist/web/bolloon-web.json +3 -3
- package/dist/web/mobile.js +64 -0
- package/dist/web/routes-x402-info.js +28 -1
- package/dist/web/sw.js +1 -1
- 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
|
@@ -68,7 +68,9 @@ ${BOLD}命令:${RESET}
|
|
|
68
68
|
bolloon setup 初始化向导 (你的称呼 + 模型供应商 + API key + 连通性测试)
|
|
69
69
|
bolloon identity init 非交互建本机身份 (~/.bolloon/identity.json, 0600, 幂等; 新机器/第二实例用)
|
|
70
70
|
bolloon identity show 看本机身份 (只出 DID/指纹, 绝不打印私钥)
|
|
71
|
-
bolloon
|
|
71
|
+
bolloon identity bind-address 登记「地址 ↔ DID」绑定 (链下登记·双侧签名·可离线验签)
|
|
72
|
+
bolloon identity bindings 绑定库: list | show <file> | verify <file> | publish --out <path>
|
|
73
|
+
bolloon update [wait|force|--dry-run|status|history] 直接执行更新 (先打印计划与风险检查; 只看计划用 --dry-run)
|
|
72
74
|
bolloon doctor 安装入口 + 版本事实 + 更新状态自洽性诊断
|
|
73
75
|
bolloon runtime [plan|install] 运行时 (Node/npm/Git/Python) 检查与安装
|
|
74
76
|
bolloon model [name] [model] 列出 / 切换模型供应商 (如: bolloon model deepseek deepseek-v4-flash)
|
|
@@ -84,6 +86,7 @@ ${BOLD}命令:${RESET}
|
|
|
84
86
|
bolloon engine run <prompt> 委派任务给智能体
|
|
85
87
|
bolloon x402 fetch <url> x402 自动支付 HTTP 请求
|
|
86
88
|
bolloon x402 balance <address> 查询 x402 钱包余额
|
|
89
|
+
bolloon x402 pay <endpoint> 买方自己发 EIP-3009 授权转账 (--with-memo 带订单标识)
|
|
87
90
|
bolloon mcp serve MCP server (stdio) —— 给 MCP 客户端接本机 Agent 能力
|
|
88
91
|
bolloon mcp tools 列出 MCP 暴露的 tools/resources (只读清单)
|
|
89
92
|
|
|
@@ -94,9 +97,8 @@ ${BOLD}示例:${RESET}
|
|
|
94
97
|
bolloon --web # 启动 Web UI
|
|
95
98
|
bolloon --cli # 命令行模式
|
|
96
99
|
bolloon model # 查看当前模型供应商
|
|
97
|
-
bolloon update #
|
|
98
|
-
bolloon update
|
|
99
|
-
bolloon update now # 真正执行更新
|
|
100
|
+
bolloon update # 直接执行更新 (先打印计划与风险检查)
|
|
101
|
+
bolloon update --dry-run # 只看计划, 不动任何东西
|
|
100
102
|
bolloon doctor # 我这台机器的 Bolloon 是否自洽
|
|
101
103
|
bolloon network join --json # 入网 (失败也结构化: ok:false + code + next_action)
|
|
102
104
|
bolloon task --request-id <id> "…" # 用给定幂等键跑 M1 任务 (同一 requestId 不会付两次)
|
|
@@ -247,12 +249,14 @@ function firstPositional(args) {
|
|
|
247
249
|
}
|
|
248
250
|
return undefined;
|
|
249
251
|
}
|
|
250
|
-
/** x402 子命令: fetch / balance */
|
|
252
|
+
/** x402 子命令: fetch / balance / pending (卖方本机签名交付) */
|
|
251
253
|
async function handleX402Command(x402Args) {
|
|
252
254
|
if (x402Args.length === 0) {
|
|
253
255
|
console.log(`${BOLD}用法:${RESET}`);
|
|
254
256
|
console.log(' bolloon x402 fetch <url> [options] # 自动处理 402 Payment Required');
|
|
255
257
|
console.log(' bolloon x402 balance <address> [options] # 查询钱包余额');
|
|
258
|
+
console.log(' bolloon x402 pay <endpoint> [--with-memo] # 买方自己发 EIP-3009 授权转账 (nonce 带订单标识 BOL1)');
|
|
259
|
+
console.log(' bolloon x402 pending list|show|sign # 卖方本机签名交付 (私钥不离开本机)');
|
|
256
260
|
console.log('');
|
|
257
261
|
console.log(`${BOLD}选项:${RESET}`);
|
|
258
262
|
console.log(' --private-key <0x...> 自动支付钱包私钥,也可用 X402_PRIVATE_KEY');
|
|
@@ -266,6 +270,18 @@ async function handleX402Command(x402Args) {
|
|
|
266
270
|
}
|
|
267
271
|
const sub = x402Args[0];
|
|
268
272
|
const rest = x402Args.slice(1);
|
|
273
|
+
// 2026-09-28: 卖方本机签名交付 —— 待办队列 (list/show/sign/auth-init/key)
|
|
274
|
+
if (sub === 'pending') {
|
|
275
|
+
const { x402PendingCommand } = await import('./cli/x402-seller-command.js');
|
|
276
|
+
process.exit(await x402PendingCommand(rest));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
// 2026-09-29: 买方自己发 EIP-3009 transferWithAuthorization (nonce 带订单标识 BOL1)
|
|
280
|
+
if (sub === 'pay') {
|
|
281
|
+
const { x402PayCommand } = await import('./cli/x402-buyer-command.js');
|
|
282
|
+
process.exit(await x402PayCommand(rest));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
269
285
|
if (sub === 'fetch') {
|
|
270
286
|
const url = firstPositional(rest);
|
|
271
287
|
if (!url) {
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* efficode/compress - 可插拔压缩层
|
|
3
|
+
*
|
|
4
|
+
* 三个算法 (数据块第一字节 = 算法 id):
|
|
5
|
+
* 0 none 原样 (小消息用 —— 头上开箱代价为 0)
|
|
6
|
+
* 1 lz77 本仓实现, 纯 JS 可跑任何运行面 (含手机 WebView)
|
|
7
|
+
* 2 deflate node:zlib deflateRaw (真压缩, 真数字)
|
|
8
|
+
*
|
|
9
|
+
* 默认策略 `pickAlgo`: 先按内容选, 选完**真压一遍**, 压不小就退回 `none`
|
|
10
|
+
* (小消息硬压一定更贵 —— 这是实测结论, 见 wiki §"更高效吗")。
|
|
11
|
+
*/
|
|
12
|
+
import { deflateRawSync, inflateRawSync } from 'node:zlib';
|
|
13
|
+
import { lz77Compress, lz77Decompress } from './lz77.js';
|
|
14
|
+
import { COMPRESSION_IDS, COMPRESSION_BY_ID, EfficodeError } from './types.js';
|
|
15
|
+
export const COMPRESSION_ALGOS = ['none', 'lz77', 'deflate'];
|
|
16
|
+
/** 压缩。返回 [1B 算法 id][载荷] 的完整数据块体 */
|
|
17
|
+
export function compressBlock(raw, algo) {
|
|
18
|
+
const body = compressPayload(raw, algo);
|
|
19
|
+
const out = new Uint8Array(body.length + 1);
|
|
20
|
+
out[0] = COMPRESSION_IDS[algo];
|
|
21
|
+
out.set(body, 1);
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
function compressPayload(raw, algo) {
|
|
25
|
+
switch (algo) {
|
|
26
|
+
case 'none':
|
|
27
|
+
return raw;
|
|
28
|
+
case 'lz77':
|
|
29
|
+
return lz77Compress(raw);
|
|
30
|
+
case 'deflate':
|
|
31
|
+
return new Uint8Array(deflateRawSync(raw));
|
|
32
|
+
default:
|
|
33
|
+
throw new EfficodeError('EFFICODE_UNKNOWN_COMPRESSION', `未知压缩算法 ${String(algo)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** 解压 [1B 算法 id][载荷]。未知算法 id → 报错, 绝不"跳过解压直接给原文" */
|
|
37
|
+
export function decompressBlock(block) {
|
|
38
|
+
if (block.length < 1)
|
|
39
|
+
throw new EfficodeError('EFFICODE_TRUNCATED', '数据块空 (缺算法 id)');
|
|
40
|
+
const algo = COMPRESSION_BY_ID[block[0]];
|
|
41
|
+
if (!algo) {
|
|
42
|
+
throw new EfficodeError('EFFICODE_UNKNOWN_COMPRESSION', `数据块声明未知压缩算法 id=${block[0]}`, 0);
|
|
43
|
+
}
|
|
44
|
+
const payload = block.subarray(1);
|
|
45
|
+
switch (algo) {
|
|
46
|
+
case 'none':
|
|
47
|
+
return { algo, data: payload.slice() };
|
|
48
|
+
case 'lz77':
|
|
49
|
+
return { algo, data: lz77Decompress(payload) };
|
|
50
|
+
case 'deflate': {
|
|
51
|
+
try {
|
|
52
|
+
return { algo, data: new Uint8Array(inflateRawSync(payload)) };
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
throw new EfficodeError('EFFICODE_TRUNCATED', `deflate 解压失败: ${String(e?.message ?? e).slice(0, 120)}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
default:
|
|
59
|
+
throw new EfficodeError('EFFICODE_UNKNOWN_COMPRESSION', `未知压缩算法 ${String(algo)}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 默认算法选择: 结构化文本 (重复多) 用 deflate; 其余先用 lz77 试;
|
|
64
|
+
* 压缩后**不小于**原长就退回 none (省下的不该是负数)。
|
|
65
|
+
*/
|
|
66
|
+
export function pickAlgo(raw, preferred = 'deflate') {
|
|
67
|
+
if (raw.length < 32)
|
|
68
|
+
return 'none'; // 小消息: 头上开箱代价 > 收益
|
|
69
|
+
const got = compressPayload(raw, preferred);
|
|
70
|
+
return got.length < raw.length ? preferred : 'none';
|
|
71
|
+
}
|
|
72
|
+
/** 真测: 三个算法分别压出来的字节数 (给门和 wiki 用真实数字, 不许估) */
|
|
73
|
+
export function measureAll(raw) {
|
|
74
|
+
const out = {};
|
|
75
|
+
for (const a of COMPRESSION_ALGOS)
|
|
76
|
+
out[a] = compressPayload(raw, a).length + 1; // +1 = 算法 id 字节
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* efficode/crc32 - 包尾 CRC32 (IEEE 802.3, 多项式 0xEDB88320)
|
|
3
|
+
*
|
|
4
|
+
* 为什么自己写而不是用 zlib.crc32: 包层要能在**任何**运行面 (含手机 WebView 的
|
|
5
|
+
* 纯 JS 上下文) 真算同一个校验值, 所以这里是不依赖 node:zlib 的纯实现。
|
|
6
|
+
* 自证向量写在 src/test/efficode.test.ts: crc32("123456789") === 0xCBF43926 (规范值)。
|
|
7
|
+
*/
|
|
8
|
+
const TABLE = (() => {
|
|
9
|
+
const t = new Uint32Array(256);
|
|
10
|
+
for (let n = 0; n < 256; n++) {
|
|
11
|
+
let c = n;
|
|
12
|
+
for (let k = 0; k < 8; k++)
|
|
13
|
+
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
14
|
+
t[n] = c >>> 0;
|
|
15
|
+
}
|
|
16
|
+
return t;
|
|
17
|
+
})();
|
|
18
|
+
/** 算 CRC32, 返回无符号 32 位整数 */
|
|
19
|
+
export function crc32(buf) {
|
|
20
|
+
let c = 0xffffffff;
|
|
21
|
+
for (let i = 0; i < buf.length; i++)
|
|
22
|
+
c = TABLE[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
|
|
23
|
+
return (c ^ 0xffffffff) >>> 0;
|
|
24
|
+
}
|
|
25
|
+
/** 打包成 4 字节大端 */
|
|
26
|
+
export function crc32Bytes(buf) {
|
|
27
|
+
const v = crc32(buf);
|
|
28
|
+
const out = new Uint8Array(4);
|
|
29
|
+
out[0] = (v >>> 24) & 0xff;
|
|
30
|
+
out[1] = (v >>> 16) & 0xff;
|
|
31
|
+
out[2] = (v >>> 8) & 0xff;
|
|
32
|
+
out[3] = v & 0xff;
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
/** 从 4 字节大端读回 */
|
|
36
|
+
export function readCrc32Bytes(b, offset = 0) {
|
|
37
|
+
return (((b[offset] << 24) | (b[offset + 1] << 16) | (b[offset + 2] << 8) | b[offset + 3]) >>> 0);
|
|
38
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* efficode/did - DID 验证段 (固定 32B)
|
|
3
|
+
*
|
|
4
|
+
* 布局 (32 字节):
|
|
5
|
+
* [0] algo id (= 1, sha256-截断)
|
|
6
|
+
* [1..3] 原始身份串字节数 (24 位大端, 上限 16MB)
|
|
7
|
+
* [4..31] sha256(身份串) 的**前 28 字节**
|
|
8
|
+
*
|
|
9
|
+
* 诚实边界 (必须写进 wiki, 不许含糊):
|
|
10
|
+
* - 32B **装不下**一个 DID / 公钥, 所以本段是**摘要**, 只能做"是不是同一个身份"的比对,
|
|
11
|
+
* **不能**从段里还原身份, 也**不能**独立证明持有私钥;
|
|
12
|
+
* - 真正的"链上签名验证 + ECC 临时会话密钥"属**规范中/未实现** —— 本模块没有签名函数,
|
|
13
|
+
* 也没有密钥交换函数 (不写假实现)。签名校验的接线点在 packet 层的 `verifyDid` 回调,
|
|
14
|
+
* 现成可用的只有摘要比对这一条。
|
|
15
|
+
*/
|
|
16
|
+
import { createHash } from 'node:crypto';
|
|
17
|
+
import { DID_SEGMENT_BYTES, EfficodeError } from './types.js';
|
|
18
|
+
export const DID_ALGO_SHA256_TRUNC28 = 1;
|
|
19
|
+
/** 编: 身份串 → 32B 段 */
|
|
20
|
+
export function encodeDidSegment(did) {
|
|
21
|
+
const raw = Buffer.from(String(did), 'utf-8');
|
|
22
|
+
if (raw.length === 0)
|
|
23
|
+
throw new EfficodeError('EFFICODE_DID_SEGMENT_BAD', '身份串为空');
|
|
24
|
+
if (raw.length > 0xffffff)
|
|
25
|
+
throw new EfficodeError('EFFICODE_DID_SEGMENT_BAD', `身份串过长 (${raw.length}B > 16MB)`);
|
|
26
|
+
const full = createHash('sha256').update(raw).digest();
|
|
27
|
+
const seg = new Uint8Array(DID_SEGMENT_BYTES);
|
|
28
|
+
seg[0] = DID_ALGO_SHA256_TRUNC28;
|
|
29
|
+
seg[1] = (raw.length >>> 16) & 0xff;
|
|
30
|
+
seg[2] = (raw.length >>> 8) & 0xff;
|
|
31
|
+
seg[3] = raw.length & 0xff;
|
|
32
|
+
seg.set(full.subarray(0, DID_SEGMENT_BYTES - 4), 4);
|
|
33
|
+
return seg;
|
|
34
|
+
}
|
|
35
|
+
/** 解: 32B 段 → 结构。长度不足 / algo 未知 → 抛 */
|
|
36
|
+
export function decodeDidSegment(seg) {
|
|
37
|
+
if (seg.length !== DID_SEGMENT_BYTES) {
|
|
38
|
+
throw new EfficodeError('EFFICODE_DID_SEGMENT_BAD', `DID 段应为 ${DID_SEGMENT_BYTES}B, 实得 ${seg.length}B`);
|
|
39
|
+
}
|
|
40
|
+
const algo = seg[0];
|
|
41
|
+
if (algo !== DID_ALGO_SHA256_TRUNC28) {
|
|
42
|
+
throw new EfficodeError('EFFICODE_DID_SEGMENT_BAD', `未知 DID 摘要算法 id=${algo}`, 0);
|
|
43
|
+
}
|
|
44
|
+
const origLen = (seg[1] << 16) | (seg[2] << 8) | seg[3];
|
|
45
|
+
return { algo, origLen, digest: seg.slice(4) };
|
|
46
|
+
}
|
|
47
|
+
/** 摘要段的稳定文本形式: `algo1:<56 hex>` —— 可安全落日志 (单向, 不可还原身份) */
|
|
48
|
+
export function didDigestString(seg) {
|
|
49
|
+
return `algo${seg.algo}:${Buffer.from(seg.digest).toString('hex')}`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 校验一条身份声明是否与段匹配。
|
|
53
|
+
* 这是**明文比对** (拿原身份串再算一遍摘要), 不是签名验证 —— 返回值里如实标出方式。
|
|
54
|
+
*/
|
|
55
|
+
export function matchesDidSegment(did, seg) {
|
|
56
|
+
const expected = encodeDidSegment(did);
|
|
57
|
+
let match = expected.length === seg.length;
|
|
58
|
+
if (match) {
|
|
59
|
+
for (let i = 0; i < seg.length; i++)
|
|
60
|
+
if (expected[i] !== seg[i]) {
|
|
61
|
+
match = false;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { match, method: 'digest', signed: false };
|
|
66
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* efficode - AI 专用交流语言的**参考实现** (v0.1.0)
|
|
3
|
+
*
|
|
4
|
+
* 模块划分:
|
|
5
|
+
* types.ts 公共类型 / 失败码
|
|
6
|
+
* ops.ts 指令集与词典 (符号 ↔ 二进制操作码, 前缀表达式)
|
|
7
|
+
* crc32.ts 包尾 CRC32 (纯 JS, 任何运行面可算)
|
|
8
|
+
* lz77.ts 自解压数据块用的轻量 LZ77 (真实实现)
|
|
9
|
+
* compress.ts 可插拔压缩层 (none / lz77 / deflate)
|
|
10
|
+
* did.ts DID 验证段 (32B 摘要段)
|
|
11
|
+
* packet.ts 包封装: [Header 2B]|[DID 32B]|[指令段]|[数据块]|[CRC32 4B]
|
|
12
|
+
* negotiate.ts 智能体"用哪种语言"的声明/协商/回落与记录
|
|
13
|
+
*
|
|
14
|
+
* 状态的**唯一事实源**在 docs/wiki/efficode.md 的逐节标注 (已实现 / 规范中 / 未实现);
|
|
15
|
+
* 本目录只提供代码, 代码里的注释只写"这段代码真做了什么"。
|
|
16
|
+
*/
|
|
17
|
+
export * from './types.js';
|
|
18
|
+
export * from './ops.js';
|
|
19
|
+
export * from './crc32.js';
|
|
20
|
+
export * from './lz77.js';
|
|
21
|
+
export * from './compress.js';
|
|
22
|
+
export * from './did.js';
|
|
23
|
+
export * from './packet.js';
|
|
24
|
+
export * from './negotiate.js';
|