@itpay/cli 2.1.22 → 2.1.29
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 +2 -2
- package/dist/src/commands/buy.js +3 -3
- package/dist/src/commands/cart.js +15 -15
- package/dist/src/commands/catalog.js +10 -8
- package/dist/src/commands/checkout.js +8 -11
- package/dist/src/commands/docs.js +3 -14
- package/dist/src/commands/guidance.js +41 -25
- package/dist/src/commands/install.js +2 -2
- package/dist/src/commands/order.js +4 -3
- package/dist/src/commands/orders.js +4 -9
- package/dist/src/commands/pay.js +1 -1
- package/dist/src/commands/refund.js +5 -5
- package/dist/src/commands/services.js +482 -353
- package/dist/src/commands/vault.js +27 -20
- package/dist/src/main.js +32 -21
- package/dist/src/state/account_auth.js +3 -3
- package/dist/src/state/config.js +1 -1
- package/docs/agent/buyer/catalog-list.json +1 -1
- package/docs/agent/buyer/identity-and-sessions.json +3 -7
- package/docs/agent/buyer/orders-refunds.json +7 -1
- package/docs/agent/buyer/purchased-content.json +1 -1
- package/docs/agent/buyer/quickstart.json +5 -4
- package/docs/agent/buyer/rail-booking.json +87 -46
- package/docs/agent/buyer/render-hosts.json +1 -1
- package/docs/cli-reference/commands/device.md +4 -5
- package/docs/cli-reference/commands/services/invoke.md +2 -2
- package/docs/cli-reference/commands/services/read-result.md +12 -5
- package/docs/cli-reference/commands/services/run.md +2 -0
- package/package.json +1 -1
- package/skills/itpay/SKILL.md +19 -12
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ The official V3 CLI and the single ItPay entry point for services, purchases, ac
|
|
|
4
4
|
|
|
5
5
|
## One Entry Point, Several Human Intents
|
|
6
6
|
|
|
7
|
-
`itpay` is the only public CLI entry point, and `$itpay` is its user-facing Skill invocation. The Agent first distinguishes a new service request, previously purchased content, order history, or a refund. Seller workflows
|
|
7
|
+
`itpay` is the only public CLI entry point, and `$itpay` is its user-facing Skill invocation. The Agent first distinguishes a new service request, previously purchased content, order history, or a refund. Seller workflows use the existing `itpay sell` entry point.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npm install -g @itpay/cli
|
|
@@ -68,7 +68,7 @@ The local installation keeps one Ed25519 private key and a separate registration
|
|
|
68
68
|
- `services start/invoke/action/checkout/next`: generic Service Execution flow.
|
|
69
69
|
- `cart add/show/remove/clear/next`, `buy`: canonical Cart and ordinary Checkout flow.
|
|
70
70
|
- `checkout`: authoritative payment and fulfillment recovery.
|
|
71
|
-
- `services read-result`: read
|
|
71
|
+
- `services read-result`: read saved railway query pages or human-granted protected content.
|
|
72
72
|
- `order`, `orders`: exact order and account order views.
|
|
73
73
|
- `vault list/access/read`: find and read previously purchased content after time-limited human authorization.
|
|
74
74
|
- `refund create/list/get/watch/cancel`: Refund Owner flow.
|
package/dist/src/commands/buy.js
CHANGED
|
@@ -135,7 +135,7 @@ export async function runBuy(backend, config, options) {
|
|
|
135
135
|
});
|
|
136
136
|
}
|
|
137
137
|
// --- Output ---
|
|
138
|
-
if (options.jsonOutput) {
|
|
138
|
+
if (options.jsonOutput || platformKeyForHost(options.host) === "plain_chat") {
|
|
139
139
|
const envelope = buildBuyEnvelope({
|
|
140
140
|
cart,
|
|
141
141
|
checkoutID,
|
|
@@ -148,7 +148,7 @@ export async function runBuy(backend, config, options) {
|
|
|
148
148
|
...(options.agentType ? { agentType: options.agentType } : {}),
|
|
149
149
|
...(options.target ? { target: options.target } : {}),
|
|
150
150
|
});
|
|
151
|
-
writeCommandEnvelope(envelope, { jsonOutput:
|
|
151
|
+
writeCommandEnvelope(envelope, { jsonOutput: Boolean(options.jsonOutput), ...(options.output ? { output: options.output } : {}), ...(options.agentType ? { agentType: options.agentType } : {}) });
|
|
152
152
|
return {
|
|
153
153
|
kind: "checkout_rendered",
|
|
154
154
|
plan,
|
|
@@ -219,7 +219,7 @@ function buildBuyEnvelope(input) {
|
|
|
219
219
|
result,
|
|
220
220
|
handoff: presentationHandoff.handoff,
|
|
221
221
|
instruction: presentationHandoff.instruction,
|
|
222
|
-
next: { command: input.plan.afterActionCommand ?? `itpay checkout --id ${input.checkoutID} --token ${input.displayToken} --json`, reason: "
|
|
222
|
+
next: { command: input.plan.afterActionCommand ?? `itpay checkout --id ${input.checkoutID} --token ${input.displayToken} --json`, reason: "仅用户完成付款或要求查询时,读取同一笔Checkout权威状态" },
|
|
223
223
|
recovery: [],
|
|
224
224
|
};
|
|
225
225
|
}
|
|
@@ -21,7 +21,7 @@ export function runCartAdd(session, options) {
|
|
|
21
21
|
offer_id: item.offerID,
|
|
22
22
|
quantity: item.quantity,
|
|
23
23
|
},
|
|
24
|
-
instruction: "仅写入本地兼容草稿,未验证目录、价格或服务合同;不要把它当作
|
|
24
|
+
instruction: "仅写入本地兼容草稿,未验证目录、价格或服务合同;不要把它当作 服务端购物车。",
|
|
25
25
|
next: { command: "itpay cart show --local", reason: "检查本地草稿" },
|
|
26
26
|
recovery: [],
|
|
27
27
|
};
|
|
@@ -74,10 +74,10 @@ export async function runCartAddServer(options) {
|
|
|
74
74
|
},
|
|
75
75
|
instruction: serviceExecutionID
|
|
76
76
|
? "服务型项目已创建 Service Execution;先读取其当前步骤,不要直接进入普通 buy。"
|
|
77
|
-
: "普通项目已加入
|
|
77
|
+
: "普通项目已加入 服务端购物车;先检查购物车,再创建 Checkout。",
|
|
78
78
|
next: serviceExecutionID
|
|
79
79
|
? { command: `itpay services next ${serviceExecutionID} --json`, reason: "读取服务执行的当前步骤" }
|
|
80
|
-
: { command: "itpay cart next --json", reason: "检查
|
|
80
|
+
: { command: "itpay cart next --json", reason: "检查 服务端购物车" },
|
|
81
81
|
recovery: [],
|
|
82
82
|
};
|
|
83
83
|
writeCommandEnvelope(envelope, {
|
|
@@ -121,7 +121,7 @@ export async function runCartAddQuoteServer(options) {
|
|
|
121
121
|
total: formatMoney(cart.amount_minor, cart.currency),
|
|
122
122
|
},
|
|
123
123
|
instruction: "付费服务报价已加入同一 Cart;每个项目仍保持独立 Execution 和交付。",
|
|
124
|
-
next: { command: `itpay buy --cart ${cart.cart_id} --json`, reason: "
|
|
124
|
+
next: { command: `itpay buy --cart ${cart.cart_id} --json`, reason: "仅已有购买授权且项目齐全时创建一次合并付款" },
|
|
125
125
|
recovery: [{ command: "itpay cart show --json", reason: "检查当前合并 Cart" }],
|
|
126
126
|
};
|
|
127
127
|
writeCommandEnvelope(envelope, {
|
|
@@ -144,7 +144,7 @@ export function runCartRemove(session, options) {
|
|
|
144
144
|
catalog_variant_id: options.catalogVariantID,
|
|
145
145
|
offer_id: options.offerID,
|
|
146
146
|
},
|
|
147
|
-
instruction: "本地兼容草稿已更新;这不代表任何
|
|
147
|
+
instruction: "本地兼容草稿已更新;这不代表任何 服务端购物车 或 Service Execution 已变更。",
|
|
148
148
|
next: { command: "itpay cart show --local --json", reason: "检查剩余本地草稿" },
|
|
149
149
|
recovery: [],
|
|
150
150
|
}, {
|
|
@@ -176,7 +176,7 @@ export async function runCartRemoveServer(backend, session, cartItemID, options
|
|
|
176
176
|
cart_item_id: lineID,
|
|
177
177
|
remaining_item_count: cart.items.length,
|
|
178
178
|
},
|
|
179
|
-
instruction: "
|
|
179
|
+
instruction: "服务端购物车 已更新;被删除的最后一个 服务项目 对应执行会由服务端同时取消。",
|
|
180
180
|
next: { command: "itpay cart next --json", reason: "检查剩余内容" },
|
|
181
181
|
recovery: [],
|
|
182
182
|
};
|
|
@@ -198,10 +198,10 @@ export function runCartShow(session, options = {}) {
|
|
|
198
198
|
status: items.length > 0 ? "shown_local" : "local_empty",
|
|
199
199
|
result: { currency: snap.currency, items },
|
|
200
200
|
instruction: items.length > 0
|
|
201
|
-
? "这是未验证的本地兼容草稿,只能用于明确的普通商品流程;不要把它当作
|
|
201
|
+
? "这是未验证的本地兼容草稿,只能用于明确的普通商品流程;不要把它当作 服务端购物车。"
|
|
202
202
|
: "本地兼容草稿为空;从已发布目录重新选择项目。",
|
|
203
203
|
next: items.length > 0
|
|
204
|
-
? { command: "itpay buy --json", reason: "将普通本地草稿提交为
|
|
204
|
+
? { command: "itpay buy --json", reason: "将普通本地草稿提交为 服务端购物车" }
|
|
205
205
|
: { command: "itpay catalog list --json", reason: "读取已发布目录" },
|
|
206
206
|
recovery: [],
|
|
207
207
|
};
|
|
@@ -215,7 +215,7 @@ export async function runCartShowServer(backend, session, options = {}) {
|
|
|
215
215
|
writeCommandEnvelope({
|
|
216
216
|
status: "cart_handle_missing",
|
|
217
217
|
result: {},
|
|
218
|
-
instruction: "本地没有
|
|
218
|
+
instruction: "本地没有 服务端购物车 句柄;不要把本地草稿默认为服务端 Cart。",
|
|
219
219
|
next: { command: "itpay catalog list --json", reason: "读取已发布目录" },
|
|
220
220
|
recovery: [{ command: "itpay cart show --local --json", reason: "仅在明确需要时检查本地兼容草稿" }],
|
|
221
221
|
}, {
|
|
@@ -241,7 +241,7 @@ export async function runCartShowServer(backend, session, options = {}) {
|
|
|
241
241
|
},
|
|
242
242
|
instruction: items.length > 0
|
|
243
243
|
? "使用 line 或 execution 句柄继续;不要使用内部 quote lock ID。"
|
|
244
|
-
: "
|
|
244
|
+
: "服务端购物车 当前为空;从已发布目录选择项目。",
|
|
245
245
|
next: items.length > 0
|
|
246
246
|
? { command: "itpay cart next --json", reason: "取得当前首选动作" }
|
|
247
247
|
: { command: "itpay catalog list --json", reason: "读取已发布目录" },
|
|
@@ -278,7 +278,7 @@ export async function runCartAbandonServer(backend, session, options = {}) {
|
|
|
278
278
|
writeCommandEnvelope({
|
|
279
279
|
status: "cart_handle_missing",
|
|
280
280
|
result: {},
|
|
281
|
-
instruction: "本地没有
|
|
281
|
+
instruction: "本地没有 服务端购物车 句柄;未修改任何 Backend 或本地资源。",
|
|
282
282
|
next: { command: "itpay next --json", reason: "检查其他可恢复句柄" },
|
|
283
283
|
recovery: [{ command: "itpay cart clear --local --json", reason: "仅在明确放弃本地草稿和句柄时执行" }],
|
|
284
284
|
}, {
|
|
@@ -292,7 +292,7 @@ export async function runCartAbandonServer(backend, session, options = {}) {
|
|
|
292
292
|
const envelope = {
|
|
293
293
|
status: "abandoned",
|
|
294
294
|
result: { cart_id: cart.cart_id, server_abandoned: true },
|
|
295
|
-
instruction: "
|
|
295
|
+
instruction: "服务端购物车 已放弃;服务端已清空有效项目,并取消关联的未付款服务执行。",
|
|
296
296
|
next: { command: "itpay catalog list --json", reason: "仅在用户提出新需求时重新选择" },
|
|
297
297
|
recovery: [],
|
|
298
298
|
};
|
|
@@ -308,7 +308,7 @@ export async function runCartNext(backend, session, options = {}) {
|
|
|
308
308
|
writeCartNextEnvelope({
|
|
309
309
|
status: "cart_handle_missing",
|
|
310
310
|
result: {},
|
|
311
|
-
instruction: "本地没有
|
|
311
|
+
instruction: "本地没有 服务端购物车 句柄;先恢复已有资源,不要创建重复 Cart。",
|
|
312
312
|
next: { command: "itpay next --json", reason: "检查其他可恢复句柄" },
|
|
313
313
|
recovery: [{ command: "itpay services list --json", reason: "从服务端恢复当前设备的执行" }],
|
|
314
314
|
}, options);
|
|
@@ -325,7 +325,7 @@ export async function runCartNext(backend, session, options = {}) {
|
|
|
325
325
|
cart_status: cart.status,
|
|
326
326
|
service_execution_id: unquotedServiceLine.service_execution_id,
|
|
327
327
|
},
|
|
328
|
-
instruction: "该 Cart 包含
|
|
328
|
+
instruction: "该 Cart 包含 服务项目;继续 Service Execution,不要从 Cart 猜 capability。",
|
|
329
329
|
next: {
|
|
330
330
|
command: `itpay services next ${unquotedServiceLine.service_execution_id} --json`,
|
|
331
331
|
reason: "读取服务端最新执行状态",
|
|
@@ -355,7 +355,7 @@ export async function runCartNext(backend, session, options = {}) {
|
|
|
355
355
|
instruction: cart.items.some((item) => item.service_quote_lock_id)
|
|
356
356
|
? "服务报价已锁定输入和价格;确认项目齐全后使用同一 Cart 创建一次 Checkout。"
|
|
357
357
|
: "该 Cart 是普通购买流程;使用同一 Cart 创建 Checkout,不要重复添加商品。",
|
|
358
|
-
next: { command: `itpay buy --cart ${cart.cart_id} --json`, reason: "
|
|
358
|
+
next: { command: `itpay buy --cart ${cart.cart_id} --json`, reason: "仅已有购买授权时继续服务端购物车结算" },
|
|
359
359
|
recovery: [],
|
|
360
360
|
};
|
|
361
361
|
}
|
|
@@ -9,14 +9,11 @@ export async function runCatalogList(backend, options = {}) {
|
|
|
9
9
|
status: empty ? "catalog_empty" : "listed",
|
|
10
10
|
result: { catalog_version: manifest.version, services },
|
|
11
11
|
instruction: empty
|
|
12
|
-
? "
|
|
12
|
+
? "当前没有已发布服务;本次目录读取结束,不猜测服务或自动重试。"
|
|
13
13
|
: "向用户解释主服务、辅助步骤和价格;得到用户意图后再启动对应 service_id。",
|
|
14
|
-
next:
|
|
15
|
-
? { command: `itpay
|
|
16
|
-
:
|
|
17
|
-
command: `itpay services start ${services.length === 1 && firstServiceID ? firstServiceID : "<service_id>"}${jsonFlag}`,
|
|
18
|
-
reason: "启动用户选择的服务",
|
|
19
|
-
},
|
|
14
|
+
next: services.length === 1 && firstServiceID
|
|
15
|
+
? { command: `itpay services start ${firstServiceID}${jsonFlag}`, reason: "仅当当前服务符合用户请求时读取输入合同;不表示购买同意" }
|
|
16
|
+
: null,
|
|
20
17
|
recovery: [],
|
|
21
18
|
}, {
|
|
22
19
|
...options,
|
|
@@ -28,9 +25,10 @@ function summarizeService(item) {
|
|
|
28
25
|
const offer = item.variants?.[0];
|
|
29
26
|
return {
|
|
30
27
|
service_id: item.service_id ?? null,
|
|
28
|
+
...(item.service_id ? { entry: `itpay services start ${item.service_id} --json` } : {}),
|
|
31
29
|
title: item.title,
|
|
32
30
|
description: item.description ?? "",
|
|
33
|
-
...(item.service_id?.startsWith("itpay-rail-") ? { guide: "itpay docs show rail-booking
|
|
31
|
+
...(item.service_id?.startsWith("itpay-rail-") ? { guide: "itpay docs show rail-booking" } : {}),
|
|
34
32
|
...(flow ? {
|
|
35
33
|
discovery: {
|
|
36
34
|
title: flow.discovery.title,
|
|
@@ -59,6 +57,10 @@ function catalogPlainLines(version, services) {
|
|
|
59
57
|
for (const service of services) {
|
|
60
58
|
lines.push(`service: ${String(service.title)}`);
|
|
61
59
|
lines.push(` service_id: ${String(service.service_id ?? "unavailable")}`);
|
|
60
|
+
if (service.entry)
|
|
61
|
+
lines.push(` entry: ${String(service.entry)}`);
|
|
62
|
+
if (service.guide)
|
|
63
|
+
lines.push(` entry: ${String(service.guide)}`);
|
|
62
64
|
if (service.description)
|
|
63
65
|
lines.push(` description: ${String(service.description)}`);
|
|
64
66
|
const discovery = service.discovery;
|
|
@@ -10,7 +10,7 @@ import { hostCapabilities, normalizeViewer } from "../state/client_context.js";
|
|
|
10
10
|
import { DEFAULT_BASE_URL } from "../state/config.js";
|
|
11
11
|
import { buildCheckoutQRPlan } from "./buy.js";
|
|
12
12
|
import { buildCheckoutHandoff, shouldPrepareLocalCheckoutImage } from "./checkout_handoff.js";
|
|
13
|
-
import { CommandContractError, writeCommandEnvelope } from "./guidance.js";
|
|
13
|
+
import { CommandContractError, plainValueLines, writeCommandEnvelope } from "./guidance.js";
|
|
14
14
|
import { resolvePresentation, resolveRelay } from "./presentation.js";
|
|
15
15
|
export async function runCheckoutPresentation(backend, options) {
|
|
16
16
|
const present = options.present;
|
|
@@ -81,10 +81,6 @@ export async function runCheckoutPresentation(backend, options) {
|
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
83
|
const envelope = pendingCheckoutEnvelope(presentation, checkoutURL, plan, nextCommand, options.agentType, options.target);
|
|
84
|
-
const plainResult = checkoutPlainResult(envelope.result);
|
|
85
|
-
if (!options.jsonOutput && platformKeyForHost(host) === "terminal") {
|
|
86
|
-
plainResult.push("qr:", await renderTerminalQR(checkoutURL, "terminal"));
|
|
87
|
-
}
|
|
88
84
|
// W5: deterministic presentation plan — business state first, then declared
|
|
89
85
|
// capabilities. `present` is the explicit display request; without it (or
|
|
90
86
|
// under --json alone) nothing opens.
|
|
@@ -143,6 +139,10 @@ export async function runCheckoutPresentation(backend, options) {
|
|
|
143
139
|
wait_for: decision.communication.wait_for,
|
|
144
140
|
must_convey: decision.communication.must_convey,
|
|
145
141
|
};
|
|
142
|
+
const plainResult = checkoutPlainResult(envelope.result);
|
|
143
|
+
if (!options.jsonOutput && platformKeyForHost(host) === "terminal") {
|
|
144
|
+
plainResult.push("qr:", await renderTerminalQR(checkoutURL, "terminal"));
|
|
145
|
+
}
|
|
146
146
|
writeCommandEnvelope(envelope, {
|
|
147
147
|
...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
|
|
148
148
|
...(options.output ? { output: options.output } : {}),
|
|
@@ -213,11 +213,8 @@ function pendingCheckoutEnvelope(presentation, checkoutURL, plan, nextCommand, a
|
|
|
213
213
|
instruction: railPassengersPending
|
|
214
214
|
? `${presentationHandoff.instruction} 请用户在受保护网页填写乘车人并确认报价;姓名、证件和手机号只在网页填写,不要贴到对话中。座位偏好仅为购票请求、购票时才提交给供应商且不保证满足;无法逐人提交的偏好将自动分配座位,以实际出票为准。`
|
|
215
215
|
: presentationHandoff.instruction,
|
|
216
|
-
next: { command: nextCommand, reason: "
|
|
217
|
-
recovery: [
|
|
218
|
-
command: nextCommand,
|
|
219
|
-
reason: "付款时限由服务端统一确定,刷新不会延长;到期未支付自动取消后须重新核价下单,不要重发旧付款动作",
|
|
220
|
-
}],
|
|
216
|
+
next: { command: nextCommand, reason: "展示后停止;仅用户完成操作或要求核对时读取同一 Checkout" },
|
|
217
|
+
recovery: [],
|
|
221
218
|
};
|
|
222
219
|
}
|
|
223
220
|
function terminalCheckoutEnvelope(presentation) {
|
|
@@ -263,7 +260,7 @@ function checkoutNeedsHumanHandoff(status) {
|
|
|
263
260
|
return !new Set(["payment_succeeded", "completed", "failed", "expired", "refunded"]).has(status);
|
|
264
261
|
}
|
|
265
262
|
function checkoutPlainResult(result) {
|
|
266
|
-
return
|
|
263
|
+
return plainValueLines(result);
|
|
267
264
|
}
|
|
268
265
|
function formatMoney(amountMinor, currency) {
|
|
269
266
|
return `${(amountMinor / 100).toFixed(2)} ${currency}`;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import {
|
|
5
|
-
import { CommandContractError, writeCommandEnvelope } from "./guidance.js";
|
|
4
|
+
import { CommandContractError, shellArgument, writeCommandEnvelope } from "./guidance.js";
|
|
6
5
|
const commandDir = dirname(fileURLToPath(import.meta.url));
|
|
7
6
|
export function runDocsList(options = {}) {
|
|
8
7
|
const topics = loadDocs().map(({ topic, title, purpose }) => ({ topic, title, purpose }));
|
|
@@ -23,7 +22,7 @@ export function runDocsShow(topic, options = {}) {
|
|
|
23
22
|
if (!doc) {
|
|
24
23
|
throw new CommandContractError("doc_not_found", `doc topic not found: ${topic}`, "使用稳定 topic 名称;不要根据标题猜 topic。", [
|
|
25
24
|
{ command: "itpay docs list --json", reason: "列出全部 topic" },
|
|
26
|
-
{ command: `itpay docs search ${
|
|
25
|
+
{ command: `itpay docs search ${shellArgument(normalized || "topic")} --json`, reason: "按关键词重新搜索" },
|
|
27
26
|
]);
|
|
28
27
|
}
|
|
29
28
|
const envelope = {
|
|
@@ -33,14 +32,7 @@ export function runDocsShow(topic, options = {}) {
|
|
|
33
32
|
next: null,
|
|
34
33
|
recovery: [],
|
|
35
34
|
};
|
|
36
|
-
|
|
37
|
-
writeCommandEnvelope(envelope, options);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const out = resolveOutput(options.output);
|
|
41
|
-
out("shown\n");
|
|
42
|
-
out(`${JSON.stringify(doc, null, 2)}\n`);
|
|
43
|
-
out(`instruction: ${envelope.instruction}\n`);
|
|
35
|
+
writeCommandEnvelope(envelope, options);
|
|
44
36
|
}
|
|
45
37
|
export function runDocsSearch(query, options = {}) {
|
|
46
38
|
const normalized = query.trim().toLowerCase();
|
|
@@ -103,6 +95,3 @@ function parseDoc(raw, file) {
|
|
|
103
95
|
function searchableText(doc) {
|
|
104
96
|
return [doc.topic, doc.title, doc.purpose, ...(doc.search_terms ?? [])].join(" ").toLowerCase();
|
|
105
97
|
}
|
|
106
|
-
function shellWord(value) {
|
|
107
|
-
return /^[a-zA-Z0-9._-]+$/.test(value) ? value : JSON.stringify(value);
|
|
108
|
-
}
|
|
@@ -15,56 +15,79 @@ export class CommandContractError extends Error {
|
|
|
15
15
|
code;
|
|
16
16
|
instruction;
|
|
17
17
|
recovery;
|
|
18
|
-
|
|
18
|
+
interaction;
|
|
19
|
+
constructor(code, message, instruction, recovery, interaction) {
|
|
19
20
|
super(message);
|
|
20
21
|
this.code = code;
|
|
21
22
|
this.instruction = instruction;
|
|
22
23
|
this.recovery = recovery;
|
|
24
|
+
this.interaction = interaction;
|
|
23
25
|
this.name = "CommandContractError";
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
// Domain projections and pages own byte limits; serialization never truncates facts.
|
|
29
|
+
export function plainValueLines(value, label = "", depth = 0) {
|
|
30
|
+
const indent = " ".repeat(depth);
|
|
31
|
+
if (value === null || typeof value !== "object")
|
|
32
|
+
return [`${indent}${label ? `${label}: ` : ""}${String(value ?? "unknown")}`];
|
|
33
|
+
const lines = label ? [`${indent}${label}:`] : [];
|
|
34
|
+
const entries = Array.isArray(value) ? value.map((v, i) => [String(i + 1), v]) : Object.entries(value);
|
|
35
|
+
for (const [key, child] of entries)
|
|
36
|
+
lines.push(...plainValueLines(child, key, depth + (label ? 1 : 0)));
|
|
37
|
+
if (!entries.length)
|
|
38
|
+
lines.push(`${indent} (无记录)`);
|
|
39
|
+
return lines;
|
|
40
|
+
}
|
|
41
|
+
export function shellArgument(value) {
|
|
42
|
+
if (/^[\p{L}\p{N}._:=/-]+$/u.test(value))
|
|
43
|
+
return value;
|
|
44
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
45
|
+
}
|
|
30
46
|
export function writeCommandEnvelope(value, options = {}) {
|
|
31
47
|
const out = resolveOutput(options.output);
|
|
32
48
|
const agentType = options.agentType ?? declaredAgentType();
|
|
33
49
|
const qualified = qualifyEnvelope(value, agentType);
|
|
34
50
|
if (options.jsonOutput) {
|
|
35
51
|
const pretty = JSON.stringify(qualified, null, 2) + "\n";
|
|
36
|
-
out(pretty
|
|
52
|
+
out(pretty);
|
|
37
53
|
return;
|
|
38
54
|
}
|
|
39
55
|
out(`${qualified.status}\n`);
|
|
40
56
|
const facts = "error" in qualified ? qualified.error : qualified.result ?? {};
|
|
41
57
|
if (options.plainResult) {
|
|
42
|
-
for (const line of options.plainResult)
|
|
43
|
-
|
|
58
|
+
for (const line of options.plainResult) {
|
|
59
|
+
const command = line.match(/^(\s*(?:command|reader|entry): )(itpay .*)$/);
|
|
60
|
+
out(`${command ? command[1] + qualifyBackendCommand(qualifyItPayCommand(command[2], agentType)) : line}\n`);
|
|
61
|
+
}
|
|
44
62
|
}
|
|
45
63
|
else {
|
|
46
64
|
for (const [key, fact] of Object.entries(facts)) {
|
|
47
|
-
|
|
65
|
+
for (const line of plainValueLines(fact, key))
|
|
66
|
+
out(`${line}\n`);
|
|
48
67
|
}
|
|
49
68
|
if ("error" in qualified && qualified.result) {
|
|
50
69
|
for (const [key, fact] of Object.entries(qualified.result)) {
|
|
51
|
-
|
|
70
|
+
for (const line of plainValueLines(fact, key))
|
|
71
|
+
out(`${line}\n`);
|
|
52
72
|
}
|
|
53
73
|
}
|
|
54
74
|
}
|
|
55
75
|
if ("handoff" in qualified && qualified.handoff) {
|
|
56
76
|
for (const [key, fact] of Object.entries(qualified.handoff)) {
|
|
57
|
-
|
|
77
|
+
for (const line of plainValueLines(fact, `handoff.${key}`))
|
|
78
|
+
out(`${line}\n`);
|
|
58
79
|
}
|
|
59
80
|
}
|
|
60
81
|
out(`instruction: ${qualified.instruction}\n`);
|
|
61
82
|
if (qualified.next)
|
|
62
|
-
out(`next: ${qualified.next.command}\n`);
|
|
83
|
+
out(`next: ${qualified.next.command}\n when: ${qualified.next.reason}\n`);
|
|
63
84
|
if ("interaction" in qualified && qualified.interaction) {
|
|
64
|
-
|
|
85
|
+
for (const line of plainValueLines(qualified.interaction, "interaction"))
|
|
86
|
+
out(`${line}\n`);
|
|
65
87
|
}
|
|
66
88
|
if ("communication" in qualified && qualified.communication) {
|
|
67
|
-
|
|
89
|
+
for (const line of plainValueLines(qualified.communication, "communication"))
|
|
90
|
+
out(`${line}\n`);
|
|
68
91
|
}
|
|
69
92
|
if (qualified.recovery.length > 0) {
|
|
70
93
|
out("recovery:\n");
|
|
@@ -123,19 +146,12 @@ export function errorRecoveryActions(error) {
|
|
|
123
146
|
return [{
|
|
124
147
|
id: "inspect_service_execution",
|
|
125
148
|
label: "Inspect Service Execution before checkout",
|
|
126
|
-
command: "itpay services
|
|
149
|
+
command: "itpay services list --json",
|
|
127
150
|
}];
|
|
128
151
|
}
|
|
129
|
-
if (error.code === "cart_item_locked"
|
|
130
|
-
return [
|
|
131
|
-
|
|
132
|
-
{ id: "continue_checkout", label: "Continue the last locally remembered checkout", command: "itpay checkout" },
|
|
133
|
-
{
|
|
134
|
-
id: "recover_service_execution",
|
|
135
|
-
label: "List recoverable Service Executions if the local handoff is missing",
|
|
136
|
-
command: "itpay services list",
|
|
137
|
-
},
|
|
138
|
-
];
|
|
152
|
+
if (error.code === "cart_item_locked") {
|
|
153
|
+
return [{ id: "show_cart", label: "Inspect the locked cart", command: "itpay cart show --json",
|
|
154
|
+
reason: "读取已有购物内容和订单锁,不创建替代 checkout。" }];
|
|
139
155
|
}
|
|
140
156
|
if (error.status === 404) {
|
|
141
157
|
return [{
|
|
@@ -17,7 +17,7 @@ const INSTRUCTIONS = {
|
|
|
17
17
|
"codex-cli": "在 Codex CLI 中始终传这个 Agent Type;付款交接只应显示在用户可见终端。",
|
|
18
18
|
"claude-code-desktop": "在 Claude Code Desktop 中始终传这个 Agent Type;付款时把返回的二维码和链接实际展示到当前对话。",
|
|
19
19
|
"claude-code-cli": "在 Claude Code CLI 中始终传这个 Agent Type;付款交接只应显示在用户可见终端。",
|
|
20
|
-
workbuddy: "在 WorkBuddy 中始终传这个 Agent Type。运行会持久化 ~/.itpay-v3 的 ItPay Bash 命令前,把 Shell 工具参数 dangerouslyDisableSandbox 设为 true;用户拒绝权限时停止,不要切换 Node、删除 identity
|
|
20
|
+
workbuddy: "在 WorkBuddy 中始终传这个 Agent Type。运行会持久化 ~/.itpay-v3 的 ItPay Bash 命令前,把 Shell 工具参数 dangerouslyDisableSandbox 设为 true;用户拒绝权限时停止,不要切换 Node、删除 identity;锁忙只用官方 device repair-lock。Checkout 返回已渲染的付款 Card Link 和 present_files action;原样执行一次 action 打开 handoff.url,不要检查本地二维码文件。",
|
|
21
21
|
zcode: "在 ZCode 中始终传这个 Agent Type。Checkout 或授权返回 handoff.url 后,立即用 ZCode 内置浏览器打开;不要只粘贴文字链接或重建二维码。只有内置浏览器不可用时才展示同一个可点击链接。",
|
|
22
22
|
"doubao-work": "在豆包工作中始终传这个 Agent Type。Checkout 或授权返回后,同时展示 handoff.url 和 handoff.qr_image_url 两个有标签的官方入口;优先让用户在手机上直接打开链接,二维码供保存或另一台设备扫码。",
|
|
23
23
|
"kimi-code": "保持 kimi-code Agent Type,使用标准 CLI 命令和终端付款交接;不要切换到其他平台身份。",
|
|
@@ -53,7 +53,7 @@ export function runInstall(target, options = {}) {
|
|
|
53
53
|
command: `itpay --agent-type ${normalized} readyz --json`,
|
|
54
54
|
reason: "验证当前官方 ItPay API 的可用性",
|
|
55
55
|
},
|
|
56
|
-
recovery: [
|
|
56
|
+
recovery: [],
|
|
57
57
|
}, options);
|
|
58
58
|
}
|
|
59
59
|
function isInstallAgentType(value) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { refundStateEnvelope } from "./refund.js";
|
|
1
2
|
import { formatMoney } from "../render/output.js";
|
|
2
3
|
import { appendFeedbackPostmortemInstruction, writeCommandEnvelope } from "./guidance.js";
|
|
3
4
|
export async function runOrder(backend, orderID, options = {}) {
|
|
@@ -19,7 +20,7 @@ function orderEnvelope(order, delivery, lockedRefund) {
|
|
|
19
20
|
let instruction = "订单状态已读取;当前没有可用交付入口。";
|
|
20
21
|
let next = null;
|
|
21
22
|
if (lockedRefund) {
|
|
22
|
-
instruction =
|
|
23
|
+
instruction = refundStateEnvelope(lockedRefund, "shown").instruction;
|
|
23
24
|
if (!refundTerminal) {
|
|
24
25
|
next = { command: `itpay refund get ${lockedRefund.refund_request_id} --json`, reason: "读取退款的服务器状态" };
|
|
25
26
|
}
|
|
@@ -41,9 +42,9 @@ function orderEnvelope(order, delivery, lockedRefund) {
|
|
|
41
42
|
else if (order.status === "pending_payment") {
|
|
42
43
|
const remaining = typeof order.payment_remaining_seconds === "number" && order.payment_remaining_seconds <= 0;
|
|
43
44
|
instruction = remaining
|
|
44
|
-
? "
|
|
45
|
+
? "付款时限已到,但订单权威状态仍为待付款;读取同一订单核对到账或终止结果。不要重复支付、重建订单或推测已取消,刷新不会延长期限。"
|
|
45
46
|
: `先告诉用户订单正在等待付款${order.payment_deadline_at ? `,付款截止时间为 ${order.payment_deadline_at}` : ""},超时未支付会被自动取消;不要创建替代订单,也不要重复发起支付授权。`;
|
|
46
|
-
next = { command: `itpay order ${order.order_id} --json`, reason: "
|
|
47
|
+
next = { command: `itpay order ${order.order_id} --json`, reason: "展示后停止;仅用户说已付款或要求核对时读取同一订单,到期可核对原单" };
|
|
47
48
|
}
|
|
48
49
|
else if (!["delivered", "refunded", "failed", "cancelled"].includes(order.status)) {
|
|
49
50
|
instruction = "先告诉用户订单仍在处理,已记录的付款和订单不需要重复创建;稍后查询同一订单,不要创建替代订单。";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { HttpError } from "../client/http.js";
|
|
2
2
|
import { formatMoney } from "../render/output.js";
|
|
3
3
|
import { resolveOutput } from "../render/sink.js";
|
|
4
|
-
import { CommandContractError, writeCommandEnvelope } from "./guidance.js";
|
|
5
|
-
import { accessContextInstruction,
|
|
4
|
+
import { CommandContractError, shellArgument, writeCommandEnvelope } from "./guidance.js";
|
|
5
|
+
import { accessContextInstruction, vaultAccessGuidance } from "./vault.js";
|
|
6
6
|
const ORDER_STATUSES = new Set([
|
|
7
7
|
"pending_payment",
|
|
8
8
|
"paid",
|
|
@@ -30,8 +30,8 @@ export async function runListOrders(backend, config, options) {
|
|
|
30
30
|
writeCommandEnvelope({
|
|
31
31
|
status: "human_authorization_required",
|
|
32
32
|
result: { intent: "list_purchase_history" },
|
|
33
|
-
instruction:
|
|
34
|
-
|
|
33
|
+
instruction: `需要用户确认一次身份和只读权限。按当前动作或待填模板生成官方入口;用户完成后重新运行原始 orders 命令。${accessContextInstruction(options)}`,
|
|
34
|
+
...vaultAccessGuidance(undefined, options),
|
|
35
35
|
recovery: [],
|
|
36
36
|
}, {
|
|
37
37
|
...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
|
|
@@ -92,8 +92,3 @@ function ordersPageCommand(cursor, options) {
|
|
|
92
92
|
parts.push("--json");
|
|
93
93
|
return parts.join(" ");
|
|
94
94
|
}
|
|
95
|
-
function shellArgument(value) {
|
|
96
|
-
if (/^[\p{L}\p{N}._:=/-]+$/u.test(value))
|
|
97
|
-
return value;
|
|
98
|
-
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
99
|
-
}
|
package/dist/src/commands/pay.js
CHANGED
|
@@ -53,7 +53,7 @@ function payEnvelope(intent, options) {
|
|
|
53
53
|
instruction: payInstruction(options, verified, terminal, hasAction, amount),
|
|
54
54
|
next: {
|
|
55
55
|
command: `itpay checkout --id ${options.checkoutID} --token ${options.displayToken} --json`,
|
|
56
|
-
reason: verified ? "读取权威订单和履约状态" : "
|
|
56
|
+
reason: verified ? "读取权威订单和履约状态" : "展示后停止;仅用户完成付款或要求核对时读取同一 Checkout",
|
|
57
57
|
},
|
|
58
58
|
recovery: [],
|
|
59
59
|
};
|
|
@@ -12,11 +12,11 @@ export async function runRefund(backend, config, options) {
|
|
|
12
12
|
plainResult: Object.entries(envelope.result).map(([key, value]) => `${key}: ${typeof value === "string" ? value : JSON.stringify(value)}`),
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
-
function refundStateEnvelope(refund, status) {
|
|
15
|
+
export function refundStateEnvelope(refund, status) {
|
|
16
16
|
const terminal = ["succeeded", "failed", "cancelled", "rejected"].includes(refund.status);
|
|
17
17
|
let instruction = refund.decision_mode === "manual"
|
|
18
18
|
? "先告诉用户退款已进入人工审核,原交付保持冻结;人工审核不等于拒绝,等待服务器决定,不要重复申请或承诺结果。"
|
|
19
|
-
: "先告诉用户退款申请已经记录,原交付已冻结;自动路径表示系统会继续处理,但只有最终 succeeded
|
|
19
|
+
: "先告诉用户退款申请已经记录,原交付已冻结;自动路径表示系统会继续处理,但只有最终 succeeded 才能确认退款成功。然后只跟踪同一退款,不读取交付、不创建授权或重复申请。";
|
|
20
20
|
if (!refund.access_locked)
|
|
21
21
|
instruction = "先告诉用户退款当前没有锁定交付;按服务器事实解释当前状态,不要自行推断退款结果、到账时间或交付资格。";
|
|
22
22
|
if (refund.status === "succeeded")
|
|
@@ -73,7 +73,7 @@ export async function runListRefunds(backend, options) {
|
|
|
73
73
|
: "该订单没有退款记录;确认用户确实要求退款后再创建。",
|
|
74
74
|
next: selected
|
|
75
75
|
? { command: `itpay refund get ${selected.refund_request_id} --json`, reason: active ? "读取活跃退款" : "读取最新退款" }
|
|
76
|
-
:
|
|
76
|
+
: null,
|
|
77
77
|
recovery: [],
|
|
78
78
|
};
|
|
79
79
|
writeCommandEnvelope(envelope, {
|
|
@@ -96,13 +96,13 @@ export async function runGetRefund(backend, refundID, options = {}) {
|
|
|
96
96
|
export async function runCancelRefund(backend, refundID, reason, options = {}) {
|
|
97
97
|
const refund = await backend.cancelRefund(refundID, reason?.trim() || "buyer_cancelled");
|
|
98
98
|
const envelope = {
|
|
99
|
-
status:
|
|
99
|
+
status: refund.status,
|
|
100
100
|
result: {
|
|
101
101
|
refund_request_id: refund.refund_request_id,
|
|
102
102
|
order_id: refund.order_id,
|
|
103
103
|
access_locked: refund.access_locked,
|
|
104
104
|
},
|
|
105
|
-
instruction: "
|
|
105
|
+
instruction: refund.status === "cancelled" ? "Backend确认退款已取消;如需交付,重新进入原订单核对当前权限,旧grant不自动复活;后端要求时取得新的授权。" : "Backend未确认取消;按返回的退款权威状态核对原订单,不重复提交取消。",
|
|
106
106
|
next: { command: `itpay order ${refund.order_id} --json`, reason: "确认订单访问状态" },
|
|
107
107
|
recovery: [],
|
|
108
108
|
};
|