@itpay/cli 2.0.14 → 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.
- package/README.md +5 -4
- package/dist/src/commands/buy.js +3 -0
- package/dist/src/commands/checkout.js +4 -2
- package/dist/src/commands/checkout_handoff.js +7 -0
- package/dist/src/commands/compatibility.js +0 -1
- package/dist/src/commands/guidance.js +6 -5
- package/dist/src/commands/install.js +15 -10
- package/dist/src/commands/readyz.js +6 -2
- package/dist/src/commands/services.js +14 -6
- package/dist/src/main.js +60 -27
- package/dist/src/render/telegram.js +51 -25
- package/dist/src/state/client_context.js +2 -0
- package/dist/src/state/config.js +48 -7
- package/docs/agent/buyer/identity-and-sessions.json +10 -10
- package/docs/agent/buyer/install-and-setup.json +13 -12
- package/docs/agent/buyer/payment-flow.json +1 -0
- package/docs/agent/buyer/quickstart.json +4 -2
- package/docs/agent/buyer/render-hosts.json +18 -2
- package/docs/cli-reference/agent-types.md +12 -2
- package/docs/cli-reference/commands/buy.md +9 -4
- package/docs/cli-reference/commands/cart/add.md +1 -1
- package/docs/cli-reference/commands/cart/clear.md +1 -1
- package/docs/cli-reference/commands/cart/index.md +1 -1
- package/docs/cli-reference/commands/cart/next.md +1 -1
- package/docs/cli-reference/commands/cart/remove.md +2 -2
- package/docs/cli-reference/commands/cart/show.md +1 -1
- package/docs/cli-reference/commands/catalog/index.md +1 -1
- package/docs/cli-reference/commands/checkout.md +5 -3
- package/docs/cli-reference/commands/device.md +1 -1
- package/docs/cli-reference/commands/docs/index.md +1 -1
- package/docs/cli-reference/commands/docs/list.md +1 -1
- package/docs/cli-reference/commands/docs/search.md +1 -1
- package/docs/cli-reference/commands/docs/show.md +1 -1
- package/docs/cli-reference/commands/install.md +46 -12
- package/docs/cli-reference/commands/next.md +1 -1
- package/docs/cli-reference/commands/readyz.md +61 -10
- package/docs/cli-reference/commands/refund/cancel.md +1 -1
- package/docs/cli-reference/commands/refund/create.md +1 -1
- package/docs/cli-reference/commands/refund/index.md +1 -1
- package/docs/cli-reference/commands/refund/list.md +1 -1
- package/docs/cli-reference/commands/services/action.md +1 -1
- package/docs/cli-reference/commands/services/checkout.md +5 -1
- package/docs/cli-reference/commands/services/events.md +1 -1
- package/docs/cli-reference/commands/services/get.md +1 -1
- package/docs/cli-reference/commands/services/index.md +1 -1
- package/docs/cli-reference/commands/services/invoke.md +1 -1
- package/docs/cli-reference/commands/services/list.md +1 -1
- package/docs/cli-reference/commands/services/read-result.md +1 -1
- package/docs/cli-reference/conventions.md +7 -1
- package/docs/skill-bundle-rollout/01-mcp-authentication.md +256 -0
- package/docs/skill-bundle-rollout/02-platform-bundle-repositories.md +279 -0
- package/docs/skill-bundle-rollout/03-platform-publishing.md +271 -0
- package/docs/skill-bundle-rollout/04-first-wave-platforms.md +32 -0
- package/docs/skill-bundle-rollout/README.md +99 -0
- package/package.json +1 -1
- package/skills/itpay/SKILL.md +4 -4
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ itpay --agent-type codex-desktop readyz --json
|
|
|
15
15
|
# follow next.command: typed skill show, then catalog list
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
The
|
|
18
|
+
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.
|
|
19
19
|
|
|
20
20
|
## Output Contract
|
|
21
21
|
|
|
@@ -46,11 +46,11 @@ Normative per-command contracts: [CLI Command Reference](docs/cli-reference/inde
|
|
|
46
46
|
|
|
47
47
|
`--agent-type` identifies the stable runtime and registered Agent instance. Every returned ItPay command preserves it. Different windows or chats of the same type reuse one Agent Instance; they are not separate identities. `--host` only selects the human presentation surface, and `--target` only routes output to a Host destination. Use `itpay install <agent_type> --json` for the exact responsibility.
|
|
48
48
|
|
|
49
|
-
The local installation keeps one Ed25519 private key and
|
|
49
|
+
The local installation keeps one Ed25519 private key and a separate registration for each official Backend, with one Agent Instance per Agent Type under each registration. A rejected session is renewed and the same request is retried once; revoked v2 registrations are never silently replaced.
|
|
50
50
|
|
|
51
51
|
## Command Families
|
|
52
52
|
|
|
53
|
-
- `readyz`:
|
|
53
|
+
- `readyz`: selected official Backend liveness; `catalog list`: compatibility-gated discovery.
|
|
54
54
|
- `services start/invoke/action/checkout/next`: generic Service Execution flow.
|
|
55
55
|
- `cart add/show/remove/clear/next`, `buy`: canonical Cart and ordinary Checkout flow.
|
|
56
56
|
- `checkout`: authoritative payment and fulfillment recovery.
|
|
@@ -75,11 +75,12 @@ itpay --agent-type <agent_type> services checkout <service_execution_id> --resum
|
|
|
75
75
|
itpay checkout --id <checkout_id> --token <display_token> --json
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
The local `~/.itpay-v3` directory stores one owner-only signing key,
|
|
78
|
+
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.
|
|
79
79
|
|
|
80
80
|
## Environment
|
|
81
81
|
|
|
82
82
|
- `ITPAY_AGENT_TYPE`: stable alternative to global `--agent-type`.
|
|
83
|
+
- `ITPAY_BACKEND_URL`: optional test override; only the exact official URL `https://dev.itpay.ai` is accepted. Unset it for production.
|
|
83
84
|
- `ITPAY_BEARER_TOKEN`: account-scoped Buyer session for account-only commands such as `orders`.
|
|
84
85
|
- `ITPAY_CART_SESSION_PATH`: local recovery-state path override.
|
|
85
86
|
- `ITPAY_CURRENCY`: ordinary Cart currency, default `CNY`.
|
package/dist/src/commands/buy.js
CHANGED
|
@@ -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) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { HttpError } from "../client/http.js";
|
|
2
2
|
import { resolveOutput } from "../render/sink.js";
|
|
3
3
|
import { declaredAgentType, qualifyItPayCommand } from "../state/agent_type.js";
|
|
4
|
+
import { qualifyBackendCommand } from "../state/config.js";
|
|
4
5
|
export function isTerminalServiceExecutionStatus(status) {
|
|
5
6
|
return status === "failed" || status === "refunded" || status === "cancelled";
|
|
6
7
|
}
|
|
@@ -84,7 +85,7 @@ export function printAgentGuidance(guidance, output) {
|
|
|
84
85
|
out("next actions:\n");
|
|
85
86
|
for (const action of guidance.next_actions) {
|
|
86
87
|
out(` - ${action.label}\n`);
|
|
87
|
-
out(` ${qualifyItPayCommand(action.command, agentType)}\n`);
|
|
88
|
+
out(` ${qualifyBackendCommand(qualifyItPayCommand(action.command, agentType))}\n`);
|
|
88
89
|
if (action.requires_human)
|
|
89
90
|
out(" requires human confirmation\n");
|
|
90
91
|
if (action.reason)
|
|
@@ -95,17 +96,17 @@ export function printAgentGuidance(guidance, output) {
|
|
|
95
96
|
out("recovery:\n");
|
|
96
97
|
for (const action of guidance.recovery) {
|
|
97
98
|
out(` - ${action.label}\n`);
|
|
98
|
-
out(` ${qualifyItPayCommand(action.command, agentType)}\n`);
|
|
99
|
+
out(` ${qualifyBackendCommand(qualifyItPayCommand(action.command, agentType))}\n`);
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
function qualifyEnvelope(value, agentType) {
|
|
103
104
|
return {
|
|
104
105
|
...value,
|
|
105
|
-
next: value.next ? { ...value.next, command: qualifyItPayCommand(value.next.command, agentType) } : null,
|
|
106
|
+
next: value.next ? { ...value.next, command: qualifyBackendCommand(qualifyItPayCommand(value.next.command, agentType)) } : null,
|
|
106
107
|
recovery: value.recovery.map((action) => ({
|
|
107
108
|
...action,
|
|
108
|
-
command: qualifyItPayCommand(action.command, agentType),
|
|
109
|
+
command: qualifyBackendCommand(qualifyItPayCommand(action.command, agentType)),
|
|
109
110
|
})),
|
|
110
111
|
};
|
|
111
112
|
}
|
|
@@ -304,7 +305,7 @@ export function errorRecoveryActions(error) {
|
|
|
304
305
|
return [
|
|
305
306
|
{
|
|
306
307
|
id: "retry_after_backend_recovers",
|
|
307
|
-
label: "Retry after
|
|
308
|
+
label: "Retry after the selected official Backend is reachable",
|
|
308
309
|
command: "itpay readyz",
|
|
309
310
|
},
|
|
310
311
|
];
|
|
@@ -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(
|
|
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,19 +41,23 @@ export function runInstall(target, options = {}) {
|
|
|
40
41
|
writeCommandEnvelope({
|
|
41
42
|
status: "instructions_ready",
|
|
42
43
|
result: {
|
|
43
|
-
|
|
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: {
|
|
50
49
|
command: `itpay --agent-type ${normalized} readyz --json`,
|
|
51
|
-
reason: "
|
|
50
|
+
reason: "验证当前官方 ItPay API 的可用性",
|
|
52
51
|
},
|
|
53
|
-
recovery: [{ command: "itpay docs show install-and-setup", reason: "
|
|
52
|
+
recovery: [{ command: "itpay docs show install-and-setup", reason: "查看官方 Backend 和首次使用说明" }],
|
|
54
53
|
}, options);
|
|
55
54
|
}
|
|
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
|
+
}
|
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
import { writeCommandEnvelope } from "./guidance.js";
|
|
3
3
|
export async function runReadyz(backend, options = {}) {
|
|
4
4
|
const response = await backend.readyz();
|
|
5
|
+
const backendURL = options.backendURL ?? "https://app.itpay.ai";
|
|
6
|
+
const environment = options.environment ?? "production";
|
|
5
7
|
writeCommandEnvelope({
|
|
6
8
|
status: response.status,
|
|
7
|
-
result: { backend: "available", ...(options.agentType ? { agent_type: options.agentType } : {}) },
|
|
8
|
-
instruction:
|
|
9
|
+
result: { backend: "available", backend_url: backendURL, environment, ...(options.agentType ? { agent_type: options.agentType } : {}) },
|
|
10
|
+
instruction: environment === "development"
|
|
11
|
+
? "ItPay dev 可用;后续必须执行返回的完整命令,并继续使用同一个 dev Backend。先完整读取内置 ItPay Skill,再进入当前已支持的 buy 流程。"
|
|
12
|
+
: "ItPay 可用;先完整读取内置 ItPay Skill,再进入当前已支持的 buy 流程。sell 将来也使用同一入口,但当前尚未实现。",
|
|
9
13
|
next: { command: "itpay skill show itpay --json", reason: "加载完整操作与安全规则" },
|
|
10
14
|
recovery: [],
|
|
11
15
|
}, options);
|
|
@@ -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
|
|
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
|
|
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 { 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
|
-
|
|
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
|
|
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
|
}
|
|
@@ -83,6 +90,7 @@ function resolveCheckoutPresentationArgs(input) {
|
|
|
83
90
|
}
|
|
84
91
|
function reportCLIError(error, contract) {
|
|
85
92
|
const commandError = error instanceof CommandContractError ? error : undefined;
|
|
93
|
+
const backendOverrideError = error instanceof BackendOverrideError ? error : undefined;
|
|
86
94
|
const deviceError = error instanceof DeviceAuthorizationError ? error : undefined;
|
|
87
95
|
const stateError = error instanceof DeviceStateError ? error : undefined;
|
|
88
96
|
const httpRecovery = errorRecoveryActions(error).map((action) => ({
|
|
@@ -122,11 +130,11 @@ function reportCLIError(error, contract) {
|
|
|
122
130
|
: deviceError
|
|
123
131
|
? "Device 身份验证失败;停止重试,不要切换 Agent Type、删除状态或旋转私钥。"
|
|
124
132
|
: undefined;
|
|
125
|
-
if (contract || commandError) {
|
|
133
|
+
if (contract || commandError || backendOverrideError) {
|
|
126
134
|
writeCommandEnvelope({
|
|
127
135
|
status: "error",
|
|
128
136
|
error: {
|
|
129
|
-
code: incompatible ? "backend_contract_incompatible" : commandError?.code ?? (error instanceof HttpError ? error.code : stateError?.code ?? deviceError?.code ?? contract?.code ?? "command_failed"),
|
|
137
|
+
code: incompatible ? "backend_contract_incompatible" : backendOverrideError?.code ?? commandError?.code ?? (error instanceof HttpError ? error.code : stateError?.code ?? deviceError?.code ?? contract?.code ?? "command_failed"),
|
|
130
138
|
message: error instanceof Error ? error.message : String(error),
|
|
131
139
|
},
|
|
132
140
|
...(requiredCLIVersion ? {
|
|
@@ -148,7 +156,7 @@ function reportCLIError(error, contract) {
|
|
|
148
156
|
} : {}),
|
|
149
157
|
instruction: incompatible
|
|
150
158
|
? requiredCLIVersion
|
|
151
|
-
?
|
|
159
|
+
? distributionUpgradeInstruction()
|
|
152
160
|
: "立即向用户报告 error.message 并结束本次任务。Backend 未提供可验证的兼容 CLI 版本;不要运行其他 ItPay 或 npm 命令,不要猜测版本、切换 Agent Type 或删除 Device 身份。"
|
|
153
161
|
: backendInternal
|
|
154
162
|
? "Backend 内部故障;立即停止并向用户报告。不要重试、检查或删除 Device 身份、创建替代 Execution、切换 Backend,或尝试 quote、checkout、cart、buy、pay 等付费路径。"
|
|
@@ -164,17 +172,19 @@ function reportCLIError(error, contract) {
|
|
|
164
172
|
? "Provider 拒绝了本次请求,但未声明这是输入错误;向用户逐字报告 error.message 和 result.quota 并停止。不要修改输入、不要重试、不要创建新 Execution。"
|
|
165
173
|
: capabilityInputInvalid
|
|
166
174
|
? "输入未通过本地校验,上游尚未被调用且用户额度未变化。向用户逐字报告 error.message 并停止,不要原样重试或运行其他恢复命令。用户提供修正后的输入后,继续使用当前未结束的 Execution。"
|
|
167
|
-
:
|
|
175
|
+
: backendOverrideError
|
|
176
|
+
? "移除 ITPAY_BACKEND_URL 使用正式环境,或准确设置为 https://dev.itpay.ai。"
|
|
177
|
+
: commandError?.instruction ?? authorizationInstruction ?? contract?.instruction ?? "检查命令参数后重试。",
|
|
168
178
|
next: null,
|
|
169
179
|
recovery: incompatible
|
|
170
180
|
? requiredCLIVersion
|
|
171
|
-
? [
|
|
181
|
+
? [distributionUpgradeAction(requiredCLIVersion)]
|
|
172
182
|
: []
|
|
173
183
|
: backendInternal || providerConnectionUnavailable || providerTemporary || providerInputRejected || providerContractMismatch || providerRejected || capabilityInputInvalid
|
|
174
184
|
? []
|
|
175
|
-
: commandError?.recovery ?? (stateError ? stateRecovery : deviceError ? deviceRecovery : identityRecovery ? httpRecovery : contract?.recovery ?? []),
|
|
185
|
+
: backendOverrideError ? [] : commandError?.recovery ?? (stateError ? stateRecovery : deviceError ? deviceRecovery : identityRecovery ? httpRecovery : contract?.recovery ?? []),
|
|
176
186
|
}, {
|
|
177
|
-
...(contract?.jsonOutput !== undefined ? { jsonOutput: contract.jsonOutput } : {}),
|
|
187
|
+
...(contract?.jsonOutput !== undefined ? { jsonOutput: contract.jsonOutput } : backendOverrideError ? { jsonOutput: process.argv.includes("--json") } : {}),
|
|
178
188
|
output: (text) => { process.stderr.write(text); },
|
|
179
189
|
});
|
|
180
190
|
process.exitCode = 1;
|
|
@@ -197,10 +207,29 @@ function docsErrorFallback(jsonOutput) {
|
|
|
197
207
|
return {
|
|
198
208
|
jsonOutput,
|
|
199
209
|
code: "docs_unavailable",
|
|
200
|
-
instruction: "
|
|
201
|
-
recovery: [
|
|
210
|
+
instruction: "内置文档缺失或损坏;通过当前分发方式恢复同版本 CLI 后重试。",
|
|
211
|
+
recovery: [distributionUpgradeAction(CLI_VERSION)],
|
|
202
212
|
};
|
|
203
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
|
+
}
|
|
204
233
|
program
|
|
205
234
|
.command("readyz")
|
|
206
235
|
.description("Probe the V3 backend readiness endpoint")
|
|
@@ -209,31 +238,34 @@ program
|
|
|
209
238
|
const config = loadConfig();
|
|
210
239
|
const backend = newBackendClient(config);
|
|
211
240
|
try {
|
|
212
|
-
await runReadyz(backend, {
|
|
241
|
+
await runReadyz(backend, {
|
|
242
|
+
jsonOutput: Boolean(options.json), backendURL: config.baseURL, environment: config.environment,
|
|
243
|
+
...(config.agentType ? { agentType: config.agentType } : {}),
|
|
244
|
+
});
|
|
213
245
|
}
|
|
214
246
|
catch (error) {
|
|
215
247
|
reportCLIError(error, {
|
|
216
248
|
jsonOutput: Boolean(options.json),
|
|
217
249
|
code: "backend_unavailable",
|
|
218
|
-
instruction:
|
|
250
|
+
instruction: `当前官方 Backend ${config.baseURL} 不可用;恢复前不要继续下单,也不要切换环境。`,
|
|
219
251
|
recovery: [
|
|
220
|
-
{ command: "itpay readyz", reason: "
|
|
252
|
+
{ command: "itpay readyz", reason: "重试当前官方 Backend 的可用性检查" },
|
|
221
253
|
],
|
|
222
254
|
});
|
|
223
255
|
}
|
|
224
256
|
});
|
|
225
257
|
// --- device ---------------------------------------------------------------
|
|
226
|
-
const deviceCmd = program.command("device").description("Recover
|
|
258
|
+
const deviceCmd = program.command("device").description("Recover the current official Backend registration after an operator-confirmed reset");
|
|
227
259
|
deviceCmd
|
|
228
260
|
.command("recover")
|
|
229
|
-
.description("Forget only the
|
|
261
|
+
.description("Forget only the current official Backend registration while preserving the local private key")
|
|
230
262
|
.option("--confirm-backend-reset", "confirm that an operator reset the selected Backend registration database")
|
|
231
263
|
.option("--json", "output JSON instead of terminal text")
|
|
232
264
|
.action(async (options) => {
|
|
233
265
|
const config = loadConfig();
|
|
234
266
|
try {
|
|
235
267
|
if (!config.agentType) {
|
|
236
|
-
throw new CommandContractError("agent_type_required",
|
|
268
|
+
throw new CommandContractError("agent_type_required", `agent type is required for ${config.baseURL} Device recovery`, "如实声明当前 Agent Type;恢复后必须用同一类型重新登记。", [{ command: "itpay install --json", reason: "选择当前真实 Agent Type" }]);
|
|
237
269
|
}
|
|
238
270
|
if (!options.confirmBackendReset) {
|
|
239
271
|
throw new CommandContractError("backend_reset_confirmation_required", "--confirm-backend-reset is required", "仅在运营已确认当前 Backend 的 Device 登记数据库被重建或清空后执行;普通 session 失效或 revoked 不得使用。", [{ command: "itpay docs show identity-and-sessions --json", reason: "检查适用边界" }]);
|
|
@@ -292,8 +324,8 @@ skillCmd
|
|
|
292
324
|
reportCLIError(error, {
|
|
293
325
|
jsonOutput: Boolean(options.json),
|
|
294
326
|
code: "skill_unavailable",
|
|
295
|
-
instruction: "内置 Skill
|
|
296
|
-
recovery: [
|
|
327
|
+
instruction: "内置 Skill 缺失或损坏;通过当前分发方式恢复同版本 CLI 后重试。",
|
|
328
|
+
recovery: [distributionUpgradeAction(CLI_VERSION)],
|
|
297
329
|
});
|
|
298
330
|
}
|
|
299
331
|
});
|
|
@@ -450,7 +482,7 @@ cart
|
|
|
450
482
|
runCartAdd(session, { ...addOptions, jsonOutput });
|
|
451
483
|
}
|
|
452
484
|
else {
|
|
453
|
-
const host = withHost(options.host
|
|
485
|
+
const host = withHost(options.host, config.agentType, options.target);
|
|
454
486
|
const contextError = validateContext(host, options.target);
|
|
455
487
|
if (contextError) {
|
|
456
488
|
throw new CommandContractError(contextError.code, contextError.message, "补齐当前客户端所需的 Host/target;本次未创建或修改 Cart。", [{ command: "itpay cart add --help", reason: "查看客户端参数" }]);
|
|
@@ -686,7 +718,7 @@ program
|
|
|
686
718
|
}
|
|
687
719
|
const quantity = positiveInteger(options.quantity, "--quantity");
|
|
688
720
|
const timeout = positiveInteger(options.timeout, "--timeout");
|
|
689
|
-
const host = withHost(options.host
|
|
721
|
+
const host = withHost(options.host, config.agentType, options.target);
|
|
690
722
|
const contact = {};
|
|
691
723
|
if (options.contactEmail)
|
|
692
724
|
contact.email = options.contactEmail;
|
|
@@ -750,11 +782,11 @@ program
|
|
|
750
782
|
.option("--json", "output compact JSON")
|
|
751
783
|
.action(async (options) => {
|
|
752
784
|
const config = loadConfig();
|
|
753
|
-
const host = withHost(options.host ?? defaultHostForAgentType(config.agentType));
|
|
754
785
|
const session = CartSession.loadFromFile(cartSessionPath(), config.checkoutCurrency);
|
|
755
786
|
const snap = session.show();
|
|
756
787
|
const backend = newBackendClient(config);
|
|
757
788
|
try {
|
|
789
|
+
const host = withHost(options.host, config.agentType, options.target);
|
|
758
790
|
const { checkoutID, displayToken } = resolveCheckoutPresentationArgs({
|
|
759
791
|
...(options.id ? { requestedCheckoutID: options.id } : {}),
|
|
760
792
|
...(options.token ? { requestedDisplayToken: options.token } : {}),
|
|
@@ -765,6 +797,7 @@ program
|
|
|
765
797
|
checkoutID,
|
|
766
798
|
displayToken,
|
|
767
799
|
host,
|
|
800
|
+
...(options.target ? { target: options.target } : {}),
|
|
768
801
|
...(config.agentType ? { agentType: config.agentType } : {}),
|
|
769
802
|
baseURL: config.baseURL,
|
|
770
803
|
jsonOutput: Boolean(options.json),
|
|
@@ -811,7 +844,7 @@ program
|
|
|
811
844
|
if (!displayToken) {
|
|
812
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" }]);
|
|
813
846
|
}
|
|
814
|
-
const host = withHost(options.host
|
|
847
|
+
const host = withHost(options.host, config.agentType, options.target);
|
|
815
848
|
const contextError = validateContext(host, options.target);
|
|
816
849
|
if (contextError) {
|
|
817
850
|
throw new CommandContractError(contextError.code, contextError.message, "为当前 Host 提供有效 target;本次未创建 Payment Intent。", [
|
|
@@ -1018,7 +1051,7 @@ services
|
|
|
1018
1051
|
const backend = newBackendClient(config);
|
|
1019
1052
|
try {
|
|
1020
1053
|
await runServicesStart(backend, serviceID, {
|
|
1021
|
-
host: withHost(options.host
|
|
1054
|
+
host: withHost(options.host, config.agentType, options.target),
|
|
1022
1055
|
...(options.target ? { target: options.target } : {}),
|
|
1023
1056
|
jsonOutput: Boolean(options.json),
|
|
1024
1057
|
});
|
|
@@ -1144,7 +1177,7 @@ services
|
|
|
1144
1177
|
...(options.email ? { email: options.email } : {}),
|
|
1145
1178
|
lockedInput: parseKeyValueList(options.input),
|
|
1146
1179
|
resume: Boolean(options.resume),
|
|
1147
|
-
host: withHost(options.host
|
|
1180
|
+
host: withHost(options.host, config.agentType, options.target),
|
|
1148
1181
|
...(config.agentType ? { agentType: config.agentType } : {}),
|
|
1149
1182
|
...(options.target ? { target: options.target } : {}),
|
|
1150
1183
|
...(options.qrFormat ? { qrFormat: options.qrFormat } : {}),
|