@itpay/cli 2.0.15 → 2.0.16

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 (50) hide show
  1. package/dist/src/commands/buy.js +3 -0
  2. package/dist/src/commands/checkout.js +4 -2
  3. package/dist/src/commands/checkout_handoff.js +7 -0
  4. package/dist/src/commands/compatibility.js +0 -1
  5. package/dist/src/commands/install.js +13 -8
  6. package/dist/src/commands/services.js +14 -6
  7. package/dist/src/main.js +43 -16
  8. package/dist/src/render/telegram.js +51 -25
  9. package/dist/src/state/client_context.js +2 -0
  10. package/dist/src/state/config.js +8 -1
  11. package/docs/agent/buyer/install-and-setup.json +9 -7
  12. package/docs/agent/buyer/payment-flow.json +1 -0
  13. package/docs/agent/buyer/quickstart.json +1 -0
  14. package/docs/agent/buyer/render-hosts.json +18 -2
  15. package/docs/cli-reference/agent-types.md +12 -2
  16. package/docs/cli-reference/commands/buy.md +9 -4
  17. package/docs/cli-reference/commands/cart/add.md +1 -1
  18. package/docs/cli-reference/commands/cart/clear.md +1 -1
  19. package/docs/cli-reference/commands/cart/index.md +1 -1
  20. package/docs/cli-reference/commands/cart/next.md +1 -1
  21. package/docs/cli-reference/commands/cart/remove.md +1 -1
  22. package/docs/cli-reference/commands/cart/show.md +1 -1
  23. package/docs/cli-reference/commands/catalog/index.md +1 -1
  24. package/docs/cli-reference/commands/checkout.md +5 -3
  25. package/docs/cli-reference/commands/docs/index.md +1 -1
  26. package/docs/cli-reference/commands/docs/list.md +1 -1
  27. package/docs/cli-reference/commands/docs/search.md +1 -1
  28. package/docs/cli-reference/commands/docs/show.md +1 -1
  29. package/docs/cli-reference/commands/install.md +44 -10
  30. package/docs/cli-reference/commands/next.md +1 -1
  31. package/docs/cli-reference/commands/readyz.md +26 -2
  32. package/docs/cli-reference/commands/refund/cancel.md +1 -1
  33. package/docs/cli-reference/commands/refund/create.md +1 -1
  34. package/docs/cli-reference/commands/refund/index.md +1 -1
  35. package/docs/cli-reference/commands/refund/list.md +1 -1
  36. package/docs/cli-reference/commands/services/action.md +1 -1
  37. package/docs/cli-reference/commands/services/checkout.md +5 -1
  38. package/docs/cli-reference/commands/services/events.md +1 -1
  39. package/docs/cli-reference/commands/services/get.md +1 -1
  40. package/docs/cli-reference/commands/services/index.md +1 -1
  41. package/docs/cli-reference/commands/services/invoke.md +1 -1
  42. package/docs/cli-reference/commands/services/list.md +1 -1
  43. package/docs/cli-reference/commands/services/read-result.md +1 -1
  44. package/docs/cli-reference/conventions.md +7 -1
  45. package/docs/skill-bundle-rollout/02-platform-bundle-repositories.md +20 -23
  46. package/docs/skill-bundle-rollout/03-platform-publishing.md +2 -1
  47. package/docs/skill-bundle-rollout/04-first-wave-platforms.md +32 -0
  48. package/docs/skill-bundle-rollout/README.md +3 -2
  49. package/package.json +1 -1
  50. package/skills/itpay/SKILL.md +1 -1
@@ -141,6 +141,7 @@ export async function runBuy(backend, config, options) {
141
141
  ...(qrPNGURL ? { qrPNGURL } : {}),
142
142
  ...(paymentIntent ? { paymentIntent } : {}),
143
143
  ...(options.agentType ? { agentType: options.agentType } : {}),
144
+ ...(options.target ? { target: options.target } : {}),
144
145
  });
145
146
  writeCommandEnvelope(envelope, { jsonOutput: true, ...(options.output ? { output: options.output } : {}) });
146
147
  return {
@@ -198,7 +199,9 @@ function buildBuyEnvelope(input) {
198
199
  platform,
199
200
  url: input.checkoutURL,
200
201
  amount,
202
+ plan: input.plan,
201
203
  ...(input.agentType ? { agentType: input.agentType } : {}),
204
+ ...(input.target ? { target: input.target } : {}),
202
205
  ...(input.qrPNGURL ? { qrImageURL: input.qrPNGURL } : {}),
203
206
  ...(input.plan.ideImageAttach?.status === "downloaded" && input.plan.ideImageAttach.localPath
204
207
  ? { localPath: input.plan.ideImageAttach.localPath }
@@ -46,7 +46,7 @@ export async function runCheckoutPresentation(backend, options) {
46
46
  ...(options.baseURL ? { baseURL: options.baseURL } : {}),
47
47
  });
48
48
  }
49
- const envelope = pendingCheckoutEnvelope(presentation, checkoutURL, plan, nextCommand, options.agentType);
49
+ const envelope = pendingCheckoutEnvelope(presentation, checkoutURL, plan, nextCommand, options.agentType, options.target);
50
50
  const plainResult = checkoutPlainResult(envelope.result);
51
51
  if (!options.jsonOutput && platformKeyForHost(host) === "terminal") {
52
52
  plainResult.push("qr:", await renderTerminalQR(checkoutURL, "terminal"));
@@ -57,14 +57,16 @@ export async function runCheckoutPresentation(backend, options) {
57
57
  plainResult,
58
58
  });
59
59
  }
60
- function pendingCheckoutEnvelope(presentation, checkoutURL, plan, nextCommand, agentType) {
60
+ function pendingCheckoutEnvelope(presentation, checkoutURL, plan, nextCommand, agentType, target) {
61
61
  const platform = platformKeyForHost(plan.host);
62
62
  const amount = formatMoney(presentation.checkout.amount_minor, presentation.checkout.currency);
63
63
  const presentationHandoff = buildCheckoutHandoff({
64
64
  platform,
65
65
  url: checkoutURL,
66
66
  amount,
67
+ plan,
67
68
  ...(agentType ? { agentType } : {}),
69
+ ...(target ? { target } : {}),
68
70
  ...(plan.preferredQRSources[0] ? { qrImageURL: plan.preferredQRSources[0] } : {}),
69
71
  ...(plan.ideImageAttach?.status === "downloaded" && plan.ideImageAttach.localPath
70
72
  ? { localPath: plan.ideImageAttach.localPath }
@@ -1,3 +1,4 @@
1
+ import { buildOpenClawTelegramAction } from "../render/telegram.js";
1
2
  export function shouldPrepareLocalCheckoutImage(platform) {
2
3
  return platform === "markdown";
3
4
  }
@@ -15,6 +16,12 @@ export function buildCheckoutHandoff(input) {
15
16
  else if (input.platform === "plain_chat" && input.qrImageURL) {
16
17
  handoff.qr_image_url = input.qrImageURL;
17
18
  }
19
+ else if (input.platform === "telegram" && input.qrImageURL) {
20
+ handoff.qr_image_url = input.qrImageURL;
21
+ }
22
+ if (input.agentType?.trim().toLowerCase() === "openclaw" && input.platform === "telegram" && input.plan && input.target) {
23
+ handoff.agent_action = buildOpenClawTelegramAction(input.plan, input.target);
24
+ }
18
25
  return {
19
26
  handoff,
20
27
  instruction: checkoutHandoffInstruction(input.agentType, input.platform, input.amount, Boolean(input.qrImageURL)),
@@ -14,7 +14,6 @@ export async function requirePlatformCompatibility(backend) {
14
14
  maximum_cli_major: platform.maximum_cli_major,
15
15
  platform_revision: platform.platform_revision,
16
16
  api_contract_revision: platform.api_contract_revision,
17
- upgrade_command: `npm install -g @itpay/cli@${platform.minimum_cli_version}`,
18
17
  }, "CLI is incompatible with the active ItPay platform release");
19
18
  }
20
19
  function compareVersions(left, right) {
@@ -7,6 +7,8 @@ export const INSTALL_AGENT_TYPES = [
7
7
  "claude-code-desktop",
8
8
  "claude-code-cli",
9
9
  "workbuddy",
10
+ "kimi-code",
11
+ "openclaw",
10
12
  ];
11
13
  const INSTRUCTIONS = {
12
14
  "codex-desktop": "在 Codex Desktop 中始终传这个 Agent Type;付款时把返回的二维码和链接实际展示到当前对话。",
@@ -14,13 +16,12 @@ const INSTRUCTIONS = {
14
16
  "claude-code-desktop": "在 Claude Code Desktop 中始终传这个 Agent Type;付款时把返回的二维码和链接实际展示到当前对话。",
15
17
  "claude-code-cli": "在 Claude Code CLI 中始终传这个 Agent Type;付款交接只应显示在用户可见终端。",
16
18
  workbuddy: "在 WorkBuddy 中始终传这个 Agent Type。运行会持久化 ~/.itpay-v3 的 ItPay Bash 命令前,把 Shell 工具参数 dangerouslyDisableSandbox 设为 true;用户拒绝权限时停止,不要切换 Node、删除 identity 或处理 lock。Checkout 返回 qr_image_url 时,按当次 instruction 调用 present_files 在右侧打开二维码,不要检查本地二维码文件。",
19
+ "kimi-code": "保持 kimi-code Agent Type,使用标准 CLI 命令和终端付款交接;不要切换到其他平台身份。",
20
+ openclaw: "保持 openclaw Agent Type;每个展示命令都从当前可信会话上下文显式传 --host,IM 入口同时传 --target。Telegram 使用返回的原生 message action,其他入口展示标准二维码和付款链接。",
17
21
  };
18
22
  export function runInstall(target, options = {}) {
19
23
  if (!target || target === "list") {
20
- const agentTypes = INSTALL_AGENT_TYPES.map((agentType) => ({
21
- agent_type: agentType,
22
- default_host: defaultHostForAgentType(agentType),
23
- }));
24
+ const agentTypes = INSTALL_AGENT_TYPES.map(installDefinition);
24
25
  writeCommandEnvelope({
25
26
  status: "install_targets",
26
27
  result: { agent_types: agentTypes },
@@ -29,7 +30,7 @@ export function runInstall(target, options = {}) {
29
30
  recovery: [{ command: "itpay docs show install-and-setup", reason: "查看安装与环境说明" }],
30
31
  }, {
31
32
  ...options,
32
- plainResult: agentTypes.map((item) => `${item.agent_type}: ${item.default_host}`),
33
+ plainResult: agentTypes.map((item) => `${item.agent_type}: ${item.default_host ?? "explicit --host required"}`),
33
34
  });
34
35
  return;
35
36
  }
@@ -40,10 +41,8 @@ export function runInstall(target, options = {}) {
40
41
  writeCommandEnvelope({
41
42
  status: "instructions_ready",
42
43
  result: {
43
- agent_type: normalized,
44
- default_host: defaultHostForAgentType(normalized),
44
+ ...installDefinition(normalized),
45
45
  default_api: DEFAULT_BASE_URL,
46
- install_command: "npm install -g @itpay/cli",
47
46
  },
48
47
  instruction: INSTRUCTIONS[normalized],
49
48
  next: {
@@ -56,3 +55,9 @@ export function runInstall(target, options = {}) {
56
55
  function isInstallAgentType(value) {
57
56
  return INSTALL_AGENT_TYPES.includes(value);
58
57
  }
58
+ function installDefinition(agentType) {
59
+ if (agentType === "openclaw") {
60
+ return { agent_type: agentType, default_host: null, host_required: true, native_hosts: ["telegram"] };
61
+ }
62
+ return { agent_type: agentType, default_host: defaultHostForAgentType(agentType) ?? null };
63
+ }
@@ -1,4 +1,5 @@
1
1
  import { operationID } from "../state/config.js";
2
+ import { validateContext } from "../state/client_context.js";
2
3
  import { dispatchRender } from "../render/index.js";
3
4
  import { ensureIdeImageAttach } from "../render/ide.js";
4
5
  import { buildCheckoutHandoff, shouldPrepareLocalCheckoutImage } from "./checkout_handoff.js";
@@ -373,6 +374,11 @@ function actionInputError(serviceExecutionID, message, code = "service_action_in
373
374
  : "使用当前 safe result 中的合法 action 和 candidate rank;需要人确认时先询问用户。", [{ command: `itpay services next ${serviceExecutionID} --json`, reason: "重新读取同一 Execution 的当前可选动作" }]);
374
375
  }
375
376
  export async function runServicesCheckout(backend, config, serviceExecutionID, capabilityID, options = {}) {
377
+ const host = options.host ?? "terminal";
378
+ const contextError = validateContext(host, options.target);
379
+ if (contextError) {
380
+ throw new CommandContractError(contextError.code, contextError.message, "从当前可信会话上下文补齐 Host/target;本次未创建 Checkout。", []);
381
+ }
376
382
  const deliveryContact = {
377
383
  ...(options.deliveryContact ?? {}),
378
384
  ...(options.email ? { email: options.email } : {}),
@@ -409,12 +415,12 @@ export async function runServicesCheckout(backend, config, serviceExecutionID, c
409
415
  const displayToken = checkout.display_token;
410
416
  const checkoutURL = tokenizedCheckoutURL(checkout.checkout_url, displayToken, checkout.qr_payload);
411
417
  const plan = buildCheckoutQRPlan({
412
- host: options.host ?? "terminal",
418
+ host,
413
419
  checkoutID,
414
420
  checkoutURL,
415
421
  displayToken,
416
422
  qrPayload: checkout.qr_payload,
417
- ...(checkout.qr_png_url ? { qrPNGURL: checkout.qr_png_url } : {}),
423
+ ...(checkout.qr_png_url ? { qrPNGURL: absolutePublicURL(config.baseURL, checkout.qr_png_url) } : {}),
418
424
  nextAction: checkout.checkout.next_action,
419
425
  orderItems: response.cart.items.map((item) => ({
420
426
  title: item.title,
@@ -433,9 +439,9 @@ export async function runServicesCheckout(backend, config, serviceExecutionID, c
433
439
  checkoutURL,
434
440
  });
435
441
  const platform = platformKeyForHost(plan.host);
436
- if (platform === "telegram" || platform === "feishu" || platform === "lark") {
442
+ if (!options.jsonOutput && (platform === "telegram" || platform === "feishu" || platform === "lark")) {
437
443
  await dispatchRender(plan, {
438
- host: options.host ?? "terminal",
444
+ host,
439
445
  ...(options.target ? { target: options.target } : {}),
440
446
  ...(options.qrFormat ? { qrFormat: options.qrFormat } : {}),
441
447
  ...(options.qrFilePath ? { qrFilePath: options.qrFilePath } : {}),
@@ -452,7 +458,7 @@ export async function runServicesCheckout(backend, config, serviceExecutionID, c
452
458
  ...(options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}),
453
459
  });
454
460
  }
455
- const envelope = buildServicesCheckoutEnvelope(response, checkoutURL, plan, config.baseURL, options.agentType);
461
+ const envelope = buildServicesCheckoutEnvelope(response, checkoutURL, plan, config.baseURL, options.agentType, options.target);
456
462
  const plainResult = [
457
463
  `service_execution_id: ${response.binding.service_execution_id}`,
458
464
  `checkout_id: ${checkoutID}`,
@@ -933,7 +939,7 @@ function parseValue(value) {
933
939
  return Number(value);
934
940
  return value;
935
941
  }
936
- function buildServicesCheckoutEnvelope(response, checkoutURL, plan, baseURL, agentType) {
942
+ function buildServicesCheckoutEnvelope(response, checkoutURL, plan, baseURL, agentType, target) {
937
943
  const checkout = response.checkout;
938
944
  const platform = platformKeyForHost(plan.host);
939
945
  const amount = formatMoney(checkout.checkout.amount_minor, checkout.checkout.currency);
@@ -941,7 +947,9 @@ function buildServicesCheckoutEnvelope(response, checkoutURL, plan, baseURL, age
941
947
  platform,
942
948
  url: checkoutURL,
943
949
  amount,
950
+ plan,
944
951
  ...(agentType ? { agentType } : {}),
952
+ ...(target ? { target } : {}),
945
953
  ...(checkout.qr_png_url ? { qrImageURL: absolutePublicURL(baseURL, checkout.qr_png_url) } : {}),
946
954
  ...(plan.ideImageAttach?.status === "downloaded" && plan.ideImageAttach.localPath
947
955
  ? { localPath: plan.ideImageAttach.localPath }
package/dist/src/main.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // services/backend/internal/httpapi/handlers/*.go. Commands only
3
3
  // orchestrate; HTTP and rendering live in src/client and src/render.
4
4
  import { Command } from "commander";
5
- import { BackendOverrideError, CLI_VERSION, loadConfig, cartSessionPath, newBackendClient } from "./state/config.js";
5
+ import { BackendOverrideError, CLI_VERSION, cliDistribution, loadConfig, cartSessionPath, newBackendClient } from "./state/config.js";
6
6
  import { DeviceAuthority, DeviceAuthorizationError, DeviceStateError } from "./state/device_authority.js";
7
7
  import { CartSession } from "./state/cart_session.js";
8
8
  import { defaultHostForAgentType, normalizeHost, validateContext } from "./state/client_context.js";
@@ -29,10 +29,17 @@ program
29
29
  .description("V3 ItPay CLI — one entry point for buy workflows and future sell workflows")
30
30
  .option("--agent-type <type>", "agent runtime type used for device enrollment and client-specific guidance")
31
31
  .version(CLI_VERSION);
32
- function withHost(value) {
33
- const host = normalizeHost(value);
32
+ function withHost(value, agentType, target) {
33
+ if (!value && agentType?.trim().toLowerCase() === "openclaw") {
34
+ throw new CommandContractError("host_required", "OpenClaw requires an explicit --host entry", "从当前可信 OpenClaw 会话上下文传入 --host;IM 入口同时传 --target。本次未创建或修改资源。", []);
35
+ }
36
+ const host = normalizeHost(value ?? defaultHostForAgentType(agentType));
34
37
  if (!host) {
35
- throw new Error(`invalid --host "${value ?? ""}". Supported: terminal, codex, claude-code, telegram, discord, whatsapp, feishu, lark, plain-chat`);
38
+ throw new CommandContractError("invalid_host", `invalid --host "${value ?? ""}". Supported: terminal, codex, claude-code, telegram, discord, whatsapp, feishu, lark, plain-chat`, "传入受支持的展示 Host;本次未创建或修改资源。", []);
39
+ }
40
+ const contextError = agentType?.trim().toLowerCase() === "openclaw" ? validateContext(host, target) : undefined;
41
+ if (contextError) {
42
+ throw new CommandContractError(contextError.code, contextError.message, "从当前可信会话上下文补齐 --target;本次未创建或修改资源。", []);
36
43
  }
37
44
  return host;
38
45
  }
@@ -149,7 +156,7 @@ function reportCLIError(error, contract) {
149
156
  } : {}),
150
157
  instruction: incompatible
151
158
  ? requiredCLIVersion
152
- ? "当前 CLI 与 Backend 合约不兼容。停止所有 ItPay 业务命令;只执行 recovery.command,将 @itpay/cli 更新到 Backend 指定的精确版本。安装完成后确认 itpay --version 与 result.required_cli_version 完全一致,再重新运行 readyz。不要安装 latest、猜测版本、切换 Agent Type 或删除 Device 身份。"
159
+ ? distributionUpgradeInstruction()
153
160
  : "立即向用户报告 error.message 并结束本次任务。Backend 未提供可验证的兼容 CLI 版本;不要运行其他 ItPay 或 npm 命令,不要猜测版本、切换 Agent Type 或删除 Device 身份。"
154
161
  : backendInternal
155
162
  ? "Backend 内部故障;立即停止并向用户报告。不要重试、检查或删除 Device 身份、创建替代 Execution、切换 Backend,或尝试 quote、checkout、cart、buy、pay 等付费路径。"
@@ -171,7 +178,7 @@ function reportCLIError(error, contract) {
171
178
  next: null,
172
179
  recovery: incompatible
173
180
  ? requiredCLIVersion
174
- ? [{ command: `npm install -g @itpay/cli@${requiredCLIVersion}`, reason: "安装 Backend 指定的兼容 CLI 版本" }]
181
+ ? [distributionUpgradeAction(requiredCLIVersion)]
175
182
  : []
176
183
  : backendInternal || providerConnectionUnavailable || providerTemporary || providerInputRejected || providerContractMismatch || providerRejected || capabilityInputInvalid
177
184
  ? []
@@ -200,10 +207,29 @@ function docsErrorFallback(jsonOutput) {
200
207
  return {
201
208
  jsonOutput,
202
209
  code: "docs_unavailable",
203
- instruction: "内置文档缺失或损坏;重新安装同版本 CLI 后重试。",
204
- recovery: [{ command: `npm install -g @itpay/cli@${CLI_VERSION}`, reason: "恢复随包发布的文档" }],
210
+ instruction: "内置文档缺失或损坏;通过当前分发方式恢复同版本 CLI 后重试。",
211
+ recovery: [distributionUpgradeAction(CLI_VERSION)],
205
212
  };
206
213
  }
214
+ function distributionUpgradeAction(version) {
215
+ switch (cliDistribution()) {
216
+ case "openclaw-skill-bundle":
217
+ return { command: "openclaw skills update itpay", reason: `更新包含 ItPay CLI ${version} 的 Skill bundle` };
218
+ case "kimi-plugin-bundle":
219
+ return {
220
+ command: "/plugins install https://github.com/itpay-ai/itpay-plugin-kimi-work",
221
+ reason: `更新包含 ItPay CLI ${version} 的 Kimi plugin`,
222
+ };
223
+ default:
224
+ return { command: `npm install -g @itpay/cli@${version}`, reason: "安装 Backend 指定的兼容 CLI 版本" };
225
+ }
226
+ }
227
+ function distributionUpgradeInstruction() {
228
+ if (cliDistribution() === "npm") {
229
+ return "当前 CLI 与 Backend 合约不兼容。停止所有 ItPay 业务命令;只执行 recovery.command,将 @itpay/cli 更新到 Backend 指定的精确版本。安装完成后确认 itpay --version 与 result.required_cli_version 完全一致,再重新运行 readyz。不要安装 latest、猜测版本、切换 Agent Type 或删除 Device 身份。";
230
+ }
231
+ return "当前平台 bundle 与 Backend 合约不兼容。停止所有 ItPay 业务命令;只执行 recovery.command,更新平台 Skill/plugin 后启动新会话,确认 itpay --version 与 result.required_cli_version 完全一致,再重新运行 readyz。不要运行 npm、猜测版本、切换 Agent Type 或删除 Device 身份。";
232
+ }
207
233
  program
208
234
  .command("readyz")
209
235
  .description("Probe the V3 backend readiness endpoint")
@@ -298,8 +324,8 @@ skillCmd
298
324
  reportCLIError(error, {
299
325
  jsonOutput: Boolean(options.json),
300
326
  code: "skill_unavailable",
301
- instruction: "内置 Skill 缺失或损坏;重新安装同版本 CLI 后重试。",
302
- recovery: [{ command: `npm install -g @itpay/cli@${CLI_VERSION}`, reason: "恢复随包发布的 Skill" }],
327
+ instruction: "内置 Skill 缺失或损坏;通过当前分发方式恢复同版本 CLI 后重试。",
328
+ recovery: [distributionUpgradeAction(CLI_VERSION)],
303
329
  });
304
330
  }
305
331
  });
@@ -456,7 +482,7 @@ cart
456
482
  runCartAdd(session, { ...addOptions, jsonOutput });
457
483
  }
458
484
  else {
459
- const host = withHost(options.host ?? defaultHostForAgentType(config.agentType));
485
+ const host = withHost(options.host, config.agentType, options.target);
460
486
  const contextError = validateContext(host, options.target);
461
487
  if (contextError) {
462
488
  throw new CommandContractError(contextError.code, contextError.message, "补齐当前客户端所需的 Host/target;本次未创建或修改 Cart。", [{ command: "itpay cart add --help", reason: "查看客户端参数" }]);
@@ -692,7 +718,7 @@ program
692
718
  }
693
719
  const quantity = positiveInteger(options.quantity, "--quantity");
694
720
  const timeout = positiveInteger(options.timeout, "--timeout");
695
- const host = withHost(options.host ?? defaultHostForAgentType(config.agentType));
721
+ const host = withHost(options.host, config.agentType, options.target);
696
722
  const contact = {};
697
723
  if (options.contactEmail)
698
724
  contact.email = options.contactEmail;
@@ -756,11 +782,11 @@ program
756
782
  .option("--json", "output compact JSON")
757
783
  .action(async (options) => {
758
784
  const config = loadConfig();
759
- const host = withHost(options.host ?? defaultHostForAgentType(config.agentType));
760
785
  const session = CartSession.loadFromFile(cartSessionPath(), config.checkoutCurrency);
761
786
  const snap = session.show();
762
787
  const backend = newBackendClient(config);
763
788
  try {
789
+ const host = withHost(options.host, config.agentType, options.target);
764
790
  const { checkoutID, displayToken } = resolveCheckoutPresentationArgs({
765
791
  ...(options.id ? { requestedCheckoutID: options.id } : {}),
766
792
  ...(options.token ? { requestedDisplayToken: options.token } : {}),
@@ -771,6 +797,7 @@ program
771
797
  checkoutID,
772
798
  displayToken,
773
799
  host,
800
+ ...(options.target ? { target: options.target } : {}),
774
801
  ...(config.agentType ? { agentType: config.agentType } : {}),
775
802
  baseURL: config.baseURL,
776
803
  jsonOutput: Boolean(options.json),
@@ -817,7 +844,7 @@ program
817
844
  if (!displayToken) {
818
845
  throw new CommandContractError("checkout_token_required", "display token is required for this checkout", "提供同一 Checkout 的 display token;不要拼接其他 Checkout 的 token。", [{ command: "itpay next --json", reason: "恢复本机保存的同一 Checkout" }]);
819
846
  }
820
- const host = withHost(options.host ?? defaultHostForAgentType(config.agentType));
847
+ const host = withHost(options.host, config.agentType, options.target);
821
848
  const contextError = validateContext(host, options.target);
822
849
  if (contextError) {
823
850
  throw new CommandContractError(contextError.code, contextError.message, "为当前 Host 提供有效 target;本次未创建 Payment Intent。", [
@@ -1024,7 +1051,7 @@ services
1024
1051
  const backend = newBackendClient(config);
1025
1052
  try {
1026
1053
  await runServicesStart(backend, serviceID, {
1027
- host: withHost(options.host ?? defaultHostForAgentType(config.agentType)),
1054
+ host: withHost(options.host, config.agentType, options.target),
1028
1055
  ...(options.target ? { target: options.target } : {}),
1029
1056
  jsonOutput: Boolean(options.json),
1030
1057
  });
@@ -1150,7 +1177,7 @@ services
1150
1177
  ...(options.email ? { email: options.email } : {}),
1151
1178
  lockedInput: parseKeyValueList(options.input),
1152
1179
  resume: Boolean(options.resume),
1153
- host: withHost(options.host ?? defaultHostForAgentType(config.agentType)),
1180
+ host: withHost(options.host, config.agentType, options.target),
1154
1181
  ...(config.agentType ? { agentType: config.agentType } : {}),
1155
1182
  ...(options.target ? { target: options.target } : {}),
1156
1183
  ...(options.qrFormat ? { qrFormat: options.qrFormat } : {}),
@@ -28,14 +28,49 @@ function buttonsFor(plan) {
28
28
  : []),
29
29
  ];
30
30
  }
31
+ function nativeButton(button) {
32
+ if (button.kind === "url") {
33
+ return { label: button.label, action: { type: "url", url: button.url ?? "" } };
34
+ }
35
+ const value = button.intent === "check_checkout_status"
36
+ ? `itp:checkout:${button.ref ?? ""}`
37
+ : `itp:${button.intent ?? "callback"}:${button.ref ?? ""}`;
38
+ return { label: button.label, action: { type: "callback", value } };
39
+ }
31
40
  function format(plan) {
32
41
  return plan.kind === "payment_qr" ? "photo_text_inline_buttons" : "text_inline_buttons";
33
42
  }
34
43
  export function renderTelegram(plan, options) {
35
44
  const out = options.output ?? ((line) => process.stdout.write(line));
36
- const buttons = buttonsFor(plan);
45
+ const agentAction = buildOpenClawTelegramAction(plan, options.target);
46
+ const presentation = agentAction.arguments.presentation;
47
+ const media = collectTelegramMedia(plan);
48
+ const text = agentAction.arguments.message;
49
+ const openclawMessage = {
50
+ command: [
51
+ "openclaw",
52
+ "message",
53
+ "send",
54
+ "--channel",
55
+ "telegram",
56
+ "--target",
57
+ options.target,
58
+ "--message",
59
+ text,
60
+ ...(media.length > 0 ? ["--media", media[0].url] : []),
61
+ "--presentation",
62
+ JSON.stringify(presentation),
63
+ ],
64
+ // Hint: agents that cannot run openclaw must stop and tell the user
65
+ // they lack the native Telegram buttons tool — never downgrade.
66
+ if_unavailable: "Current agent cannot run `openclaw message send`. Stop and tell the user the native Telegram inline-button tool is missing; do not downgrade to a markdown table or plain link.",
67
+ };
68
+ out(JSON.stringify({ presentation, agent_action: agentAction, openclaw_message: openclawMessage }, null, 2) + "\n");
69
+ }
70
+ export function buildOpenClawTelegramAction(plan, target) {
71
+ const buttons = buttonsFor(plan).map(nativeButton);
37
72
  const media = collectTelegramMedia(plan);
38
- const text = plan.kind === "payment_qr"
73
+ const message = plan.kind === "payment_qr"
39
74
  ? `ItPay payment QR — ${plan.summary}`
40
75
  : plan.kind === "auth_qr"
41
76
  ? `ItPay auth required — ${plan.summary}`
@@ -43,12 +78,12 @@ export function renderTelegram(plan, options) {
43
78
  const presentation = {
44
79
  format: format(plan),
45
80
  media,
46
- text,
81
+ text: message,
47
82
  links: plan.platform.links,
48
83
  buttons,
49
84
  interactions: plan.platform.interactions ?? [],
50
85
  blocks: [
51
- { type: "text", text },
86
+ { type: "text", text: message },
52
87
  ...(media.length > 0 ? [{ type: "image", url: media[0].url }] : []),
53
88
  { type: "buttons", buttons },
54
89
  ],
@@ -64,31 +99,22 @@ export function renderTelegram(plan, options) {
64
99
  must_render_reason: plan.ideImageAttach.mustRenderReason,
65
100
  ...(plan.ideImageAttach.error ? { error: plan.ideImageAttach.error } : {}),
66
101
  action: "agent_must_render_into_ide_chat",
67
- instructions: ideImageAttachBlock(plan.ideImageAttach).filter((l) => l.length > 0),
102
+ instructions: ideImageAttachBlock(plan.ideImageAttach).filter((line) => line.length > 0),
68
103
  },
69
104
  }
70
105
  : {}),
71
106
  };
72
- const openclawMessage = {
73
- command: [
74
- "openclaw",
75
- "message",
76
- "send",
77
- "--channel",
78
- "telegram",
79
- "--target",
80
- options.target,
81
- "--message",
82
- text,
83
- ...(media.length > 0 ? ["--media", media[0].url] : []),
84
- "--presentation",
85
- JSON.stringify(presentation),
86
- ],
87
- // Hint: agents that cannot run openclaw must stop and tell the user
88
- // they lack the native Telegram buttons tool — never downgrade.
89
- if_unavailable: "Current agent cannot run `openclaw message send`. Stop and tell the user the native Telegram inline-button tool is missing; do not downgrade to a markdown table or plain link.",
107
+ return {
108
+ tool: "message",
109
+ arguments: {
110
+ action: "send",
111
+ channel: "telegram",
112
+ target,
113
+ message,
114
+ ...(media[0]?.url ? { media: media[0].url } : {}),
115
+ presentation,
116
+ },
90
117
  };
91
- out(JSON.stringify({ presentation, openclaw_message: openclawMessage }, null, 2) + "\n");
92
118
  }
93
119
  export function renderTelegramInteraction(request, options) {
94
120
  const out = options.output ?? ((line) => process.stdout.write(line));
@@ -97,7 +123,7 @@ export function renderTelegramInteraction(request, options) {
97
123
  mimeType: item.mimeType ?? "image/png",
98
124
  }));
99
125
  const buttons = request.kind === "selector"
100
- ? request.options.map((option) => selectorButton(request.id, option))
126
+ ? request.options.map((option) => nativeButton(selectorButton(request.id, option)))
101
127
  : [];
102
128
  const text = `${request.title} — ${request.prompt}`;
103
129
  const presentation = {
@@ -62,6 +62,8 @@ export function defaultHostForAgentType(agentType) {
62
62
  return "claude-code";
63
63
  if (normalized === "workbuddy")
64
64
  return "plain-chat";
65
+ if (normalized === "openclaw")
66
+ return undefined;
65
67
  return "terminal";
66
68
  }
67
69
  export function validateContext(host, target) {
@@ -12,11 +12,18 @@ import { DeviceAuthority } from "./device_authority.js";
12
12
  import { OperationJournal } from "./operation_journal.js";
13
13
  export const DEFAULT_BASE_URL = "https://app.itpay.ai";
14
14
  export const DEV_BASE_URL = "https://dev.itpay.ai";
15
- export const CLI_VERSION = "2.0.15";
15
+ export const CLI_VERSION = "2.0.16";
16
16
  export const API_CONTRACT_REVISION = "sha256:7f4c40b082292bf823631bcd37d452f4a8537153e30636d5eb3a2b24a77ce602";
17
17
  const CART_SESSION_DEFAULT_DIR = ".itpay-v3";
18
18
  const CART_SESSION_FILENAME = "cart.json";
19
19
  const OPERATION_JOURNAL_FILENAME = "operations.json";
20
+ export function cliDistribution(env = process.env) {
21
+ if (env.ITPAY_DISTRIBUTION === "openclaw-skill-bundle")
22
+ return "openclaw-skill-bundle";
23
+ if (env.ITPAY_DISTRIBUTION === "kimi-plugin-bundle")
24
+ return "kimi-plugin-bundle";
25
+ return "npm";
26
+ }
20
27
  export class BackendOverrideError extends Error {
21
28
  code = "backend_override_forbidden";
22
29
  constructor() {
@@ -4,7 +4,7 @@
4
4
  "product_scope": "itpay is the single public CLI entry point, and $itpay is its user-facing Skill invocation. Under that one product entry point, the two top-level commerce actions are buy and sell: Buyer workflows are available now; Seller workflows will use the same entry point and are not implemented yet.",
5
5
  "topic": "install-and-setup",
6
6
  "title": "Install And Identify The ItPay Agent Runtime",
7
- "purpose": "Install the CLI, load the complete ItPay Skill, and select one stable Agent Type without confusing identity, Backend, Host, target, or chat window.",
7
+ "purpose": "Load the complete ItPay Skill and select one stable Agent Type without confusing distribution, identity, Backend, Host, target, or chat window.",
8
8
  "when_to_use": [
9
9
  "The CLI is being installed or upgraded.",
10
10
  "The agent needs to confirm its stable runtime identity and default output Host."
@@ -23,7 +23,7 @@
23
23
  {
24
24
  "intent": "list supported Agent Types",
25
25
  "command": "itpay install --json",
26
- "success_signal": "status is install_targets and exactly five agent_type/default_host pairs are returned"
26
+ "success_signal": "status is install_targets and exactly seven supported Agent Type definitions are returned"
27
27
  },
28
28
  {
29
29
  "intent": "read setup for the real runtime",
@@ -32,14 +32,14 @@
32
32
  }
33
33
  ],
34
34
  "agent_rules": [
35
- "Install with npm install -g @itpay/cli.",
35
+ "Use the CLI distribution already provided by the current npm package, Skill, or plugin; install only through that distribution's own instructions.",
36
36
  "Production defaults to https://app.itpay.ai. Testing may set ITPAY_BACKEND_URL to the exact official URL https://dev.itpay.ai; no other Backend is allowed.",
37
- "If backend_contract_incompatible includes result.required_cli_version, stop all ItPay business commands and run only the exact npm install recovery returned by the CLI; never replace its version with latest.",
37
+ "If backend_contract_incompatible includes result.required_cli_version, stop all ItPay business commands and use only the exact distribution-specific update recovery returned by the CLI; never replace its version with latest.",
38
38
  "After upgrading, require itpay --version to equal result.required_cli_version before running readyz again; never change Agent Type or Device identity to recover compatibility.",
39
- "Use one exact type: codex-desktop, codex-cli, claude-code-desktop, claude-code-cli, or workbuddy.",
39
+ "Use one exact type: codex-desktop, codex-cli, claude-code-desktop, claude-code-cli, workbuddy, kimi-code, or openclaw.",
40
40
  "One local private key is reused, while Device registrations and quota lineage remain separate for app.itpay.ai and dev.itpay.ai.",
41
41
  "Within each official Backend registration, each Agent Type has one Agent Instance; all windows and chats of the same type reuse it.",
42
- "Agent Type identifies the runtime. Host controls rendering and target only identifies a presentation destination.",
42
+ "Agent Type identifies the runtime. Host controls rendering and target only identifies a presentation destination. OpenClaw requires an explicit Host and IM target; Kimi Code uses the standard terminal CLI contract.",
43
43
  "Keep the exact Agent Type in every next or recovery command; do not fall back to a type previously used by another runtime.",
44
44
  "Do not change Agent Type or rotate local identity to reset quota or recover a failed command."
45
45
  ],
@@ -66,6 +66,8 @@
66
66
  "backend",
67
67
  "codex",
68
68
  "claude code",
69
- "workbuddy"
69
+ "workbuddy",
70
+ "kimi code",
71
+ "openclaw"
70
72
  ]
71
73
  }
@@ -32,6 +32,7 @@
32
32
  "Before creating a single-Service Checkout, send the exact price confirmation requested by the current instruction, stop, and wait for explicit human agreement.",
33
33
  "After presenting a Checkout, stop. Run the returned next.command only after the human completes the payment action or asks to query the same Checkout.",
34
34
  "For workbuddy with plain-chat, present the returned HTTPS QR with present_files exactly as instructed; presentation failure falls back only to handoff.url and never creates another payment resource.",
35
+ "For openclaw with telegram, execute the returned native message action using the current trusted target. For any other explicit OpenClaw Host, show the returned HTTPS QR image and Checkout URL.",
35
36
  "Payment is verified only by Backend Checkout or Order state, never by QR rendering, redirect, or user claim.",
36
37
  "A terminal payment state must never display another payment handoff."
37
38
  ],
@@ -59,6 +59,7 @@
59
59
  "Keep the same explicit Agent Type through next and recovery commands; same-type windows reuse one Backend Agent Instance.",
60
60
  "When testing dev, execute every returned command unchanged so its ITPAY_BACKEND_URL=https://dev.itpay.ai prefix is preserved.",
61
61
  "Use --target only for a Host presentation destination. Put capability business values in --input key=value exactly as required by the schema.",
62
+ "OpenClaw must pass the current entry as --host and an IM destination as --target. Kimi Code follows the standard terminal CLI flow.",
62
63
  "Start a separate Service Execution for each independent service intent; quota remains shared according to Backend identity policy.",
63
64
  "Ask the user for required email or contact values and explain their purpose; never invent them.",
64
65
  "Agent-visible delivery is returned by services next. Protected delivery is read only by services read-result while a human grant is active.",
@@ -34,6 +34,18 @@
34
34
  "agent_type": "workbuddy",
35
35
  "default_host": "plain-chat",
36
36
  "responsibility": "when handoff.qr_image_url exists, use its exact HTTPS value as the only files element in present_files; otherwise send the Checkout URL without calling present_files; show the amount and stop"
37
+ },
38
+ {
39
+ "agent_type": "kimi-code",
40
+ "default_host": "terminal",
41
+ "responsibility": "use the standard CLI terminal QR and Checkout URL"
42
+ },
43
+ {
44
+ "agent_type": "openclaw",
45
+ "default_host": null,
46
+ "host_required": true,
47
+ "native_hosts": ["telegram"],
48
+ "responsibility": "pass the current entry explicitly; for Telegram execute handoff.agent_action with the current target, otherwise show the returned HTTPS QR image and Checkout URL"
37
49
  }
38
50
  ],
39
51
  "commands": [
@@ -53,7 +65,8 @@
53
65
  "A local QR path is not visible until a desktop Agent attaches or renders that file on the human-facing surface; WorkBuddy never receives a local QR path.",
54
66
  "For workbuddy with plain-chat, call present_files exactly as the returned instruction says. If it fails, send only handoff.url, report that the preview did not open, and stop.",
55
67
  "Do not claim the handoff was shown until both a usable payment image or QR and the Checkout URL are visible.",
56
- "IM Hosts that require a target must receive --target; the initial five Agent Types currently default to desktop, terminal, or plain-chat Hosts."
68
+ "IM Hosts that require a target must receive --target before Checkout creation. OpenClaw has no default Host; Kimi Code uses terminal.",
69
+ "OpenClaw Telegram typed callback actions carry only the Checkout ID, never a display token, and never prove payment."
57
70
  ],
58
71
  "forbidden": [
59
72
  "Do not expose display tokens separately from the tokenized Checkout handoff.",
@@ -74,6 +87,9 @@
74
87
  "markdown",
75
88
  "terminal",
76
89
  "desktop",
77
- "workbuddy"
90
+ "workbuddy",
91
+ "kimi code",
92
+ "openclaw",
93
+ "telegram"
78
94
  ]
79
95
  }