@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
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 use the existing `itpay sell` entry point.
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 will use the same entry point later and are not implemented yet.
8
8
 
9
9
  ```bash
10
10
  npm install -g @itpay/cli
@@ -25,7 +25,7 @@ Official Agent install prompt(可直接复制给 Agent):
25
25
  Use a separate test prompt to set the exact official Dev Backend. Never put a
26
26
  Dev override in the public install prompt.
27
27
 
28
- The CLI defaults to the production Backend `https://app.itpay.ai`. Explicit sandbox runs may set `ITPAY_BACKEND_URL=https://sandbox.itpay.ai`; every other Backend URL is rejected before network or local state access.
28
+ The CLI defaults to the production Backend `https://app.itpay.ai`. Explicit tests may set `ITPAY_BACKEND_URL=https://dev.itpay.ai`; every other Backend URL is rejected before network or local state access.
29
29
 
30
30
  ## Output Contract
31
31
 
@@ -54,7 +54,6 @@ Normative per-command contracts: [CLI Command Reference](docs/cli-reference/inde
54
54
  | `claude-code-cli` | `terminal` |
55
55
  | `workbuddy` | `plain-chat` |
56
56
  | `zcode` | `plain-chat` |
57
- | `doubao-work` | `plain-chat` |
58
57
  | `kimi-code` | `terminal` |
59
58
  | `openclaw` | 必须显式提供 |
60
59
 
@@ -68,7 +67,7 @@ The local installation keeps one Ed25519 private key and a separate registration
68
67
  - `services start/invoke/action/checkout/next`: generic Service Execution flow.
69
68
  - `cart add/show/remove/clear/next`, `buy`: canonical Cart and ordinary Checkout flow.
70
69
  - `checkout`: authoritative payment and fulfillment recovery.
71
- - `services read-result`: read saved railway query pages or human-granted protected content.
70
+ - `services read-result`: read one human-granted protected result.
72
71
  - `order`, `orders`: exact order and account order views.
73
72
  - `vault list/access/read`: find and read previously purchased content after time-limited human authorization.
74
73
  - `refund create/list/get/watch/cancel`: Refund Owner flow.
@@ -91,12 +90,12 @@ itpay --agent-type <agent_type> services checkout <service_execution_id> --resum
91
90
  itpay checkout --id <checkout_id> --token <display_token> --json
92
91
  ```
93
92
 
94
- The local `~/.itpay-v3` directory stores one owner-only signing key, Backend-scoped Device registrations and Agent instances, idempotency operations, and recovery handles. Production uses `cart.json` / `operations.json`; sandbox uses `cart.sandbox.json` / `operations.sandbox.json`. Backend state remains authoritative. Do not delete or rotate this identity to recover quota.
93
+ The local `~/.itpay-v3` directory stores one owner-only signing key, Backend-scoped Device registrations and Agent instances, idempotency operations, and recovery handles. Production uses `cart.json` / `operations.json`; dev uses `cart.dev.json` / `operations.dev.json`. Backend state remains authoritative. Do not delete or rotate this identity to recover quota.
95
94
 
96
95
  ## Environment
97
96
 
98
97
  - `ITPAY_AGENT_TYPE`: stable alternative to global `--agent-type`.
99
- - `ITPAY_BACKEND_URL`: optional sandbox override; only the exact official URL `https://sandbox.itpay.ai` is accepted. Unset it for production.
98
+ - `ITPAY_BACKEND_URL`: optional test override; only the exact official URL `https://dev.itpay.ai` is accepted. Unset it for production.
100
99
  - `ITPAY_BEARER_TOKEN`: optional account-scoped browser session for legacy account reads. Local Agents normally use signed Device Authority plus the time-limited human authorization returned by `vault access`.
101
100
  - `ITPAY_CART_SESSION_PATH`: local recovery-state path override.
102
101
  - `ITPAY_CURRENCY`: ordinary Cart currency, default `CNY`.
@@ -128,9 +127,3 @@ Source boundaries:
128
127
  - `src/state`: local Device Authority, idempotency journal, and recovery handles.
129
128
  - `docs/cli-reference`: normative command contracts.
130
129
  - `docs/agent/buyer`: packaged progressive workflow guidance.
131
-
132
- ### Run a published service
133
-
134
- Use `itpay services run <service_id> --input-json ./input.json --json` for a published workflow. The input file must contain a JSON object matching the service input schema. With no input file, the CLI reports required fields and a command to continue the same execution.
135
-
136
- Use `itpay services run <service_id> --execution <execution_id> --json` to resume. At payment, the CLI uses the existing Checkout QR/card handoff. The backend continues after verified payment even if the CLI exits. `--timeout <seconds>` controls initial status polling (default 120, maximum 600). Use existing order/refund commands for refund requests; an application is not a successful refund until its final status confirms success.
@@ -5,19 +5,6 @@ export class BackendClient {
5
5
  constructor(http) {
6
6
  this.http = http;
7
7
  }
8
- get localSimulation() { return this.http.localSimulation; }
9
- // Called only through the fixed Sell operation contract, never arbitrary Agent URLs.
10
- sellRequest(request) {
11
- if (!request.path.startsWith("/v1/seller/organizations") && !request.path.startsWith("/v1/library/"))
12
- throw new Error("Invalid Sell route");
13
- return this.http.request(request.path, { method: request.method, ...(request.body !== undefined ? { body: request.body } : {}) });
14
- }
15
- agentAccountStatus() {
16
- return this.http.get('/v1/agent-device-account-bindings');
17
- }
18
- bindAgentAccount(input) {
19
- return this.http.post('/v1/agent-device-account-bindings', input);
20
- }
21
8
  readyz() {
22
9
  return this.http.get("/v1/readyz");
23
10
  }
@@ -103,11 +90,6 @@ export class BackendClient {
103
90
  readBuyerVaultArtifact(artifactRef, sections) {
104
91
  return this.http.post(`/v1/vault/artifacts/${encodeURIComponent(artifactRef)}/reads`, sections.length ? { sections } : {});
105
92
  }
106
- listRailAftercare(orderID) { return this.http.get(`/v1/orders/${encodeURIComponent(orderID)}/rail-aftercare`); }
107
- createRailAftercare(orderID, input, idempotencyKey) { return this.http.post(`/v1/orders/${encodeURIComponent(orderID)}/rail-aftercare`, input, { idempotencyKey }); }
108
- getRailAftercare(id) { return this.http.get(`/v1/rail-aftercare/${encodeURIComponent(id)}`); }
109
- respondRailAftercare(id, input) { return this.http.post(`/v1/rail-aftercare/${encodeURIComponent(id)}/respond`, input); }
110
- cancelRailAftercare(id, input) { return this.http.post(`/v1/rail-aftercare/${encodeURIComponent(id)}/cancel`, input); }
111
93
  // --- Refund ---
112
94
  createRefund(orderID, input, bearer, idempotencyKey) {
113
95
  const options = { ...(bearer ? { bearer } : {}), ...(idempotencyKey ? { idempotencyKey } : {}) };
@@ -126,15 +108,6 @@ export class BackendClient {
126
108
  startServiceExecution(input) {
127
109
  return this.http.post("/v1/service-executions", input);
128
110
  }
129
- advanceServiceExecution(id, input, idempotencyKey) {
130
- return this.http.post(`/v1/service-executions/${encodeURIComponent(id)}/advance`, { input, idempotency_key: idempotencyKey });
131
- }
132
- getServiceExecutionResultItemPage(serviceExecutionID, resultItemID, offset, limit, view) {
133
- const qs = new URLSearchParams({ offset: String(offset), limit: String(limit) });
134
- if (view)
135
- qs.set("view", view);
136
- return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/result-items/${encodeURIComponent(resultItemID)}?${qs}`);
137
- }
138
111
  invokeServiceCapability(serviceExecutionID, capabilityID, input) {
139
112
  return this.http.post(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/capabilities/${encodeURIComponent(capabilityID)}/invoke`, input, { replaySafe: Boolean(input.idempotency_key) });
140
113
  }
@@ -147,9 +120,8 @@ export class BackendClient {
147
120
  prepareServiceQuote(serviceExecutionID, input) {
148
121
  return this.http.post(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/quotes`, input, { replaySafe: true });
149
122
  }
150
- getServiceExecution(serviceExecutionID, opts = {}) {
151
- const query = opts.sinceSnapshot ? `?since_snapshot=${encodeURIComponent(opts.sinceSnapshot)}` : "";
152
- return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}${query}`);
123
+ getServiceExecution(serviceExecutionID) {
124
+ return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}`);
153
125
  }
154
126
  listServiceExecutions(limit = 50) {
155
127
  return this.http.get(`/v1/service-executions?limit=${limit}`);
@@ -164,14 +136,6 @@ export class BackendClient {
164
136
  getGrantedServiceResult(serviceExecutionID) {
165
137
  return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/granted-result`);
166
138
  }
167
- getRailPlanningCatalog(serviceExecutionID, snapshotID) {
168
- const query = snapshotID ? `?snapshot=${encodeURIComponent(snapshotID)}` : "";
169
- return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/rail-planning/catalog${query}`);
170
- }
171
- getRailJourneyDetail(serviceExecutionID, journeyID, snapshotID) {
172
- const query = snapshotID ? `?snapshot=${encodeURIComponent(snapshotID)}` : "";
173
- return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/rail-planning/journeys/${encodeURIComponent(journeyID)}${query}`);
174
- }
175
139
  }
176
140
  // --- SSE streaming helper ---
177
141
  async function streamSSE(url, onEvent, signal) {
@@ -14,7 +14,6 @@ export class HttpError extends Error {
14
14
  }
15
15
  }
16
16
  export class HttpClient {
17
- localSimulation = false;
18
17
  static MAX_TRANSPORT_RETRIES = 2;
19
18
  baseURL;
20
19
  fetchImpl;
@@ -71,7 +70,6 @@ export class HttpClient {
71
70
  ...(options.signal ? { signal: options.signal } : {}),
72
71
  });
73
72
  text = await response.text();
74
- this.localSimulation = response.headers.get("X-ItPay-Environment") === "local_simulation";
75
73
  }
76
74
  catch (error) {
77
75
  if (options.signal?.aborted)
@@ -1,4 +1,13 @@
1
- import { buildCheckoutQRPlan } from "../render/checkout_plan.js";
1
+ // V3 `buy` flow:
2
+ // 1. validate client context (host/target)
3
+ // 2. create cart via POST /v1/carts
4
+ // 3. create checkout via POST /v1/checkouts (consumes the cart)
5
+ // 4. build a checkout_qr render plan + dispatch to host renderer
6
+ // 5. [--pay] create payment intent → render payment QR → optionally SSE wait
7
+ // 6. [--json] output JSON instead of terminal text
8
+ //
9
+ // The display token is passed to the renderer and persisted in the local cart
10
+ // session so a restarted agent can resume the same checkout.
2
11
  import { operationID } from "../state/config.js";
3
12
  import { validateContext } from "../state/client_context.js";
4
13
  import { dispatchRender } from "../render/index.js";
@@ -8,7 +17,8 @@ import { localizeCardURL, normalizeCardLocale } from "../render/locale.js";
8
17
  import { buildAgentChatHandoff } from "../render/markdown.js";
9
18
  import { formatMoney } from "../render/output.js";
10
19
  import { CommandContractError, writeCommandEnvelope } from "./guidance.js";
11
- import { assertCheckoutPayable, buildCheckoutHandoff, shouldPrepareLocalCheckoutImage } from "./checkout_handoff.js";
20
+ import { buildCheckoutHandoff, isLinkOnlyBrowserAgent, shouldPrepareLocalCheckoutImage } from "./checkout_handoff.js";
21
+ import { qualifyItPayCommand } from "../state/agent_type.js";
12
22
  export async function runBuy(backend, config, options) {
13
23
  const err = validateContext(options.host, options.target);
14
24
  if (err) {
@@ -52,15 +62,6 @@ export async function runBuy(backend, config, options) {
52
62
  ...(options.contact ? { delivery_contact: options.contact } : {}),
53
63
  };
54
64
  const checkout = await backend.createCheckout(checkoutRequest, idempotencyKey);
55
- const authoritative = checkout.display_token
56
- ? await backend.getCheckoutPresentation(checkout.checkout.checkout_id, checkout.display_token)
57
- : undefined;
58
- assertCheckoutPayable(checkout, authoritative, cart.cart_id, latestLine?.service_execution_id);
59
- const current = authoritative?.checkout ?? checkout.checkout;
60
- // Only the returned checkout's items are authoritative for the handoff.
61
- if (!authoritative)
62
- throw new Error("checkout presentation unavailable");
63
- cart = { ...cart, items: authoritative.items, amount_minor: current.amount_minor, currency: current.currency };
64
65
  options.cartSession.rememberCheckout({
65
66
  checkoutID: checkout.checkout.checkout_id,
66
67
  displayToken: checkout.display_token,
@@ -105,7 +106,6 @@ export async function runBuy(backend, config, options) {
105
106
  }
106
107
  // --- Build render plan (after payment if --pay) ---
107
108
  const planInput = {
108
- localSimulation: backend.localSimulation,
109
109
  host: options.host,
110
110
  checkoutID,
111
111
  checkoutURL,
@@ -135,7 +135,7 @@ export async function runBuy(backend, config, options) {
135
135
  });
136
136
  }
137
137
  // --- Output ---
138
- if (options.jsonOutput || platformKeyForHost(options.host) === "plain_chat") {
138
+ if (options.jsonOutput) {
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: Boolean(options.jsonOutput), ...(options.output ? { output: options.output } : {}), ...(options.agentType ? { agentType: options.agentType } : {}) });
151
+ writeCommandEnvelope(envelope, { jsonOutput: true, ...(options.output ? { output: options.output } : {}) });
152
152
  return {
153
153
  kind: "checkout_rendered",
154
154
  plan,
@@ -203,7 +203,6 @@ function buildBuyEnvelope(input) {
203
203
  const presentationHandoff = buildCheckoutHandoff({
204
204
  platform,
205
205
  url: input.plan.linkOnlyURL ?? input.checkoutURL,
206
- mobileUrl: input.checkoutURL,
207
206
  amount,
208
207
  plan: input.plan,
209
208
  ...(input.agentType ? { agentType: input.agentType } : {}),
@@ -219,7 +218,7 @@ function buildBuyEnvelope(input) {
219
218
  result,
220
219
  handoff: presentationHandoff.handoff,
221
220
  instruction: presentationHandoff.instruction,
222
- next: { command: input.plan.afterActionCommand ?? `itpay checkout --id ${input.checkoutID} --token ${input.displayToken} --json`, reason: "仅用户完成付款或要求查询时,读取同一笔Checkout权威状态" },
221
+ next: { command: input.plan.afterActionCommand ?? `itpay checkout --id ${input.checkoutID} --token ${input.displayToken} --json`, reason: "稍后查询同一笔 Checkout 状态" },
223
222
  recovery: [],
224
223
  };
225
224
  }
@@ -244,6 +243,49 @@ async function waitForPaymentSSE(backend, checkoutID, displayToken, timeoutSec)
244
243
  });
245
244
  });
246
245
  }
246
+ // --- checkout QR plan ---
247
+ export function buildCheckoutQRPlan(input) {
248
+ const summary = `Scan the QR or open ${input.checkoutURL} to start the human checkout flow.`;
249
+ const isPayment = input.paymentIntentID != null;
250
+ const afterCommand = qualifyItPayCommand(`itpay checkout --id ${input.checkoutID} --token ${input.displayToken}${input.locale === "en" ? " --locale en" : ""} --json`, input.agentType);
251
+ const platform = {
252
+ text: summary,
253
+ links: [
254
+ { label: "打开付款页面", url: input.checkoutURL },
255
+ ],
256
+ buttons: [
257
+ { label: "打开收银台", kind: "url", url: input.checkoutURL },
258
+ ...(input.checkoutID
259
+ ? [{ label: "查询 Checkout 状态", kind: "callback", intent: "check_checkout_status", ref: input.checkoutID }]
260
+ : []),
261
+ ],
262
+ blocks: [],
263
+ ...(input.qrPNGURL ? { media: [{ url: input.qrPNGURL, label: "Branded QR", mimeType: "image/png" }] } : {}),
264
+ };
265
+ const plan = {
266
+ kind: isPayment ? "payment_qr" : "checkout_qr",
267
+ host: input.host,
268
+ summary,
269
+ url: input.qrPayload,
270
+ ...(isLinkOnlyBrowserAgent(input.agentType, platformKeyForHost(input.host)) && input.cardURL ? { linkOnlyURL: input.cardURL } : {}),
271
+ preferredQRSources: [input.qrPNGURL ?? input.qrPayload],
272
+ checkoutID: input.checkoutID,
273
+ platform,
274
+ afterActionCommand: afterCommand,
275
+ afterActionLabel: "扫码或点击链接完成支付后,执行以下命令查询状态:",
276
+ };
277
+ if (input.orderItems)
278
+ plan.orderItems = input.orderItems;
279
+ if (input.orderCurrency)
280
+ plan.orderCurrency = input.orderCurrency;
281
+ if (input.paymentMethod)
282
+ plan.paymentMethod = input.paymentMethod;
283
+ if (input.paymentStatus)
284
+ plan.paymentStatus = input.paymentStatus;
285
+ if (input.paymentIntentID)
286
+ plan.paymentIntentID = input.paymentIntentID;
287
+ return plan;
288
+ }
247
289
  function fallbackCardURL(baseURL, checkoutID, displayToken) {
248
290
  const root = baseURL.replace(/\/$/, "");
249
291
  return `${root}/v1/checkouts/${encodeURIComponent(checkoutID)}/card?display_token=${encodeURIComponent(displayToken)}`;
@@ -285,4 +327,3 @@ function absolutePublicURL(baseURL, value) {
285
327
  return value;
286
328
  }
287
329
  }
288
- export { buildCheckoutQRPlan } from "../render/checkout_plan.js";
@@ -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: "仅写入本地兼容草稿,未验证目录、价格或服务合同;不要把它当作 canonical Cart。",
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
- : "普通项目已加入 服务端购物车;先检查购物车,再创建 Checkout。",
77
+ : "普通项目已加入 canonical Cart;先检查购物车,再创建 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: "检查 canonical Cart" },
81
81
  recovery: [],
82
82
  };
83
83
  writeCommandEnvelope(envelope, {
@@ -117,13 +117,11 @@ export async function runCartAddQuoteServer(options) {
117
117
  status: "quote_added",
118
118
  result: {
119
119
  cart_id: cart.cart_id,
120
- service_quote_lock_id: options.serviceQuoteLockID,
121
- service_execution_id: line.service_execution_id,
122
120
  item_count: cart.items.length,
123
121
  total: formatMoney(cart.amount_minor, cart.currency),
124
122
  },
125
123
  instruction: "付费服务报价已加入同一 Cart;每个项目仍保持独立 Execution 和交付。",
126
- next: { command: `itpay buy --cart ${cart.cart_id} --json`, reason: "仅已有购买授权且项目齐全时创建一次合并付款" },
124
+ next: { command: `itpay buy --cart ${cart.cart_id} --json`, reason: "确认项目齐全后创建一次合并付款" },
127
125
  recovery: [{ command: "itpay cart show --json", reason: "检查当前合并 Cart" }],
128
126
  };
129
127
  writeCommandEnvelope(envelope, {
@@ -146,7 +144,7 @@ export function runCartRemove(session, options) {
146
144
  catalog_variant_id: options.catalogVariantID,
147
145
  offer_id: options.offerID,
148
146
  },
149
- instruction: "本地兼容草稿已更新;这不代表任何 服务端购物车 或 Service Execution 已变更。",
147
+ instruction: "本地兼容草稿已更新;这不代表任何 canonical Cart 或 Service Execution 已变更。",
150
148
  next: { command: "itpay cart show --local --json", reason: "检查剩余本地草稿" },
151
149
  recovery: [],
152
150
  }, {
@@ -178,7 +176,7 @@ export async function runCartRemoveServer(backend, session, cartItemID, options
178
176
  cart_item_id: lineID,
179
177
  remaining_item_count: cart.items.length,
180
178
  },
181
- instruction: "服务端购物车 已更新;被删除的最后一个 服务项目 对应执行会由服务端同时取消。",
179
+ instruction: "canonical Cart 已更新;被删除的最后一个 service-backed line 对应执行会由服务端一致性事务取消。",
182
180
  next: { command: "itpay cart next --json", reason: "检查剩余内容" },
183
181
  recovery: [],
184
182
  };
@@ -200,10 +198,10 @@ export function runCartShow(session, options = {}) {
200
198
  status: items.length > 0 ? "shown_local" : "local_empty",
201
199
  result: { currency: snap.currency, items },
202
200
  instruction: items.length > 0
203
- ? "这是未验证的本地兼容草稿,只能用于明确的普通商品流程;不要把它当作 服务端购物车。"
201
+ ? "这是未验证的本地兼容草稿,只能用于明确的普通商品流程;不要把它当作 canonical Cart。"
204
202
  : "本地兼容草稿为空;从已发布目录重新选择项目。",
205
203
  next: items.length > 0
206
- ? { command: "itpay buy --json", reason: "将普通本地草稿提交为 服务端购物车" }
204
+ ? { command: "itpay buy --json", reason: "将普通本地草稿提交为 canonical Cart" }
207
205
  : { command: "itpay catalog list --json", reason: "读取已发布目录" },
208
206
  recovery: [],
209
207
  };
@@ -217,7 +215,7 @@ export async function runCartShowServer(backend, session, options = {}) {
217
215
  writeCommandEnvelope({
218
216
  status: "cart_handle_missing",
219
217
  result: {},
220
- instruction: "本地没有 服务端购物车 句柄;不要把本地草稿默认为服务端 Cart。",
218
+ instruction: "本地没有 canonical Cart 句柄;不要把本地草稿默认为服务端 Cart。",
221
219
  next: { command: "itpay catalog list --json", reason: "读取已发布目录" },
222
220
  recovery: [{ command: "itpay cart show --local --json", reason: "仅在明确需要时检查本地兼容草稿" }],
223
221
  }, {
@@ -243,7 +241,7 @@ export async function runCartShowServer(backend, session, options = {}) {
243
241
  },
244
242
  instruction: items.length > 0
245
243
  ? "使用 line 或 execution 句柄继续;不要使用内部 quote lock ID。"
246
- : "服务端购物车 当前为空;从已发布目录选择项目。",
244
+ : "canonical Cart 当前为空;从已发布目录选择项目。",
247
245
  next: items.length > 0
248
246
  ? { command: "itpay cart next --json", reason: "取得当前首选动作" }
249
247
  : { command: "itpay catalog list --json", reason: "读取已发布目录" },
@@ -280,7 +278,7 @@ export async function runCartAbandonServer(backend, session, options = {}) {
280
278
  writeCommandEnvelope({
281
279
  status: "cart_handle_missing",
282
280
  result: {},
283
- instruction: "本地没有 服务端购物车 句柄;未修改任何 Backend 或本地资源。",
281
+ instruction: "本地没有 canonical Cart 句柄;未修改任何 Backend 或本地资源。",
284
282
  next: { command: "itpay next --json", reason: "检查其他可恢复句柄" },
285
283
  recovery: [{ command: "itpay cart clear --local --json", reason: "仅在明确放弃本地草稿和句柄时执行" }],
286
284
  }, {
@@ -294,7 +292,7 @@ export async function runCartAbandonServer(backend, session, options = {}) {
294
292
  const envelope = {
295
293
  status: "abandoned",
296
294
  result: { cart_id: cart.cart_id, server_abandoned: true },
297
- instruction: "服务端购物车 已放弃;服务端已清空有效项目,并取消关联的未付款服务执行。",
295
+ instruction: "canonical Cart 已放弃;Backend 已在同一事务中软删除 active lines,并取消其未付款 Service Execution。",
298
296
  next: { command: "itpay catalog list --json", reason: "仅在用户提出新需求时重新选择" },
299
297
  recovery: [],
300
298
  };
@@ -310,7 +308,7 @@ export async function runCartNext(backend, session, options = {}) {
310
308
  writeCartNextEnvelope({
311
309
  status: "cart_handle_missing",
312
310
  result: {},
313
- instruction: "本地没有 服务端购物车 句柄;先恢复已有资源,不要创建重复 Cart。",
311
+ instruction: "本地没有 canonical Cart 句柄;先恢复已有资源,不要创建重复 Cart。",
314
312
  next: { command: "itpay next --json", reason: "检查其他可恢复句柄" },
315
313
  recovery: [{ command: "itpay services list --json", reason: "从服务端恢复当前设备的执行" }],
316
314
  }, options);
@@ -327,7 +325,7 @@ export async function runCartNext(backend, session, options = {}) {
327
325
  cart_status: cart.status,
328
326
  service_execution_id: unquotedServiceLine.service_execution_id,
329
327
  },
330
- instruction: "该 Cart 包含 服务项目;继续 Service Execution,不要从 Cart 猜 capability。",
328
+ instruction: "该 Cart 包含 service-backed line;继续 Service Execution,不要从 Cart 猜 capability。",
331
329
  next: {
332
330
  command: `itpay services next ${unquotedServiceLine.service_execution_id} --json`,
333
331
  reason: "读取服务端最新执行状态",
@@ -357,7 +355,7 @@ export async function runCartNext(backend, session, options = {}) {
357
355
  instruction: cart.items.some((item) => item.service_quote_lock_id)
358
356
  ? "服务报价已锁定输入和价格;确认项目齐全后使用同一 Cart 创建一次 Checkout。"
359
357
  : "该 Cart 是普通购买流程;使用同一 Cart 创建 Checkout,不要重复添加商品。",
360
- next: { command: `itpay buy --cart ${cart.cart_id} --json`, reason: "仅已有购买授权时继续服务端购物车结算" },
358
+ next: { command: `itpay buy --cart ${cart.cart_id} --json`, reason: "继续 canonical Cart 结算" },
361
359
  recovery: [],
362
360
  };
363
361
  }
@@ -1,7 +1,7 @@
1
1
  import { writeCommandEnvelope } from "./guidance.js";
2
2
  export async function runCatalogList(backend, options = {}) {
3
3
  const manifest = await backend.getCatalogManifest();
4
- const services = manifest.manifest.items.map(item => summarizeService(item, options.locale ?? "zh-CN"));
4
+ const services = manifest.manifest.items.map(summarizeService);
5
5
  const firstServiceID = manifest.manifest.items.find((item) => item.service_id)?.service_id;
6
6
  const empty = services.length === 0;
7
7
  const jsonFlag = options.jsonOutput ? " --json" : "";
@@ -9,40 +9,30 @@ 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
- ? "当前没有已发布服务;本次目录读取结束,不猜测服务或自动重试。"
13
- : options.locale === "en" ? "Explain the service, its operations and pricing; start only the declared operation matching the human request." : "向用户解释主服务、辅助步骤和价格;得到用户意图后再启动对应 service_id。",
14
- next: services.length === 1 && firstServiceID
15
- ? { command: `itpay services start ${firstServiceID}${jsonFlag}`, reason: "仅当当前服务符合用户请求时读取输入合同;不表示购买同意" }
16
- : null,
12
+ ? "当前没有已发布服务;稍后重试,不要猜测 service_id。"
13
+ : "向用户解释主服务、辅助步骤和价格;得到用户意图后再启动对应 service_id。",
14
+ next: empty
15
+ ? { command: `itpay catalog list${jsonFlag}`, reason: "稍后重新读取已发布目录" }
16
+ : {
17
+ command: `itpay services start ${services.length === 1 && firstServiceID ? firstServiceID : "<service_id>"}${jsonFlag}`,
18
+ reason: "启动用户选择的服务",
19
+ },
17
20
  recovery: [],
18
21
  }, {
19
22
  ...options,
20
23
  plainResult: catalogPlainLines(manifest.version, services),
21
24
  });
22
25
  }
23
- function summarizeService(item, locale) {
24
- const definition = item.service_definition;
25
- if (definition) {
26
- if (definition.schema_version !== "itpay.service-definition.v1")
27
- return { service_id: item.service_id, title: item.title, description: "服务定义版本不受支持;升级CLI后读取,不猜操作。" };
28
- return {
29
- service_id: definition.service_id, title: definition.display_names[locale] ?? item.title,
30
- entry: `itpay services start ${definition.service_id} --json`, definition_version: definition.definition_version,
31
- ...(definition.guide ? { guide: `itpay docs show ${definition.guide.topic}` } : {}),
32
- operations: definition.operations.map(op => ({ operation_id: op.id, title: op.display_names[locale], role: op.role, price: op.billing_mode === "dynamic" ? (locale === "en" ? (definition.adapter_key === "rail.v1" ? "Live quote; free queries do not mean free tickets" : "Live quote; confirm the locked amount") : (definition.adapter_key === "rail.v1" ? "实时报价;免费查询不等于免费车票" : "实时报价,以锁定报价为准")) : op.billing_mode === "free" ? (locale === "en" ? "Free query" : "免费查询") : typeof op.price_amount_minor === "number" && op.price_currency ? formatProductMoney(op.price_amount_minor, op.price_currency) : (locale === "en" ? "Fixed price missing; stop purchase preparation" : "固定价格事实缺失;停止准备购买"), requires_continuation: op.requires_continuation ?? false })),
33
- };
34
- }
26
+ function summarizeService(item) {
35
27
  const flow = item.service_flow;
36
- const offer = item.variants?.[0];
37
28
  return {
38
29
  service_id: item.service_id ?? null,
39
- ...(item.service_id ? { entry: `itpay services start ${item.service_id} --json` } : {}),
40
30
  title: item.title,
41
31
  description: item.description ?? "",
42
32
  ...(flow ? {
43
33
  discovery: {
44
34
  title: flow.discovery.title,
45
- description: flow.discovery.description ?? "",
35
+ description: flow.discovery.description,
46
36
  ...(flow.discovery.free_quota_limit !== undefined ? { free_quota: flow.discovery.free_quota_limit } : {}),
47
37
  ...(flow.discovery.paid_continuation ? {
48
38
  paid_price: formatProductMoney(flow.discovery.paid_continuation.amount_minor, flow.discovery.paid_continuation.currency),
@@ -50,15 +40,9 @@ function summarizeService(item, locale) {
50
40
  },
51
41
  primary_offer: {
52
42
  title: flow.primary_service.title,
53
- description: flow.primary_service.description ?? "",
43
+ description: flow.primary_service.description,
54
44
  price: formatProductMoney(flow.primary_service.amount_minor, flow.primary_service.currency),
55
45
  },
56
- } : offer ? {
57
- primary_offer: {
58
- title: offer.title || item.title,
59
- description: item.description ?? "",
60
- price: formatProductMoney(offer.amount_minor, offer.currency),
61
- },
62
46
  } : {}),
63
47
  };
64
48
  }
@@ -67,28 +51,19 @@ function catalogPlainLines(version, services) {
67
51
  for (const service of services) {
68
52
  lines.push(`service: ${String(service.title)}`);
69
53
  lines.push(` service_id: ${String(service.service_id ?? "unavailable")}`);
70
- if (service.entry)
71
- lines.push(` entry: ${String(service.entry)}`);
72
- if (service.guide)
73
- lines.push(` guide: ${String(service.guide)}`);
74
54
  if (service.description)
75
55
  lines.push(` description: ${String(service.description)}`);
76
- if (Array.isArray(service.operations))
77
- for (const op of service.operations)
78
- lines.push(` operation: ${String(op.operation_id)} — ${String(op.title)}; ${String(op.price)}`);
79
56
  const discovery = service.discovery;
80
57
  if (discovery) {
81
58
  const quota = discovery.free_quota !== undefined ? `; free_quota: ${String(discovery.free_quota)}` : "";
82
59
  const paid = discovery.paid_price ? `; paid_price: ${String(discovery.paid_price)}` : "";
83
60
  lines.push(` discovery: ${String(discovery.title)}${quota}${paid}`);
84
- if (discovery.description)
85
- lines.push(` ${String(discovery.description)}`);
61
+ lines.push(` ${String(discovery.description)}`);
86
62
  }
87
63
  const primary = service.primary_offer;
88
64
  if (primary) {
89
65
  lines.push(` primary_offer: ${String(primary.title)}; price: ${String(primary.price)}`);
90
- if (primary.description)
91
- lines.push(` ${String(primary.description)}`);
66
+ lines.push(` ${String(primary.description)}`);
92
67
  }
93
68
  }
94
69
  return lines;