@itpay/cli 2.1.40 → 2.1.41

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 (148) hide show
  1. package/README.md +5 -12
  2. package/dist/src/client/backend.js +2 -38
  3. package/dist/src/client/http.js +0 -2
  4. package/dist/src/commands/buy.js +58 -17
  5. package/dist/src/commands/cart.js +15 -17
  6. package/dist/src/commands/catalog.js +14 -39
  7. package/dist/src/commands/checkout.js +5 -146
  8. package/dist/src/commands/checkout_handoff.js +61 -11
  9. package/dist/src/commands/docs.js +18 -7
  10. package/dist/src/commands/guidance.js +134 -1
  11. package/dist/src/commands/install.js +3 -6
  12. package/dist/src/commands/order.js +3 -34
  13. package/dist/src/commands/orders.js +9 -4
  14. package/dist/src/commands/pay.js +1 -1
  15. package/dist/src/commands/readyz.js +1 -1
  16. package/dist/src/commands/refund.js +57 -13
  17. package/dist/src/commands/services.js +1068 -14
  18. package/dist/src/commands/skill.js +5 -8
  19. package/dist/src/commands/vault.js +20 -27
  20. package/dist/src/commands/vault_handoff.js +0 -18
  21. package/dist/src/main.js +43 -330
  22. package/dist/src/render/markdown.js +3 -0
  23. package/dist/src/render/qr.js +1 -1
  24. package/dist/src/state/cart_session.js +1 -4
  25. package/dist/src/state/client_context.js +0 -72
  26. package/dist/src/state/config.js +14 -44
  27. package/dist/src/state/device_authority.js +41 -141
  28. package/docs/agent/buyer/cart-checkout.json +6 -8
  29. package/docs/agent/buyer/catalog-list.json +9 -29
  30. package/docs/agent/buyer/identity-and-sessions.json +9 -26
  31. package/docs/agent/buyer/install-and-setup.json +9 -10
  32. package/docs/agent/buyer/orders-refunds.json +9 -25
  33. package/docs/agent/buyer/payment-flow.json +4 -6
  34. package/docs/agent/buyer/purchased-content.json +4 -18
  35. package/docs/agent/buyer/quickstart.json +17 -26
  36. package/docs/agent/buyer/render-hosts.json +12 -23
  37. package/docs/agent/buyer/service-feedback.json +3 -4
  38. package/docs/cli-reference/agent-types.md +2 -7
  39. package/docs/cli-reference/commands/buy.md +1 -2
  40. package/docs/cli-reference/commands/cart/add.md +1 -5
  41. package/docs/cli-reference/commands/cart/clear.md +1 -1
  42. package/docs/cli-reference/commands/cart/index.md +1 -1
  43. package/docs/cli-reference/commands/cart/next.md +1 -5
  44. package/docs/cli-reference/commands/cart/remove.md +1 -1
  45. package/docs/cli-reference/commands/cart/show.md +1 -1
  46. package/docs/cli-reference/commands/catalog/index.md +1 -1
  47. package/docs/cli-reference/commands/catalog/list.md +2 -2
  48. package/docs/cli-reference/commands/checkout.md +2 -12
  49. package/docs/cli-reference/commands/device.md +11 -18
  50. package/docs/cli-reference/commands/docs/index.md +1 -1
  51. package/docs/cli-reference/commands/docs/list.md +1 -1
  52. package/docs/cli-reference/commands/docs/search.md +1 -1
  53. package/docs/cli-reference/commands/docs/show.md +1 -1
  54. package/docs/cli-reference/commands/install.md +6 -8
  55. package/docs/cli-reference/commands/next.md +1 -1
  56. package/docs/cli-reference/commands/order.md +1 -1
  57. package/docs/cli-reference/commands/pay.md +1 -1
  58. package/docs/cli-reference/commands/readyz.md +11 -11
  59. package/docs/cli-reference/commands/refund/cancel.md +1 -1
  60. package/docs/cli-reference/commands/refund/create.md +1 -1
  61. package/docs/cli-reference/commands/refund/get.md +2 -2
  62. package/docs/cli-reference/commands/refund/index.md +1 -1
  63. package/docs/cli-reference/commands/refund/list.md +1 -1
  64. package/docs/cli-reference/commands/refund/watch.md +1 -1
  65. package/docs/cli-reference/commands/services/action.md +3 -24
  66. package/docs/cli-reference/commands/services/checkout.md +2 -17
  67. package/docs/cli-reference/commands/services/events.md +1 -1
  68. package/docs/cli-reference/commands/services/get.md +2 -4
  69. package/docs/cli-reference/commands/services/index.md +3 -4
  70. package/docs/cli-reference/commands/services/invoke.md +4 -6
  71. package/docs/cli-reference/commands/services/list.md +1 -1
  72. package/docs/cli-reference/commands/services/next.md +12 -78
  73. package/docs/cli-reference/commands/services/quote.md +4 -4
  74. package/docs/cli-reference/commands/services/read-result.md +6 -17
  75. package/docs/cli-reference/commands/services/start.md +3 -5
  76. package/docs/cli-reference/commands/vault/access.md +1 -5
  77. package/docs/cli-reference/conventions.md +6 -4
  78. package/docs/cli-reference/index.md +1 -10
  79. package/docs/skill-bundle-rollout/01-mcp-authentication.md +134 -0
  80. package/docs/skill-bundle-rollout/02-platform-bundle-repositories.md +210 -0
  81. package/docs/skill-bundle-rollout/03-platform-publishing.md +161 -0
  82. package/docs/skill-bundle-rollout/04-first-wave-platforms.md +69 -0
  83. package/docs/skill-bundle-rollout/05-sync-operations.md +62 -0
  84. package/docs/skill-bundle-rollout/README.md +151 -0
  85. package/package.json +12 -20
  86. package/skills/itpay/SKILL.md +102 -110
  87. package/assets/sell-preview/index.html +0 -6
  88. package/assets/sell-preview/preview.js +0 -9
  89. package/bin/darwin-amd64/itpay-sell +0 -0
  90. package/bin/darwin-arm64/itpay-sell +0 -0
  91. package/bin/linux-amd64/itpay-sell +0 -0
  92. package/bin/linux-arm64/itpay-sell +0 -0
  93. package/bin/windows-amd64/itpay-sell.exe +0 -0
  94. package/dist/src/commands/aftercare.js +0 -12
  95. package/dist/src/commands/presentation.js +0 -153
  96. package/dist/src/commands/rail_catalog.js +0 -1
  97. package/dist/src/commands/services/actions.js +0 -133
  98. package/dist/src/commands/services/commerce.js +0 -197
  99. package/dist/src/commands/services/lifecycle.js +0 -418
  100. package/dist/src/commands/services/results.js +0 -130
  101. package/dist/src/render/browser.js +0 -65
  102. package/dist/src/render/checkout_handoff.js +0 -101
  103. package/dist/src/render/checkout_plan.js +0 -47
  104. package/dist/src/sell/commands.js +0 -80
  105. package/dist/src/sell/contract.js +0 -86
  106. package/dist/src/sell/local.js +0 -260
  107. package/dist/src/sell/mcp.js +0 -49
  108. package/dist/src/sell/preview.js +0 -70
  109. package/dist/src/sell/sync.js +0 -145
  110. package/dist/src/services/adapter.js +0 -1
  111. package/dist/src/services/presentation.js +0 -198
  112. package/dist/src/services/rail/actions.js +0 -51
  113. package/dist/src/services/rail/adapter.js +0 -27
  114. package/dist/src/services/rail/aftercare.js +0 -45
  115. package/dist/src/services/rail/catalog.js +0 -261
  116. package/dist/src/services/rail/checkout.js +0 -19
  117. package/dist/src/services/rail/fulfillment.js +0 -76
  118. package/dist/src/services/rail/guide.js +0 -110
  119. package/dist/src/services/rail/invocation.js +0 -54
  120. package/dist/src/services/rail/page.js +0 -76
  121. package/dist/src/services/rail/pagination.js +0 -6
  122. package/dist/src/services/rail/query.js +0 -71
  123. package/dist/src/services/rail/readers.js +0 -107
  124. package/dist/src/services/rail/reading.js +0 -125
  125. package/dist/src/services/rail/recovery.js +0 -51
  126. package/dist/src/services/rail/results.js +0 -378
  127. package/dist/src/services/rail/review.js +0 -268
  128. package/dist/src/services/rail/saved.js +0 -49
  129. package/dist/src/services/rail/wait.js +0 -11
  130. package/dist/src/services/registry.js +0 -31
  131. package/dist/src/services/schema.js +0 -21
  132. package/dist/src/services/shared/envelope.js +0 -184
  133. package/dist/src/services/shared/input.js +0 -39
  134. package/dist/src/services/shared/pagination.js +0 -28
  135. package/dist/src/services/shared/platform.js +0 -365
  136. package/dist/src/services/shared/refund.js +0 -40
  137. package/dist/src/services/shared/workflow.js +0 -28
  138. package/dist/src/state/account_auth.js +0 -347
  139. package/dist/src/state/task_journal.js +0 -90
  140. package/docs/agent/buyer/rail-booking.json +0 -176
  141. package/docs/agent/buyer/rail-fast-checkout.json +0 -48
  142. package/docs/cli-reference/commands/aftercare.md +0 -15
  143. package/docs/cli-reference/commands/auth.md +0 -96
  144. package/docs/cli-reference/commands/sell.md +0 -1364
  145. package/docs/cli-reference/commands/services/page.md +0 -71
  146. package/docs/cli-reference/commands/services/run.md +0 -152
  147. package/docs/sell.md +0 -84
  148. /package/bin/{itp.mjs → itp} +0 -0
@@ -5,40 +5,11 @@ import { buildAgentChatHandoff } from "../render/markdown.js";
5
5
  import { platformKeyForHost } from "../render/plan.js";
6
6
  import { renderTerminalQR } from "../render/qr.js";
7
7
  import { localizeCardURL, normalizeCardLocale } from "../render/locale.js";
8
- import { openInSystemBrowser } from "../render/browser.js";
9
- import { hostCapabilities, normalizeViewer } from "../state/client_context.js";
10
8
  import { DEFAULT_BASE_URL } from "../state/config.js";
11
9
  import { buildCheckoutQRPlan } from "./buy.js";
12
10
  import { buildCheckoutHandoff, shouldPrepareLocalCheckoutImage } from "./checkout_handoff.js";
13
- import { CommandContractError, plainValueLines, writeCommandEnvelope } from "./guidance.js";
14
- import { resolvePresentation, resolveRelay } from "./presentation.js";
11
+ import { writeCommandEnvelope } from "./guidance.js";
15
12
  export async function runCheckoutPresentation(backend, options) {
16
- const present = options.present;
17
- if (present !== undefined && !["auto", "browser", "image", "link", "none"].includes(present)) {
18
- throw new CommandContractError("present_invalid", `--present must be one of auto|browser|image|link|none`, "使用支持的展示方式;未知参数已被拒绝。", []);
19
- }
20
- const relayRequested = Boolean(options.relayOption || options.confirmRelay || options.requestKey || options.relayStatus);
21
- if (relayRequested && options.present && options.present !== "none") {
22
- throw new CommandContractError("present_relay_conflict", "--present and --relay-* options are mutually exclusive", "展示和消息转发二选一;不要在同一次调用中同时开页面和发消息。", []);
23
- }
24
- if (options.noOpen && options.present === "browser") {
25
- throw new CommandContractError("present_conflict", "--present browser conflicts with --no-open", "--no-open 与浏览器展示冲突;改用 --present link 或 image。", []);
26
- }
27
- if (options.relayStatus) {
28
- // Read-only relay status. The backend exposes no relay routes yet, so the
29
- // honest answer is unavailable — never a resend, never a guessed state.
30
- writeCommandEnvelope({
31
- status: "relay_unavailable",
32
- result: { relay_id: options.relayStatus },
33
- instruction: "当前 Backend 未提供消息转发状态查询;不要重发。读取原订单/Checkout 状态核对。",
34
- next: { command: `itpay checkout --id ${options.checkoutID} --token ${options.displayToken} --json`, reason: "读取原 Checkout 当前状态" },
35
- recovery: [],
36
- }, {
37
- ...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
38
- ...(options.output ? { output: options.output } : {}),
39
- });
40
- return;
41
- }
42
13
  const locale = normalizeCardLocale(options.locale);
43
14
  const presentation = await backend.getCheckoutPresentation(options.checkoutID, options.displayToken, locale === "en" ? locale : undefined);
44
15
  const host = options.host ?? "terminal";
@@ -51,13 +22,12 @@ export async function runCheckoutPresentation(backend, options) {
51
22
  });
52
23
  return;
53
24
  }
54
- const checkoutURL = savedCheckoutURLOrFallback(options.savedCheckoutURL, options.checkoutID, options.displayToken, checkoutPageURL(options.baseURL, options.checkoutID, options.displayToken));
25
+ const checkoutURL = checkoutPageURL(options.baseURL, options.checkoutID, options.displayToken);
55
26
  const cardURL = localizeCardURL(absolutePublicURL(options.baseURL, presentation.card_url ?? checkoutCardURL(options.baseURL, options.checkoutID, options.displayToken)), locale);
56
27
  const qrPNGURL = absolutePublicURL(options.baseURL, presentation.card_png_url ?? presentation.qr_png_url ?? checkoutCardPNGURL(options.baseURL, options.checkoutID, options.displayToken));
57
28
  const localizedPNGURL = localizeCardURL(qrPNGURL, locale);
58
29
  const nextCommand = `itpay checkout --id ${options.checkoutID} --token ${options.displayToken}${locale === "en" ? " --locale en" : ""} --json`;
59
30
  const plan = buildCheckoutQRPlan({
60
- localSimulation: backend.localSimulation,
61
31
  host,
62
32
  checkoutID: options.checkoutID,
63
33
  checkoutURL,
@@ -82,64 +52,6 @@ export async function runCheckoutPresentation(backend, options) {
82
52
  });
83
53
  }
84
54
  const envelope = pendingCheckoutEnvelope(presentation, checkoutURL, plan, nextCommand, options.agentType, options.target);
85
- // W5: deterministic presentation plan — business state first, then declared
86
- // capabilities. `present` is the explicit display request; without it (or
87
- // under --json alone) nothing opens.
88
- // An explicit --viewer (including "unknown") wins over the host default.
89
- const viewer = options.viewer ? normalizeViewer(options.viewer) : (host === "terminal" ? "desktop" : "unknown");
90
- const locality = ["terminal", "codex", "claude-code"].includes(host) ? "same_device" : "unknown";
91
- const decision = resolvePresentation({
92
- business: {
93
- commerce_policy: "allowed",
94
- payment_state: "unpaid",
95
- quote_valid: presentation.rail_quote?.expires_at
96
- ? Date.parse(presentation.rail_quote.expires_at) > Date.now()
97
- : true,
98
- amount_minor: presentation.checkout.amount_minor,
99
- },
100
- viewer,
101
- executor_locality: locality,
102
- capabilities: hostCapabilities(host),
103
- ...(explicitChoiceFor(options.present) ? { explicit_choice: explicitChoiceFor(options.present) } : {}),
104
- });
105
- const presentationResult = {
106
- recommended: decision.recommended,
107
- alternatives: decision.alternatives,
108
- ...(decision.blocker ? { blocker: decision.blocker } : {}),
109
- };
110
- let dispatch = { status: "not_attempted" };
111
- // Explicit --present browser may open even under --json (the flag IS the
112
- // display request); --present auto under --json stays a plan and never opens.
113
- const browserRequested = options.present === "browser"
114
- || (options.present === "auto" && !options.jsonOutput && !options.noOpen && decision.recommended === "open_system_browser");
115
- if (browserRequested && !options.noOpen && process.env.ITPAY_NO_BROWSER !== "1") {
116
- const target = decision.recommended === "open_embedded_browser" ? decision.recommended : "open_system_browser";
117
- dispatch = await openInSystemBrowser(checkoutURL, options.baseURL);
118
- presentationResult.dispatched = { route: target, observation: dispatch.status };
119
- // Dispatch proves the request was accepted — nothing about visibility.
120
- if (dispatch.status === "dispatch_accepted") {
121
- envelope.instruction += " 已发起在本机浏览器打开官方确认页;只有用户确认看到页面才视为已显示,没看到时改发链接。";
122
- }
123
- else {
124
- envelope.instruction += " 浏览器打开未成功;按 handoff 链接展示,不要重复尝试同一方式。";
125
- }
126
- }
127
- if (relayRequested) {
128
- presentationResult.relay = resolveRelay({
129
- ...(options.relayOption ? { relayOption: options.relayOption } : {}),
130
- ...(options.confirmRelay ? { confirmRelay: options.confirmRelay } : {}),
131
- ...(options.requestKey ? { requestKey: options.requestKey } : {}),
132
- backendCapability: false,
133
- });
134
- envelope.instruction += " 消息转发当前不可用;不要声称已发送,改用现有官方入口展示。";
135
- }
136
- envelope.result = { ...envelope.result, presentation: presentationResult };
137
- envelope.communication = {
138
- schema_version: "itpay.communication.v1",
139
- tell: decision.communication.tell,
140
- wait_for: decision.communication.wait_for,
141
- must_convey: decision.communication.must_convey,
142
- };
143
55
  const plainResult = checkoutPlainResult(envelope.result);
144
56
  if (!options.jsonOutput && platformKeyForHost(host) === "terminal") {
145
57
  plainResult.push("qr:", await renderTerminalQR(checkoutURL, "terminal"));
@@ -150,21 +62,12 @@ export async function runCheckoutPresentation(backend, options) {
150
62
  plainResult,
151
63
  });
152
64
  }
153
- function explicitChoiceFor(present) {
154
- switch (present) {
155
- case "browser": return "open_system_browser";
156
- case "link": return "show_link";
157
- case "image": return "show_official_image";
158
- default: return undefined;
159
- }
160
- }
161
65
  function pendingCheckoutEnvelope(presentation, checkoutURL, plan, nextCommand, agentType, target) {
162
66
  const platform = platformKeyForHost(plan.host);
163
67
  const amount = formatMoney(presentation.checkout.amount_minor, presentation.checkout.currency);
164
68
  const presentationHandoff = buildCheckoutHandoff({
165
69
  platform,
166
70
  url: plan.linkOnlyURL ?? checkoutURL,
167
- mobileUrl: checkoutURL,
168
71
  amount,
169
72
  plan,
170
73
  ...(agentType ? { agentType } : {}),
@@ -175,46 +78,16 @@ function pendingCheckoutEnvelope(presentation, checkoutURL, plan, nextCommand, a
175
78
  : {}),
176
79
  ...(platform === "markdown" ? { markdown: buildAgentChatHandoff(plan).markdown } : {}),
177
80
  });
178
- const railQuote = presentation.rail_quote;
179
- const railPassengersPending = presentation.checkout_details === "rail_passengers" && !presentation.rail_passengers_confirmed;
180
81
  return {
181
82
  status: "human_checkout_required",
182
83
  result: {
183
84
  checkout_id: presentation.checkout.checkout_id,
184
85
  payment: "pending",
185
86
  amount,
186
- ...(railQuote ? { rail_quote: {
187
- passengers: railQuote.passengers,
188
- expires_at: railQuote.expires_at,
189
- legs: railQuote.legs.map((leg) => ({
190
- train_code: leg.train_code,
191
- travel_date: leg.travel_date,
192
- route: `${leg.from} → ${leg.to}`,
193
- time: `${leg.departure}–${leg.arrival}`,
194
- seat_name: leg.seat_name,
195
- ...(leg.seat_preferences?.length ? { seat_preferences: leg.seat_preferences } : {}),
196
- ...(leg.seat_request_planned ? {
197
- seat_request_planned: leg.seat_request_planned,
198
- auto_reason: leg.auto_reason ?? "none",
199
- } : {}),
200
- })),
201
- } } : {}),
202
- ...(railPassengersPending ? { rail_passengers_confirmed: false } : {}),
203
- ...(presentation.payment_deadline_at ? { payment_deadline_at: presentation.payment_deadline_at } : {}),
204
- ...(typeof presentation.payment_remaining_seconds === "number"
205
- ? { payment_remaining_seconds: presentation.payment_remaining_seconds }
206
- : {}),
207
- ...(presentation.server_now ? { server_now: presentation.server_now } : {}),
208
- ...(presentation.verified_sms_contact ? { verified_sms_contact: presentation.verified_sms_contact } : {}),
209
- ...(presentation.account_phone_option?.masked_recipient
210
- ? { account_phone_option: { masked_recipient: presentation.account_phone_option.masked_recipient } }
211
- : {}),
212
87
  },
213
88
  handoff: presentationHandoff.handoff,
214
- instruction: railPassengersPending
215
- ? `${presentationHandoff.instruction} 请用户在受保护网页填写乘车人并确认报价;姓名、证件和手机号只在网页填写,不要贴到对话中。座位偏好仅为购票请求、购票时才提交给供应商且不保证满足;无法逐人提交的偏好将自动分配座位,以实际出票为准。`
216
- : presentationHandoff.instruction,
217
- next: { command: nextCommand, reason: "展示后停止;仅用户完成操作或要求核对时读取同一 Checkout" },
89
+ instruction: presentationHandoff.instruction,
90
+ next: { command: nextCommand, reason: "稍后只查询同一 Checkout" },
218
91
  recovery: [],
219
92
  };
220
93
  }
@@ -261,7 +134,7 @@ function checkoutNeedsHumanHandoff(status) {
261
134
  return !new Set(["payment_succeeded", "completed", "failed", "expired", "refunded"]).has(status);
262
135
  }
263
136
  function checkoutPlainResult(result) {
264
- return plainValueLines(result);
137
+ return Object.entries(result).map(([key, value]) => `${key}: ${typeof value === "string" ? value : JSON.stringify(value)}`);
265
138
  }
266
139
  function formatMoney(amountMinor, currency) {
267
140
  return `${(amountMinor / 100).toFixed(2)} ${currency}`;
@@ -270,20 +143,6 @@ function checkoutPageURL(baseURL, checkoutID, displayToken) {
270
143
  const root = publicRoot(baseURL);
271
144
  return `${root}/checkout/${encodeURIComponent(checkoutID)}?display_token=${encodeURIComponent(displayToken)}`;
272
145
  }
273
- function savedCheckoutURLOrFallback(savedURL, checkoutID, displayToken, fallback) {
274
- if (!savedURL) {
275
- return fallback;
276
- }
277
- try {
278
- const parsed = new URL(savedURL);
279
- const belongsToCheckout = parsed.pathname === `/checkout/${checkoutID}`;
280
- const sameToken = parsed.searchParams.get("display_token") === displayToken;
281
- return belongsToCheckout && sameToken ? savedURL : fallback;
282
- }
283
- catch {
284
- return fallback;
285
- }
286
- }
287
146
  function checkoutCardURL(baseURL, checkoutID, displayToken) {
288
147
  const root = publicRoot(baseURL);
289
148
  return `${root}/v1/checkouts/${encodeURIComponent(checkoutID)}/card?display_token=${encodeURIComponent(displayToken)}`;
@@ -1,13 +1,63 @@
1
- export * from "../render/checkout_handoff.js";
2
- import { CommandContractError } from "../services/shared/envelope.js";
3
- // Presentation owns the clock, original cart and payment facts for every handoff.
4
- export function assertCheckoutPayable(checkout, authoritative, cartID, sourceExecutionID) {
5
- const current = authoritative?.checkout ?? checkout.checkout;
6
- const payableStates = ["draft", "quote_bound", "auth_required", "payment_required"];
7
- const deadlineClosed = authoritative?.payment_remaining_seconds === 0 ||
8
- (current.expires_at !== undefined && (!authoritative?.server_now || Date.parse(current.expires_at) <= Date.parse(authoritative.server_now)));
9
- if ((current.cart_id && current.cart_id !== cartID) || !payableStates.includes(current.status) || authoritative?.payment_intents.some(intent => !["failed", "cancelled", "expired"].includes(intent.status)) || deadlineClosed || !checkout.display_token) {
10
- throw new CommandContractError("checkout_not_payable", "original checkout is not a current payable entry", "原Checkout不可作为新付款入口;核对原订单。过期不续期,付款处理中或未知不重放,不换购物车或幂等键创建替代购买。", sourceExecutionID ? [{ command: `itpay services next ${sourceExecutionID} --json`, reason: "读取来源执行的原订单与付款事实" }] : [], undefined, { cart_id: cartID, checkout_id: current.checkout_id, checkout_status: current.status,
11
- reason: deadlineClosed ? "payment_window_closed" : "checkout_not_payable" });
1
+ import { buildOpenClawTelegramAction } from "../render/telegram.js";
2
+ export function shouldPrepareLocalCheckoutImage(platform) {
3
+ return platform === "markdown";
4
+ }
5
+ export function isWorkBuddyPlainChat(agentType, platform) {
6
+ return agentType?.trim().toLowerCase() === "workbuddy" && platform === "plain_chat";
7
+ }
8
+ export function isZCodePlainChat(agentType, platform) {
9
+ return agentType?.trim().toLowerCase() === "zcode" && platform === "plain_chat";
10
+ }
11
+ export function isLinkOnlyBrowserAgent(agentType, platform) {
12
+ return isWorkBuddyPlainChat(agentType, platform) || isZCodePlainChat(agentType, platform);
13
+ }
14
+ export function buildWorkBuddyPresentFilesAction(url) {
15
+ return {
16
+ tool: "present_files",
17
+ arguments: { files: [url] },
18
+ };
19
+ }
20
+ export function buildCheckoutHandoff(input) {
21
+ const handoff = { url: input.url };
22
+ const workBuddyAction = isWorkBuddyPlainChat(input.agentType, input.platform);
23
+ if (workBuddyAction) {
24
+ handoff.agent_action = buildWorkBuddyPresentFilesAction(input.url);
25
+ }
26
+ if (input.platform === "markdown") {
27
+ if (input.localPath)
28
+ handoff.qr_local_path = input.localPath;
29
+ if (input.markdown)
30
+ handoff.markdown = input.markdown;
31
+ }
32
+ else if (input.platform === "plain_chat" && input.qrImageURL && !isLinkOnlyBrowserAgent(input.agentType, input.platform)) {
33
+ handoff.qr_image_url = input.qrImageURL;
34
+ }
35
+ else if (input.platform === "telegram" && input.qrImageURL) {
36
+ handoff.qr_image_url = input.qrImageURL;
37
+ }
38
+ if (input.agentType?.trim().toLowerCase() === "openclaw" && input.platform === "telegram" && input.plan && input.target) {
39
+ handoff.agent_action = buildOpenClawTelegramAction(input.plan, input.target);
40
+ }
41
+ return {
42
+ handoff,
43
+ instruction: checkoutHandoffInstruction(input.agentType, input.platform, input.amount),
44
+ };
45
+ }
46
+ function checkoutHandoffInstruction(agentType, platform, amount) {
47
+ if (isWorkBuddyPlainChat(agentType, platform)) {
48
+ return `Backend 尚未确认付款。立即严格按 handoff.agent_action.tool 和 handoff.agent_action.arguments 原样执行一次,在右侧打开 handoff.url;确认工具调用成功后说明金额 ${amount},然后停止等待。若工具失败,只发送原始 handoff.url,报告未自动打开并停止。不要用 present_files 打开本地文件或二维码 PNG,不要下载或重建二维码,不要调用 pay,不要创建新 Checkout、Payment Intent 或 Execution。只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
49
+ }
50
+ if (isZCodePlainChat(agentType, platform)) {
51
+ return `Backend 尚未确认付款。立即用 ZCode 内置浏览器打开 handoff.url,让用户完成付款;确认已发起打开后说明金额 ${amount},然后停止等待。不要只粘贴文字链接,不要下载、解析或重建二维码,不要创建新 Checkout、Payment Intent 或 Execution。只有内置浏览器明确不可用时,才展示同一个可点击 handoff.url。只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
52
+ }
53
+ if (platform === "markdown") {
54
+ return `Backend 尚未确认付款。把 handoff.markdown 原样发送到当前桌面对话,确认二维码、付款链接和金额 ${amount} 均已实际对用户可见,然后停止等待。不要创建新 Checkout、Payment Intent 或 Execution;只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
55
+ }
56
+ if (platform === "terminal") {
57
+ return `Backend 尚未确认付款。在用户可见终端展示当前 Checkout 的二维码、handoff.url 和金额 ${amount},然后停止等待。不要创建新 Checkout、Payment Intent 或 Execution;只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
58
+ }
59
+ if (agentType?.trim().toLowerCase() === "openclaw" && platform === "telegram") {
60
+ return `Backend 尚未确认付款或授权状态。立即使用 OpenClaw 原生 message tool,严格按 handoff.agent_action.tool 和 handoff.agent_action.arguments 原样执行;它会发送金额 ${amount}、二维码、📱 手机点这儿支付和 📋 已授权给我读。不得改写 presentation、换用其他消息工具、拆开按钮发送或用普通回复声称按钮已经发送。确认 message tool 调用成功后停止等待。收到 callback_data "itp:grant_confirmed:<checkout_id>" 时,表示用户声明已在收银台授权读取;立即执行 next.command 查询同一 Checkout,再只执行 Backend 返回的 next.command 进入同一 Execution 的 grant 流程。该 callback 不证明付款成功或 grant 已生效;Backend 未返回 grant_active 前不得读取或猜测结果。若原生 message tool 明确失败或当前 Telegram 未启用 inline buttons,只发送现有 handoff.qr_image_url、金额和 handoff.url,报告按钮不可用并停止;不要创建新的 Checkout、Payment Intent 或 Execution。`;
12
61
  }
62
+ return `Backend 尚未确认付款。把 handoff.url 和可用的 handoff.qr_image_url 实际发送到当前会话,说明金额 ${amount},然后停止等待。不要创建新 Checkout、Payment Intent 或 Execution;只有用户明确表示已付款或要求查询状态时,才执行 next.command;用户的话不是付款成功证明。`;
13
63
  }
@@ -1,7 +1,8 @@
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 { CommandContractError, shellArgument, writeCommandEnvelope } from "./guidance.js";
4
+ import { resolveOutput } from "../render/sink.js";
5
+ import { CommandContractError, writeCommandEnvelope } from "./guidance.js";
5
6
  const commandDir = dirname(fileURLToPath(import.meta.url));
6
7
  export function runDocsList(options = {}) {
7
8
  const topics = loadDocs().map(({ topic, title, purpose }) => ({ topic, title, purpose }));
@@ -22,7 +23,7 @@ export function runDocsShow(topic, options = {}) {
22
23
  if (!doc) {
23
24
  throw new CommandContractError("doc_not_found", `doc topic not found: ${topic}`, "使用稳定 topic 名称;不要根据标题猜 topic。", [
24
25
  { command: "itpay docs list --json", reason: "列出全部 topic" },
25
- { command: `itpay docs search ${shellArgument(normalized || "topic")} --json`, reason: "按关键词重新搜索" },
26
+ { command: `itpay docs search ${shellWord(normalized || "topic")} --json`, reason: "按关键词重新搜索" },
26
27
  ]);
27
28
  }
28
29
  const envelope = {
@@ -32,7 +33,14 @@ export function runDocsShow(topic, options = {}) {
32
33
  next: null,
33
34
  recovery: [],
34
35
  };
35
- writeCommandEnvelope(envelope, options);
36
+ if (options.jsonOutput) {
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`);
36
44
  }
37
45
  export function runDocsSearch(query, options = {}) {
38
46
  const normalized = query.trim().toLowerCase();
@@ -76,10 +84,10 @@ function loadDocs() {
76
84
  function findDocsDir() {
77
85
  if (process.env.ITPAY_CLI_DOCS_DIR)
78
86
  return resolve(process.env.ITPAY_CLI_DOCS_DIR);
79
- const packagePath = resolve(commandDir, "..", "..", "..", "docs", "agent", "buyer");
80
- if (existsSync(packagePath))
81
- return packagePath;
82
- return resolve(commandDir, "..", "..", "docs", "agent", "buyer");
87
+ const sourcePath = resolve(commandDir, "..", "..", "docs", "agent", "buyer");
88
+ if (existsSync(sourcePath))
89
+ return sourcePath;
90
+ return resolve(commandDir, "..", "..", "..", "docs", "agent", "buyer");
83
91
  }
84
92
  function parseDoc(raw, file) {
85
93
  const value = JSON.parse(raw);
@@ -95,3 +103,6 @@ function parseDoc(raw, file) {
95
103
  function searchableText(doc) {
96
104
  return [doc.topic, doc.title, doc.purpose, ...(doc.search_terms ?? [])].join(" ").toLowerCase();
97
105
  }
106
+ function shellWord(value) {
107
+ return /^[a-zA-Z0-9._-]+$/.test(value) ? value : JSON.stringify(value);
108
+ }
@@ -1 +1,134 @@
1
- export * from "../services/shared/envelope.js";
1
+ import { HttpError } from "../client/http.js";
2
+ import { resolveOutput } from "../render/sink.js";
3
+ import { declaredAgentType, qualifyItPayCommand } from "../state/agent_type.js";
4
+ import { qualifyBackendCommand } from "../state/config.js";
5
+ export function isTerminalServiceExecutionStatus(status) {
6
+ return status === "failed" || status === "refunded" || status === "cancelled";
7
+ }
8
+ export function appendFeedbackPostmortemInstruction(instruction, outcome) {
9
+ const invitation = outcome === "delivered"
10
+ ? "结果解释完毕后,Agent 必须为同一订单提交一次安全服务复盘;用户评分和评论均可选,只在用户明确给出时忠实附上,不得猜分或上传对话、报告正文、联系方式和原始日志。"
11
+ : "先完成同一订单的恢复和退款权利解释;处理清楚后,Agent 必须为同一订单提交一次安全服务复盘。只附上用户明确表达的评分或卡壳点,不得猜测。";
12
+ return `${instruction} ${invitation}`;
13
+ }
14
+ export class CommandContractError extends Error {
15
+ code;
16
+ instruction;
17
+ recovery;
18
+ constructor(code, message, instruction, recovery) {
19
+ super(message);
20
+ this.code = code;
21
+ this.instruction = instruction;
22
+ this.recovery = recovery;
23
+ this.name = "CommandContractError";
24
+ }
25
+ }
26
+ export function writeCommandEnvelope(value, options = {}) {
27
+ const out = resolveOutput(options.output);
28
+ const agentType = options.agentType ?? declaredAgentType();
29
+ const qualified = qualifyEnvelope(value, agentType);
30
+ if (options.jsonOutput) {
31
+ out(JSON.stringify(qualified, null, 2) + "\n");
32
+ return;
33
+ }
34
+ out(`${qualified.status}\n`);
35
+ const facts = "error" in qualified ? qualified.error : qualified.result ?? {};
36
+ if (options.plainResult) {
37
+ for (const line of options.plainResult)
38
+ out(`${line}\n`);
39
+ }
40
+ else {
41
+ for (const [key, fact] of Object.entries(facts)) {
42
+ out(`${key}: ${typeof fact === "string" ? fact : JSON.stringify(fact)}\n`);
43
+ }
44
+ if ("error" in qualified && qualified.result) {
45
+ for (const [key, fact] of Object.entries(qualified.result)) {
46
+ out(`${key}: ${typeof fact === "string" ? fact : JSON.stringify(fact)}\n`);
47
+ }
48
+ }
49
+ }
50
+ if ("handoff" in qualified && qualified.handoff) {
51
+ for (const [key, fact] of Object.entries(qualified.handoff)) {
52
+ out(`handoff.${key}: ${typeof fact === "string" ? fact : JSON.stringify(fact)}\n`);
53
+ }
54
+ }
55
+ out(`instruction: ${qualified.instruction}\n`);
56
+ if (qualified.next)
57
+ out(`next: ${qualified.next.command}\n`);
58
+ if (qualified.recovery.length > 0) {
59
+ out("recovery:\n");
60
+ for (const action of qualified.recovery) {
61
+ out(` - ${action.command}\n`);
62
+ out(` reason: ${action.reason}\n`);
63
+ }
64
+ }
65
+ }
66
+ function qualifyEnvelope(value, agentType) {
67
+ return {
68
+ ...value,
69
+ next: value.next ? { ...value.next, command: qualifyBackendCommand(qualifyItPayCommand(value.next.command, agentType)) } : null,
70
+ recovery: value.recovery.map((action) => ({
71
+ ...action,
72
+ command: qualifyBackendCommand(qualifyItPayCommand(action.command, agentType)),
73
+ })),
74
+ };
75
+ }
76
+ export function errorRecoveryActions(error) {
77
+ if (!(error instanceof HttpError))
78
+ return [];
79
+ if (error.code === "agent_identity_required") {
80
+ return [{ id: "inspect_agent_setup", label: "Inspect supported Agent Type setup", command: "itpay install --json" }];
81
+ }
82
+ if (error.code === "agent_device_session_required") {
83
+ return [{
84
+ id: "read_agent_session_rules",
85
+ label: "Read ItPay identity and session recovery rules",
86
+ command: "itpay skill show itpay --json",
87
+ reason: "The CLI already attempted one automatic session renewal; do not rotate identity or loop retries.",
88
+ }];
89
+ }
90
+ if (error.code === "quota_exhausted" || error.code === "checkout_required") {
91
+ return [{
92
+ id: "inspect_service_execution",
93
+ label: "Inspect Service Execution before checkout",
94
+ command: "itpay services next <service_execution_id> --json",
95
+ }];
96
+ }
97
+ if (error.code === "cart_item_locked" || error.status === 409) {
98
+ return [
99
+ { id: "show_cart", label: "Inspect the canonical server cart", command: "itpay cart show" },
100
+ { id: "continue_checkout", label: "Continue the last locally remembered checkout", command: "itpay checkout" },
101
+ {
102
+ id: "recover_service_execution",
103
+ label: "List recoverable Service Executions if the local handoff is missing",
104
+ command: "itpay services list",
105
+ },
106
+ ];
107
+ }
108
+ if (error.status === 404) {
109
+ return [{
110
+ id: "recover_service_executions",
111
+ label: "List visible Service Executions and follow their next instruction",
112
+ command: "itpay services list",
113
+ }];
114
+ }
115
+ if (error.status === 502 || error.status === 503 || error.status === 504) {
116
+ return [{
117
+ id: "retry_after_backend_recovers",
118
+ label: "Retry after the selected official Backend is reachable",
119
+ command: "itpay readyz",
120
+ }];
121
+ }
122
+ return [];
123
+ }
124
+ export function printErrorRecovery(error, output) {
125
+ const recovery = errorRecoveryActions(error);
126
+ if (recovery.length === 0)
127
+ return;
128
+ const out = resolveOutput(output);
129
+ out("recovery:\n");
130
+ for (const action of recovery) {
131
+ out(` - ${action.label}\n`);
132
+ out(` ${action.command}\n`);
133
+ }
134
+ }
@@ -8,19 +8,16 @@ export const INSTALL_AGENT_TYPES = [
8
8
  "claude-code-cli",
9
9
  "workbuddy",
10
10
  "zcode",
11
- "doubao-work",
12
11
  "kimi-code",
13
12
  "openclaw",
14
13
  ];
15
- export const CODEX_WAIT_GUIDANCE = "Codex 宿主等待:保留完整工具返回,按实际返回的会话句柄及当前宿主续读工具收取最终输出,不要只取 stdout。若使用 functions.exec 包装,可用 text(await tools.exec_command(...));内层有 session_id 时用 write_stdin,外层返回运行 cell 时才用 wait。没有该包装时按宿主实际工具操作。先收取最终输出,再执行 CLI 业务 next;不要重复提交。";
16
14
  const INSTRUCTIONS = {
17
15
  "codex-desktop": "在 Codex Desktop 中始终传这个 Agent Type;付款时把返回的二维码和链接实际展示到当前对话。",
18
16
  "codex-cli": "在 Codex CLI 中始终传这个 Agent Type;付款交接只应显示在用户可见终端。",
19
17
  "claude-code-desktop": "在 Claude Code Desktop 中始终传这个 Agent Type;付款时把返回的二维码和链接实际展示到当前对话。",
20
18
  "claude-code-cli": "在 Claude Code CLI 中始终传这个 Agent Type;付款交接只应显示在用户可见终端。",
21
- 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,不要检查本地二维码文件。",
19
+ workbuddy: "在 WorkBuddy 中始终传这个 Agent Type。运行会持久化 ~/.itpay-v3 的 ItPay Bash 命令前,把 Shell 工具参数 dangerouslyDisableSandbox 设为 true;用户拒绝权限时停止,不要切换 Node、删除 identity 或处理 lock。Checkout 返回已渲染的付款 Card Link 和 present_files action;原样执行一次 action 打开 handoff.url,不要检查本地二维码文件。",
22
20
  zcode: "在 ZCode 中始终传这个 Agent Type。Checkout 或授权返回 handoff.url 后,立即用 ZCode 内置浏览器打开;不要只粘贴文字链接或重建二维码。只有内置浏览器不可用时才展示同一个可点击链接。",
23
- "doubao-work": "在豆包工作中始终传这个 Agent Type。Checkout 或授权返回后,同时展示 handoff.url 和 handoff.qr_image_url 两个有标签的官方入口;优先让用户在手机上直接打开链接,二维码供保存或另一台设备扫码。",
24
21
  "kimi-code": "保持 kimi-code Agent Type,使用标准 CLI 命令和终端付款交接;不要切换到其他平台身份。",
25
22
  openclaw: "保持 openclaw Agent Type;每个展示命令都从当前可信会话上下文显式传 --host,IM 入口同时传 --target。Telegram 使用返回的原生 message action,其他入口展示标准二维码和付款链接。",
26
23
  };
@@ -49,12 +46,12 @@ export function runInstall(target, options = {}) {
49
46
  ...installDefinition(normalized),
50
47
  default_api: DEFAULT_BASE_URL,
51
48
  },
52
- instruction: INSTRUCTIONS[normalized] + (normalized.startsWith("codex-") ? ` ${CODEX_WAIT_GUIDANCE}` : ""),
49
+ instruction: INSTRUCTIONS[normalized],
53
50
  next: {
54
51
  command: `itpay --agent-type ${normalized} readyz --json`,
55
52
  reason: "验证当前官方 ItPay API 的可用性",
56
53
  },
57
- recovery: [],
54
+ recovery: [{ command: "itpay docs show install-and-setup", reason: "查看官方 Backend 和首次使用说明" }],
58
55
  }, options);
59
56
  }
60
57
  function isInstallAgentType(value) {
@@ -1,28 +1,13 @@
1
- import { HttpError } from "../client/http.js";
2
- import { serviceAdapter } from "../services/registry.js";
3
- import { refundStateEnvelope } from "./refund.js";
4
1
  import { formatMoney } from "../render/output.js";
5
2
  import { appendFeedbackPostmortemInstruction, writeCommandEnvelope } from "./guidance.js";
6
3
  export async function runOrder(backend, orderID, options = {}) {
7
4
  const order = await backend.getOrder(orderID);
8
5
  const [delivery, refundResponse] = await Promise.all([
9
- order.status === "delivered" ? backend.getOrderDeliveryAccess(orderID).catch(error => {
10
- // Metadata ownership never grants account-protected delivery access.
11
- if (error instanceof HttpError && ["not_found", "account_scope_required", "access_denied"].includes(error.code))
12
- return undefined;
13
- throw error;
14
- }) : Promise.resolve(undefined),
6
+ order.status === "delivered" ? backend.getOrderDeliveryAccess(orderID) : Promise.resolve(undefined),
15
7
  backend.listOrderRefunds(orderID),
16
8
  ]);
17
9
  const lockedRefund = refundResponse.refunds.find((refund) => refund.access_locked);
18
- let envelope = orderEnvelope(order, delivery, lockedRefund);
19
- for (const identity of order.service_identities ?? []) {
20
- const capability = serviceAdapter(identity).aftercare;
21
- if (capability) {
22
- envelope = await capability.order(backend, order, envelope);
23
- break;
24
- }
25
- }
10
+ const envelope = orderEnvelope(order, delivery, lockedRefund);
26
11
  writeCommandEnvelope(envelope, {
27
12
  ...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
28
13
  ...(options.output ? { output: options.output } : {}),
@@ -34,7 +19,7 @@ function orderEnvelope(order, delivery, lockedRefund) {
34
19
  let instruction = "订单状态已读取;当前没有可用交付入口。";
35
20
  let next = null;
36
21
  if (lockedRefund) {
37
- instruction = refundStateEnvelope(lockedRefund, "shown").instruction;
22
+ instruction = "告诉用户退款处理中,原交付已按政策冻结。然后读取同一退款的权威状态;Agent 不读取交付、不创建授权或重复申请退款。";
38
23
  if (!refundTerminal) {
39
24
  next = { command: `itpay refund get ${lockedRefund.refund_request_id} --json`, reason: "读取退款的服务器状态" };
40
25
  }
@@ -53,32 +38,16 @@ function orderEnvelope(order, delivery, lockedRefund) {
53
38
  else if (order.status === "cancelled") {
54
39
  instruction = "先告诉用户这笔订单已经取消,没有可继续的付款或交付;不要创建替代订单,除非用户另行提出新的购买。";
55
40
  }
56
- else if (order.status === "pending_payment") {
57
- const remaining = typeof order.payment_remaining_seconds === "number" && order.payment_remaining_seconds <= 0;
58
- instruction = remaining
59
- ? "付款时限已到,但订单权威状态仍为待付款;读取同一订单核对到账或终止结果。不要重复支付、重建订单或推测已取消,刷新不会延长期限。"
60
- : `先告诉用户订单正在等待付款${order.payment_deadline_at ? `,付款截止时间为 ${order.payment_deadline_at}` : ""},超时未支付会被自动取消;不要创建替代订单,也不要重复发起支付授权。`;
61
- next = { command: `itpay order ${order.order_id} --json`, reason: "展示后停止;仅用户说已付款或要求核对时读取同一订单,到期可核对原单" };
62
- }
63
41
  else if (!["delivered", "refunded", "failed", "cancelled"].includes(order.status)) {
64
42
  instruction = "先告诉用户订单仍在处理,已记录的付款和订单不需要重复创建;稍后查询同一订单,不要创建替代订单。";
65
43
  next = { command: `itpay order ${order.order_id} --json`, reason: "刷新订单状态" };
66
44
  }
67
- if (order.status === "partially_refunded") {
68
- instruction = "订单已部分退款;核对本次退款金额及其他履约事实,不能推断整单全部退款。平台交付访问按返回权限保持锁定,不恢复旧授权。";
69
- next = null;
70
- }
71
45
  return {
72
46
  status: order.status,
73
47
  result: {
74
48
  order_id: order.order_id,
75
49
  ...(order.order_code ? { order_code: order.order_code } : {}),
76
50
  amount: formatMoney(order.amount_minor, order.currency),
77
- ...(order.payment_deadline_at ? { payment_deadline_at: order.payment_deadline_at } : {}),
78
- ...(typeof order.payment_remaining_seconds === "number"
79
- ? { payment_remaining_seconds: order.payment_remaining_seconds }
80
- : {}),
81
- ...(order.server_now ? { server_now: order.server_now } : {}),
82
51
  ...(delivery ? { delivery_mode: delivery.delivery_mode } : {}),
83
52
  access_locked: Boolean(lockedRefund),
84
53
  ...(delivery?.service_execution_id ? { service_execution_id: delivery.service_execution_id } : {}),
@@ -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, shellArgument, writeCommandEnvelope } from "./guidance.js";
5
- import { accessContextInstruction, vaultAccessGuidance } from "./vault.js";
4
+ import { CommandContractError, writeCommandEnvelope } from "./guidance.js";
5
+ import { accessContextInstruction, vaultAccessCommand } 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: `需要用户确认一次身份和只读权限。按当前动作或待填模板生成官方入口;用户完成后重新运行原始 orders 命令。${accessContextInstruction(options)}`,
34
- ...vaultAccessGuidance(undefined, options),
33
+ instruction: `需要用户确认一次身份和只读权限。执行 next.command 生成官方入口;用户完成后重新运行原始 orders 命令。${accessContextInstruction(options)}`,
34
+ next: { command: vaultAccessCommand(undefined, options), reason: "创建一次账号读取授权" },
35
35
  recovery: [],
36
36
  }, {
37
37
  ...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
@@ -92,3 +92,8 @@ 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
+ }
@@ -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 ? "读取权威订单和履约状态" : "展示后停止;仅用户完成付款或要求核对时读取同一 Checkout",
56
+ reason: verified ? "读取权威订单和履约状态" : "读取同一 Checkout 的权威付款状态",
57
57
  },
58
58
  recovery: [],
59
59
  };