@itpay/cli 2.0.23 → 2.0.25
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 +3 -0
- package/dist/src/client/backend.js +3 -3
- package/dist/src/client/http.js +52 -15
- package/dist/src/client/transport.js +81 -0
- package/dist/src/main.js +20 -4
- package/dist/src/state/config.js +2 -2
- package/docs/agent/buyer/install-and-setup.json +2 -1
- package/docs/cli-reference/agent-types.md +1 -0
- package/docs/cli-reference/commands/buy.md +3 -1
- package/docs/cli-reference/commands/catalog/list.md +1 -1
- package/docs/cli-reference/commands/checkout.md +3 -1
- package/docs/cli-reference/commands/device.md +35 -1
- package/docs/cli-reference/commands/order.md +1 -1
- package/docs/cli-reference/commands/orders.md +1 -1
- package/docs/cli-reference/commands/pay.md +2 -0
- package/docs/cli-reference/commands/readyz.md +1 -1
- package/docs/cli-reference/commands/refund/get.md +1 -1
- package/docs/cli-reference/commands/refund/watch.md +1 -1
- package/docs/cli-reference/commands/services/checkout.md +3 -2
- package/docs/cli-reference/commands/services/next.md +1 -1
- package/docs/cli-reference/commands/services/quote.md +1 -1
- package/docs/cli-reference/commands/skill.md +15 -1
- package/docs/cli-reference/conventions.md +34 -0
- package/docs/cli-reference/index.md +11 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,6 +43,8 @@ Normative per-command contracts: [CLI Command Reference](docs/cli-reference/inde
|
|
|
43
43
|
| `claude-code-desktop` | `claude-code` |
|
|
44
44
|
| `claude-code-cli` | `terminal` |
|
|
45
45
|
| `workbuddy` | `plain-chat` |
|
|
46
|
+
| `kimi-code` | `terminal` |
|
|
47
|
+
| `openclaw` | 必须显式提供 |
|
|
46
48
|
|
|
47
49
|
`--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
50
|
|
|
@@ -59,6 +61,7 @@ The local installation keeps one Ed25519 private key and a separate registration
|
|
|
59
61
|
- `refund create/list/get/watch/cancel`: Refund Owner flow.
|
|
60
62
|
- `services get/events`: redacted support diagnostics; normal flows should use `services next`.
|
|
61
63
|
- `install`, `skill show`, `docs list/show/search`: offline packaged guidance.
|
|
64
|
+
- `device recover`: operator-confirmed Backend reset recovery that preserves the local private key.
|
|
62
65
|
- `pay`: operator escape hatch only; normal buyers use the ItPay Checkout page.
|
|
63
66
|
|
|
64
67
|
Run `itpay <command> --help` or browse [the command index](docs/cli-reference/index.md) for parameters.
|
|
@@ -84,16 +84,16 @@ export class BackendClient {
|
|
|
84
84
|
return this.http.post("/v1/service-executions", input);
|
|
85
85
|
}
|
|
86
86
|
invokeServiceCapability(serviceExecutionID, capabilityID, input) {
|
|
87
|
-
return this.http.post(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/capabilities/${encodeURIComponent(capabilityID)}/invoke`, input);
|
|
87
|
+
return this.http.post(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/capabilities/${encodeURIComponent(capabilityID)}/invoke`, input, { replaySafe: Boolean(input.idempotency_key) });
|
|
88
88
|
}
|
|
89
89
|
recordServiceExecutionAction(serviceExecutionID, input) {
|
|
90
90
|
return this.http.post(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/actions`, input);
|
|
91
91
|
}
|
|
92
92
|
createServiceExecutionCheckout(serviceExecutionID, input) {
|
|
93
|
-
return this.http.post(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/checkout`, input);
|
|
93
|
+
return this.http.post(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/checkout`, input, { replaySafe: true });
|
|
94
94
|
}
|
|
95
95
|
prepareServiceQuote(serviceExecutionID, input) {
|
|
96
|
-
return this.http.post(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/quotes`, input);
|
|
96
|
+
return this.http.post(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}/quotes`, input, { replaySafe: true });
|
|
97
97
|
}
|
|
98
98
|
getServiceExecution(serviceExecutionID) {
|
|
99
99
|
return this.http.get(`/v1/service-executions/${encodeURIComponent(serviceExecutionID)}`);
|
package/dist/src/client/http.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
// Thin HTTP client for V3 backend. Keep
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Thin HTTP client for V3 backend. Keep retry policy transport-only: bounded
|
|
2
|
+
// retries are allowed only for reads or explicitly replay-safe writes.
|
|
3
|
+
// HTTP/business failures remain owned by higher-level commands.
|
|
4
|
+
import { asTransientTransportError, HttpTransportError } from "./transport.js";
|
|
4
5
|
export class HttpError extends Error {
|
|
5
6
|
status;
|
|
6
7
|
code;
|
|
@@ -13,11 +14,13 @@ export class HttpError extends Error {
|
|
|
13
14
|
}
|
|
14
15
|
}
|
|
15
16
|
export class HttpClient {
|
|
17
|
+
static MAX_TRANSPORT_RETRIES = 2;
|
|
16
18
|
baseURL;
|
|
17
19
|
fetchImpl;
|
|
18
20
|
defaultHeaders;
|
|
19
21
|
requestAuthorizer;
|
|
20
22
|
recoverAuthorization;
|
|
23
|
+
transportRetryDelayMs;
|
|
21
24
|
constructor(config) {
|
|
22
25
|
this.baseURL = config.baseURL.replace(/\/$/, "");
|
|
23
26
|
this.fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
@@ -28,39 +31,70 @@ export class HttpClient {
|
|
|
28
31
|
};
|
|
29
32
|
this.requestAuthorizer = config.requestAuthorizer;
|
|
30
33
|
this.recoverAuthorization = config.recoverAuthorization;
|
|
34
|
+
this.transportRetryDelayMs = Math.max(0, config.transportRetryDelayMs ?? 200);
|
|
31
35
|
}
|
|
32
36
|
async request(path, options = {}) {
|
|
33
37
|
const url = path.startsWith("http") ? path : this.baseURL + path;
|
|
34
38
|
const method = options.method ?? "GET";
|
|
35
39
|
const body = options.body !== undefined ? JSON.stringify(options.body) : "";
|
|
36
40
|
const requestPath = new URL(url).pathname + new URL(url).search;
|
|
37
|
-
|
|
41
|
+
const replaySafe = method === "GET" || Boolean(options.idempotencyKey) || options.replaySafe === true;
|
|
42
|
+
let authorizationRecovered = false;
|
|
43
|
+
let transportRetries = 0;
|
|
44
|
+
for (;;) {
|
|
38
45
|
const headers = { ...this.defaultHeaders };
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
try {
|
|
47
|
+
if (this.requestAuthorizer) {
|
|
48
|
+
Object.assign(headers, await this.requestAuthorizer({ method, path: requestPath, body }));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
// Authorization can perform enrollment or session POSTs before the
|
|
53
|
+
// protected request exists. Classify their transport failure, but do
|
|
54
|
+
// not inherit the outer request's replay policy for those writes.
|
|
55
|
+
if (error instanceof HttpTransportError)
|
|
56
|
+
throw error;
|
|
57
|
+
throw asTransientTransportError(error, 1) ?? error;
|
|
41
58
|
}
|
|
42
59
|
if (options.bearer)
|
|
43
60
|
headers.Authorization = `Bearer ${options.bearer}`;
|
|
44
61
|
if (options.idempotencyKey)
|
|
45
62
|
headers["Idempotency-Key"] = options.idempotencyKey;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
let response;
|
|
64
|
+
let text;
|
|
65
|
+
try {
|
|
66
|
+
response = await this.fetchImpl(url, {
|
|
67
|
+
method,
|
|
68
|
+
headers,
|
|
69
|
+
...(options.body !== undefined ? { body } : {}),
|
|
70
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
71
|
+
});
|
|
72
|
+
text = await response.text();
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (options.signal?.aborted)
|
|
76
|
+
throw error;
|
|
77
|
+
const transportError = asTransientTransportError(error, transportRetries + 1);
|
|
78
|
+
if (!transportError)
|
|
79
|
+
throw error;
|
|
80
|
+
if (replaySafe && transportRetries < HttpClient.MAX_TRANSPORT_RETRIES) {
|
|
81
|
+
transportRetries += 1;
|
|
82
|
+
await delay(this.transportRetryDelayMs * (2 ** (transportRetries - 1)));
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
throw asTransientTransportError(error, transportRetries + 1) ?? error;
|
|
86
|
+
}
|
|
53
87
|
const parsed = text.length > 0 ? safeParseJson(text) : undefined;
|
|
54
88
|
if (response.ok)
|
|
55
89
|
return parsed;
|
|
56
90
|
const error = new HttpError(response.status, parsed, `HTTP ${response.status}`);
|
|
57
|
-
if (
|
|
91
|
+
if (!authorizationRecovered && error.status === 401 && error.code === "agent_device_session_required" && this.recoverAuthorization) {
|
|
92
|
+
authorizationRecovered = true;
|
|
58
93
|
await this.recoverAuthorization();
|
|
59
94
|
continue;
|
|
60
95
|
}
|
|
61
96
|
throw error;
|
|
62
97
|
}
|
|
63
|
-
throw new Error("unreachable HTTP retry state");
|
|
64
98
|
}
|
|
65
99
|
get(path, options = {}) {
|
|
66
100
|
return this.request(path, { ...options, method: "GET" });
|
|
@@ -72,6 +106,9 @@ export class HttpClient {
|
|
|
72
106
|
return this.request(path, { ...options, method: "DELETE" });
|
|
73
107
|
}
|
|
74
108
|
}
|
|
109
|
+
function delay(milliseconds) {
|
|
110
|
+
return milliseconds === 0 ? Promise.resolve() : new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
111
|
+
}
|
|
75
112
|
function safeParseJson(text) {
|
|
76
113
|
try {
|
|
77
114
|
return JSON.parse(text);
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export class HttpTransportError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
attempts;
|
|
4
|
+
causeCode;
|
|
5
|
+
retryable = true;
|
|
6
|
+
constructor(code, attempts, causeCode, cause) {
|
|
7
|
+
const suffix = attempts > 1 ? ` after ${attempts} attempts` : "";
|
|
8
|
+
super(`${transportMessage(code)} before a complete HTTP response was received${suffix}`, { cause });
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.attempts = attempts;
|
|
11
|
+
this.causeCode = causeCode;
|
|
12
|
+
this.name = "HttpTransportError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function asTransientTransportError(error, attempts) {
|
|
16
|
+
if (isAbort(error))
|
|
17
|
+
return undefined;
|
|
18
|
+
const causeCode = findCauseCode(error);
|
|
19
|
+
const code = causeCode ? classifyCauseCode(causeCode) : classifyFetchFailure(error);
|
|
20
|
+
return code ? new HttpTransportError(code, attempts, causeCode, error) : undefined;
|
|
21
|
+
}
|
|
22
|
+
function classifyCauseCode(code) {
|
|
23
|
+
switch (code) {
|
|
24
|
+
case "ECONNRESET":
|
|
25
|
+
return "network_connection_reset";
|
|
26
|
+
case "ETIMEDOUT":
|
|
27
|
+
case "UND_ERR_CONNECT_TIMEOUT":
|
|
28
|
+
case "UND_ERR_HEADERS_TIMEOUT":
|
|
29
|
+
case "UND_ERR_BODY_TIMEOUT":
|
|
30
|
+
return "network_timeout";
|
|
31
|
+
case "EAI_AGAIN":
|
|
32
|
+
return "network_dns_temporary";
|
|
33
|
+
case "ENETUNREACH":
|
|
34
|
+
case "EHOSTUNREACH":
|
|
35
|
+
return "network_unreachable";
|
|
36
|
+
case "ECONNREFUSED":
|
|
37
|
+
return "network_connection_refused";
|
|
38
|
+
case "UND_ERR_SOCKET":
|
|
39
|
+
return "network_socket_error";
|
|
40
|
+
default:
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function classifyFetchFailure(error) {
|
|
45
|
+
return error instanceof TypeError && error.message === "fetch failed"
|
|
46
|
+
? "network_transport_failed"
|
|
47
|
+
: undefined;
|
|
48
|
+
}
|
|
49
|
+
function findCauseCode(error) {
|
|
50
|
+
const seen = new Set();
|
|
51
|
+
let current = error;
|
|
52
|
+
while (current && typeof current === "object" && !seen.has(current)) {
|
|
53
|
+
seen.add(current);
|
|
54
|
+
const code = current.code;
|
|
55
|
+
if (typeof code === "string" && code !== "")
|
|
56
|
+
return code;
|
|
57
|
+
current = current.cause;
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
function isAbort(error) {
|
|
62
|
+
return error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
|
|
63
|
+
}
|
|
64
|
+
function transportMessage(code) {
|
|
65
|
+
switch (code) {
|
|
66
|
+
case "network_connection_reset":
|
|
67
|
+
return "network connection was reset";
|
|
68
|
+
case "network_timeout":
|
|
69
|
+
return "network connection timed out";
|
|
70
|
+
case "network_dns_temporary":
|
|
71
|
+
return "DNS lookup was temporarily unavailable";
|
|
72
|
+
case "network_unreachable":
|
|
73
|
+
return "network was unreachable";
|
|
74
|
+
case "network_connection_refused":
|
|
75
|
+
return "network connection was refused";
|
|
76
|
+
case "network_socket_error":
|
|
77
|
+
return "network socket failed";
|
|
78
|
+
case "network_transport_failed":
|
|
79
|
+
return "network transport failed";
|
|
80
|
+
}
|
|
81
|
+
}
|
package/dist/src/main.js
CHANGED
|
@@ -7,6 +7,7 @@ import { DeviceAuthority, DeviceAuthorizationError, DeviceStateError } from "./s
|
|
|
7
7
|
import { CartSession } from "./state/cart_session.js";
|
|
8
8
|
import { defaultHostForAgentType, normalizeHost, validateContext } from "./state/client_context.js";
|
|
9
9
|
import { HttpError } from "./client/http.js";
|
|
10
|
+
import { HttpTransportError } from "./client/transport.js";
|
|
10
11
|
import { runReadyz } from "./commands/readyz.js";
|
|
11
12
|
import { runBuy } from "./commands/buy.js";
|
|
12
13
|
import { runCatalogList } from "./commands/catalog.js";
|
|
@@ -93,6 +94,7 @@ function reportCLIError(error, contract) {
|
|
|
93
94
|
const backendOverrideError = error instanceof BackendOverrideError ? error : undefined;
|
|
94
95
|
const deviceError = error instanceof DeviceAuthorizationError ? error : undefined;
|
|
95
96
|
const stateError = error instanceof DeviceStateError ? error : undefined;
|
|
97
|
+
const transportError = error instanceof HttpTransportError ? error : undefined;
|
|
96
98
|
const httpRecovery = errorRecoveryActions(error).map((action) => ({
|
|
97
99
|
command: action.command,
|
|
98
100
|
reason: action.reason ?? action.label,
|
|
@@ -134,7 +136,7 @@ function reportCLIError(error, contract) {
|
|
|
134
136
|
writeCommandEnvelope({
|
|
135
137
|
status: "error",
|
|
136
138
|
error: {
|
|
137
|
-
code: incompatible ? "backend_contract_incompatible" : backendOverrideError?.code ?? commandError?.code ?? (error instanceof HttpError ? error.code : stateError?.code ?? deviceError?.code ?? contract?.code ?? "command_failed"),
|
|
139
|
+
code: incompatible ? "backend_contract_incompatible" : backendOverrideError?.code ?? commandError?.code ?? (error instanceof HttpError ? error.code : transportError?.code ?? stateError?.code ?? deviceError?.code ?? contract?.code ?? "command_failed"),
|
|
138
140
|
message: error instanceof Error ? error.message : String(error),
|
|
139
141
|
},
|
|
140
142
|
...(requiredCLIVersion ? {
|
|
@@ -142,6 +144,11 @@ function reportCLIError(error, contract) {
|
|
|
142
144
|
current_cli_version: CLI_VERSION,
|
|
143
145
|
required_cli_version: requiredCLIVersion,
|
|
144
146
|
},
|
|
147
|
+
} : transportError ? {
|
|
148
|
+
result: {
|
|
149
|
+
attempts: transportError.attempts,
|
|
150
|
+
automatic_retry_performed: transportError.attempts > 1,
|
|
151
|
+
},
|
|
145
152
|
} : error instanceof HttpError && error.payload?.service_execution_id ? {
|
|
146
153
|
result: {
|
|
147
154
|
service_execution_id: error.payload.service_execution_id,
|
|
@@ -172,9 +179,13 @@ function reportCLIError(error, contract) {
|
|
|
172
179
|
? "Provider 拒绝了本次请求,但未声明这是输入错误;向用户逐字报告 error.message 和 result.quota 并停止。不要修改输入、不要重试、不要创建新 Execution。"
|
|
173
180
|
: capabilityInputInvalid
|
|
174
181
|
? "输入未通过本地校验,上游尚未被调用且用户额度未变化。向用户逐字报告 error.message 并停止,不要原样重试或运行其他恢复命令。用户提供修正后的输入后,继续使用当前未结束的 Execution。"
|
|
175
|
-
:
|
|
176
|
-
?
|
|
177
|
-
|
|
182
|
+
: transportError
|
|
183
|
+
? transportError.attempts > 1
|
|
184
|
+
? "临时网络故障;CLI 已仅对可安全重放的操作完成有限自动重试,但仍未获得完整响应。按 recovery 查询同一资源的权威状态;不要创建替代 Checkout、Execution、Payment 或 Refund。"
|
|
185
|
+
: "网络在完整响应前中断;当前写操作没有安全重放合同,因此 CLI 未自动重试。按 recovery 查询权威状态;不要原样重放或创建替代 Checkout、Execution、Payment 或 Refund。"
|
|
186
|
+
: backendOverrideError
|
|
187
|
+
? "移除 ITPAY_BACKEND_URL 使用正式环境,或准确设置为 https://dev.itpay.ai。"
|
|
188
|
+
: commandError?.instruction ?? authorizationInstruction ?? contract?.instruction ?? "检查命令参数后重试。",
|
|
178
189
|
next: null,
|
|
179
190
|
recovery: incompatible
|
|
180
191
|
? requiredCLIVersion
|
|
@@ -196,6 +207,11 @@ function reportCLIError(error, contract) {
|
|
|
196
207
|
process.exitCode = 1;
|
|
197
208
|
return;
|
|
198
209
|
}
|
|
210
|
+
if (error instanceof HttpTransportError) {
|
|
211
|
+
process.stderr.write(`[${error.code}] ${error.message}\n`);
|
|
212
|
+
process.exitCode = 1;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
199
215
|
if (error instanceof Error) {
|
|
200
216
|
process.stderr.write(`${error.message}\n`);
|
|
201
217
|
process.exitCode = 1;
|
package/dist/src/state/config.js
CHANGED
|
@@ -12,8 +12,8 @@ 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.
|
|
16
|
-
export const API_CONTRACT_REVISION = "sha256:
|
|
15
|
+
export const CLI_VERSION = "2.0.25";
|
|
16
|
+
export const API_CONTRACT_REVISION = "sha256:d4e94049c01b38bc77dce76bf4e49e8243adfc588541e33af6477cd5722a5bc4";
|
|
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";
|
|
@@ -37,6 +37,7 @@
|
|
|
37
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
39
|
"Use one exact type: codex-desktop, codex-cli, claude-code-desktop, claude-code-cli, workbuddy, kimi-code, or openclaw.",
|
|
40
|
+
"For compatibility, a global --agent-type codex declaration is normalized immediately to codex-desktop; all registration, output, and returned commands use codex-desktop. Never generate the alias, and do not use codex as an install target.",
|
|
40
41
|
"One local private key is reused, while Device registrations and quota lineage remain separate for app.itpay.ai and dev.itpay.ai.",
|
|
41
42
|
"Within each official Backend registration, each Agent Type has one Agent Instance; all windows and chats of the same type reuse it.",
|
|
42
43
|
"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.",
|
|
@@ -44,7 +45,7 @@
|
|
|
44
45
|
"Do not change Agent Type or rotate local identity to reset quota or recover a failed command."
|
|
45
46
|
],
|
|
46
47
|
"forbidden": [
|
|
47
|
-
"Do not use
|
|
48
|
+
"Do not use terminal, claude-code, or plain-chat as Agent Types. Do not generate codex as an Agent Type; it is accepted only as a legacy global declaration and becomes codex-desktop before any request.",
|
|
48
49
|
"Do not set or suggest any Backend except the default https://app.itpay.ai or the exact test override https://dev.itpay.ai.",
|
|
49
50
|
"Do not claim that install writes host configuration or registers a device; it only prints instructions.",
|
|
50
51
|
"Do not create a new identity for a different window, task, chat, or process of the same Agent Type."
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
## 通用规则
|
|
22
22
|
|
|
23
23
|
- commerce 命令必须传 `--agent-type` 或设置 `ITPAY_AGENT_TYPE`。
|
|
24
|
+
- 兼容入口 `--agent-type codex` / `ITPAY_AGENT_TYPE=codex` 仅在 CLI 参数解析边界规范化为 `codex-desktop`;登记、输出和后续命令始终使用 `codex-desktop`。新文档和 Agent 不得主动生成该别名。`itpay install codex` 仍是无效 target。
|
|
24
25
|
- Agent Type 必须真实且稳定;同类型窗口复用同一实例,不得临时换名。
|
|
25
26
|
- `next.command` 和 `recovery.command` 必须保留当前显式 Agent Type,不读取或回退到机器上其他类型。
|
|
26
27
|
- 显式 `--host` 覆盖默认 Host,但不改变已登记的 Agent Type。
|
|
@@ -40,6 +40,7 @@ itpay buy \
|
|
|
40
40
|
--require-contact <email,phone>
|
|
41
41
|
--host <host>
|
|
42
42
|
--target <target>
|
|
43
|
+
--locale <zh-CN|en>
|
|
43
44
|
--qr-format <unicode|utf8|ansi|terminal>
|
|
44
45
|
--qr-file <path>
|
|
45
46
|
--pay
|
|
@@ -64,6 +65,7 @@ itpay buy \
|
|
|
64
65
|
| `--require-contact` | 否 | 只接受 `email`、`phone`;缺失时先询问用户,禁止 Agent 编造。 |
|
|
65
66
|
| `--host` | 条件必填 | 通常由 `--agent-type` 推导;`openclaw` 必须显式传当前入口。只改变 handoff 展示,不改变交易事实。 |
|
|
66
67
|
| `--target` | 条件必填 | 要求目标会话的 IM Host 必须提供;OpenClaw 从当前可信会话上下文传入。 |
|
|
68
|
+
| `--locale` | 否 | Payment Card 语言,默认 `zh-CN`;可使用 `en`。只改变 Card 文案,不改变交易事实。 |
|
|
67
69
|
| `--qr-format` | 否 | 非 JSON 的终端渲染选项。 |
|
|
68
70
|
| `--qr-file` | 否 | 非 JSON handoff 的明确二维码文件路径。 |
|
|
69
71
|
| `--pay` | 否 | 创建 Payment Intent 的集成/运维入口;普通 Agent 流程只展示 Checkout。 |
|
|
@@ -153,7 +155,7 @@ itpay buy \
|
|
|
153
155
|
| `buy_parameter_invalid` | quantity/timeout 非正整数,或 `--no-wait` 未配 `--pay`。 | 修正参数;本次不创建资源。 |
|
|
154
156
|
| `service_quote_required` | Cart 含尚未绑定 Quote 的 Service Execution。 | 原样执行返回的 `services next <id> --json`;不要绕过 Quote 输入校验。 |
|
|
155
157
|
| `idempotency_conflict` | 同一幂等操作被用于不同请求。 | 保留句柄并执行 `itpay next --json`,不要换键重建。 |
|
|
156
|
-
| `buy_failed` |
|
|
158
|
+
| `buy_failed` | 非传输层的未知命令错误。 | 先执行 `itpay next --json` 或 `cart next --json` 恢复现有资源。传输失败使用 conventions 中的稳定 `network_*` 错误码。 |
|
|
157
159
|
|
|
158
160
|
所有参数错误都必须在 HTTP 和本地 Cart 变更前被拒绝。服务 Cart 只有每条 service-backed line 都绑定有效 Quote 时才能创建 Checkout;付款后 Backend 按 Order Item 分别推进 Execution。
|
|
159
161
|
|
|
@@ -44,4 +44,4 @@ itpay catalog list [--json]
|
|
|
44
44
|
|
|
45
45
|
## Agent Type / Host
|
|
46
46
|
|
|
47
|
-
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy` 使用同一产品内容;只允许排版不同。
|
|
47
|
+
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 使用同一产品内容;只允许排版不同。
|
|
@@ -13,13 +13,15 @@
|
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
itpay checkout [--id <checkout_id>] [--token <display_token>]
|
|
16
|
-
[--host <host>] [--target <target>] [--json]
|
|
16
|
+
[--host <host>] [--target <target>] [--locale <zh-CN|en>] [--json]
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
省略 `--id/--token` 时只能使用本机保存的一组完整句柄;不得把其他 Checkout 的 token 拼接使用。
|
|
20
20
|
|
|
21
21
|
`--host` 默认由 `--agent-type` 决定;`openclaw` 必须显式传当前入口。IM Host 必须提供 `--target`。`--json` 输出机器可读合同,不内嵌二维码字符画或图片二进制。
|
|
22
22
|
|
|
23
|
+
`--locale` 默认 `zh-CN`,可显式使用 `en`。它只改变重新渲染的 Card 文案,不改变 Checkout、付款、授权或恢复状态。
|
|
24
|
+
|
|
23
25
|
## 等待付款输出
|
|
24
26
|
|
|
25
27
|
```json
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
# `itpay device recover`
|
|
1
|
+
# `itpay device` / `itpay device recover`
|
|
2
2
|
|
|
3
3
|
> **Product boundary:** `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.
|
|
4
4
|
|
|
5
5
|
## 范围
|
|
6
6
|
|
|
7
|
+
`itpay device` 只显示该命令组的帮助并退出,不访问 Backend、不读取或修改身份。当前唯一子命令是 `recover`。
|
|
8
|
+
|
|
7
9
|
仅在运营明确确认当前 Backend 的 Device 登记数据库已重建或清空后,删除本地该 Backend 的 v2 registration:
|
|
8
10
|
|
|
9
11
|
```bash
|
|
@@ -13,3 +15,35 @@ itpay --agent-type <agent_type> device recover --confirm-backend-reset --json
|
|
|
13
15
|
命令只作用于当前官方 Backend 的 Device registration,并保留本地 Ed25519 私钥、Cart 和业务资源。默认是 `https://app.itpay.ai`;显式测试可使用准确的 `ITPAY_BACKEND_URL=https://dev.itpay.ai`。该命令不访问 Backend、不自动创建新身份;返回的只读 `services list` 会保留同一 Backend,是重新登记入口。
|
|
14
16
|
|
|
15
17
|
缺少确认参数返回 `backend_reset_confirmation_required`。普通 session 失效由 CLI 自动续期;revoked、quota、权限或未知 Backend 故障不得使用本命令。所有 Agent Type 使用相同输入和输出合同。
|
|
18
|
+
|
|
19
|
+
## 参数
|
|
20
|
+
|
|
21
|
+
| 参数 | 必填 | 说明 |
|
|
22
|
+
|---|---:|---|
|
|
23
|
+
| 全局 `--agent-type <agent_type>` | 是 | 当前真实且稳定的 Agent Type;必须位于 `device` 前。 |
|
|
24
|
+
| `--confirm-backend-reset` | 是 | 确认运营已明确判定所选 Backend 的登记数据库被重建或清空。 |
|
|
25
|
+
| `--json` | 否 | 输出稳定 JSON 信封;否则输出同一事实的简洁文本。 |
|
|
26
|
+
|
|
27
|
+
## 标准输出
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"status": "backend_registration_removed",
|
|
32
|
+
"result": {
|
|
33
|
+
"backend": "https://app.itpay.ai",
|
|
34
|
+
"removed_agent_types": ["codex-desktop"],
|
|
35
|
+
"private_key_preserved": true,
|
|
36
|
+
"other_backend_registrations_preserved": true
|
|
37
|
+
},
|
|
38
|
+
"instruction": "只读列出 Service Executions,以同一私钥和 Agent Type 重新登记当前 Backend;不要删除 ~/.itpay-v3 或切换运行时。",
|
|
39
|
+
"next": {
|
|
40
|
+
"command": "itpay --agent-type codex-desktop services list --limit 1 --json",
|
|
41
|
+
"reason": "用无业务写入的签名请求重新登记当前 Backend"
|
|
42
|
+
},
|
|
43
|
+
"recovery": []
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
当前 Backend 已无本地登记时,`status` 为 `backend_registration_absent`、`removed_agent_types=[]`,其余合同不变。文本输出只包含 Backend、registration 状态、私钥保留和其他 Backend 保留四项,不输出私钥、公钥、Device ID、Agent Instance ID、session、token 或本地文件内容。
|
|
48
|
+
|
|
49
|
+
缺少 Agent Type 返回 `agent_type_required`;缺少确认返回 `backend_reset_confirmation_required`。其他本地恢复失败返回 `device_recovery_failed`。所有失败都必须发生在删除 registration 前;本命令不得删除整把 Device identity、Cart、operation journal 或其他 Backend 登记。
|
|
@@ -91,4 +91,4 @@ itpay services list --json
|
|
|
91
91
|
|
|
92
92
|
## Agent Type / Host
|
|
93
93
|
|
|
94
|
-
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy` 返回完全相同的订单事实、instruction 和 next。`order` 是状态读取命令,不构造二维码、Markdown handoff 或 Host renderer 数据。
|
|
94
|
+
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 返回完全相同的订单事实、instruction 和 next。`order` 是状态读取命令,不构造二维码、Markdown handoff 或 Host renderer 数据。
|
|
@@ -82,4 +82,4 @@ itpay orders [--limit <n>] [--status <status>] [--json]
|
|
|
82
82
|
|
|
83
83
|
## Agent Type / Host
|
|
84
84
|
|
|
85
|
-
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy` 行为相同;只允许展示格式差异。
|
|
85
|
+
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 行为相同;只允许展示格式差异。
|
|
@@ -101,5 +101,7 @@ API 安全合同要求后端验证 display token 是该 Checkout 当前有效的
|
|
|
101
101
|
| `claude-code-desktop` | `claude-code` | 把安全 handoff 发到当前桌面对话。 |
|
|
102
102
|
| `claude-code-cli` | `terminal` | 只在用户可见终端展示渠道动作。 |
|
|
103
103
|
| `workbuddy` | `plain-chat` | 受控逃生入口只返回一个可点击的 `handoff.url`;不得调用 `present_files`。展示后停止,不立即查询或创建替代付款。 |
|
|
104
|
+
| `kimi-code` | `terminal` | 使用标准 CLI 终端展示渠道动作。 |
|
|
105
|
+
| `openclaw` | 必须显式提供 | 按显式 Host 返回安全渠道动作;IM Host 仍要求真实 target。 |
|
|
104
106
|
|
|
105
107
|
Host 只改变 instruction;Payment Intent ID、金额、状态、重试语义和权限必须一致。
|
|
@@ -45,7 +45,7 @@ itpay readyz [--json]
|
|
|
45
45
|
|
|
46
46
|
## 异常处理
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
已收到 Backend 不可用响应等非传输异常时返回 `backend_unavailable`。尚未收到完整 HTTP 响应的临时传输失败使用 conventions 中对应的稳定 `network_*` 错误码;`readyz` 是 GET,按全局安全传输合同最多自动重试两次。两类错误都要求等待当前官方 Backend 恢复后重试同一完整命令,不得在失败时切换环境或继续下单。
|
|
49
49
|
|
|
50
50
|
非官方 URL 返回 `backend_override_forbidden`,且不提供自动 recovery:
|
|
51
51
|
|
|
@@ -64,4 +64,4 @@ CLI 不因为退款终态自行修改订单或 grant。
|
|
|
64
64
|
|
|
65
65
|
## Agent Type / Host
|
|
66
66
|
|
|
67
|
-
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy` 返回完全相同的退款事实、instruction 和 next。Host 不改变 Refund Owner 状态或访问锁。
|
|
67
|
+
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 返回完全相同的退款事实、instruction 和 next。Host 不改变 Refund Owner 状态或访问锁。
|
|
@@ -72,4 +72,4 @@ Timeout 只表示本次 CLI 等待结束,不表示退款失败:
|
|
|
72
72
|
|
|
73
73
|
## Agent Type / Host
|
|
74
74
|
|
|
75
|
-
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy` 返回相同退款事实。Desktop 不会把每次无变化轮询发送到用户对话;Host 不改变 timeout 或退款状态。
|
|
75
|
+
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 返回相同退款事实。Desktop 不会把每次无变化轮询发送到用户对话;Host 不改变 timeout 或退款状态。
|
|
@@ -14,10 +14,11 @@
|
|
|
14
14
|
```bash
|
|
15
15
|
itpay services checkout <service_execution_id> --capability <capability_id>
|
|
16
16
|
[--input <key=value> ...] [--email <delivery_email>]
|
|
17
|
-
[--host <host>] [--target <target>] [--
|
|
17
|
+
[--host <host>] [--target <target>] [--locale <zh-CN|en>]
|
|
18
|
+
[--qr-format <format>] [--qr-file <path>] [--json]
|
|
18
19
|
|
|
19
20
|
itpay services checkout <service_execution_id> --resume
|
|
20
|
-
[--host <host>] [--target <target>] [--json]
|
|
21
|
+
[--host <host>] [--target <target>] [--locale <zh-CN|en>] [--json]
|
|
21
22
|
```
|
|
22
23
|
|
|
23
24
|
创建时 `--capability` 必填。最终 locked input 必须满足 capability schema:显式输入来自 `--input`,服务端也可以按已发布 contract 从当前 Execution 的已批准 action 解析输入。解析后仍缺字段时,必须在创建 Quote、Cart 或 Checkout 前失败。只有 `delivery_email_required=true` 才要求 `--email`,并必须先向用户解释邮箱用于发送可 claim 的交付链接。`--resume` 复用同一个 Checkout 并轮换 handoff token,不再索取输入或邮箱。
|
|
@@ -207,4 +207,4 @@ itpay services get <service_execution_id> --json
|
|
|
207
207
|
|
|
208
208
|
## Agent Type / Host
|
|
209
209
|
|
|
210
|
-
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy` 返回完全相同的状态、safe payload、instruction 和 next。本命令不渲染二维码,也不包含 Host handoff。
|
|
210
|
+
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 返回完全相同的状态、safe payload、instruction 和 next。本命令不渲染二维码,也不包含 Host handoff。
|
|
@@ -62,4 +62,4 @@ itpay services quote <service_execution_id> --capability <capability_id>
|
|
|
62
62
|
|
|
63
63
|
## Agent Type / Host
|
|
64
64
|
|
|
65
|
-
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy` 返回相同 Quote 事实、instruction 和 next。本命令不显示二维码;Agent Type 只作为设备与审计上下文,不改变价格或候选规则。
|
|
65
|
+
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 返回相同 Quote 事实、instruction 和 next。本命令不显示二维码;Agent Type 只作为设备与审计上下文,不改变价格或候选规则。
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# `itpay skill show`
|
|
1
|
+
# `itpay skill` / `itpay skill show`
|
|
2
2
|
|
|
3
3
|
> **Product boundary:** `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.
|
|
4
4
|
|
|
@@ -6,12 +6,26 @@
|
|
|
6
6
|
|
|
7
7
|
读取 npm 包内置的完整 ItPay Agent Skill。与按 topic 渐进读取的 `docs` 不同,本命令故意一次返回完整 `SKILL.md`,用于首次 onboarding 和身份/session 规则恢复;不访问 Backend,不修改宿主配置或本地身份。
|
|
8
8
|
|
|
9
|
+
`itpay skill` 只显示该命令组的帮助并退出,不读取 Skill 内容。
|
|
10
|
+
|
|
9
11
|
```bash
|
|
10
12
|
itpay [--agent-type <agent_type>] skill show itpay [--json]
|
|
11
13
|
```
|
|
12
14
|
|
|
13
15
|
当前只内置 `itpay`。该 Skill 是 Buyer 与未来 Seller 的共同入口,不再按角色拆分名称。`--json` 时完整 Markdown 位于 `result.content`;文本模式直接输出完整内容。
|
|
14
16
|
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"status": "shown",
|
|
20
|
+
"result": { "skill": "itpay", "content": "<complete_packaged_SKILL.md>" },
|
|
21
|
+
"instruction": "完整读取并遵守 Skill;先如实选择当前运行环境对应的 Agent Type。",
|
|
22
|
+
"next": { "command": "itpay install --json", "reason": "选择真实且稳定的 Agent Type" },
|
|
23
|
+
"recovery": []
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
已声明 Agent Type 时 instruction 确认规范类型,`next.command` 保留该类型并指向 `catalog list --json`。除完整已发布 Skill 内容外,不得附加本地路径、安装目录、环境变量、Device 状态或 Backend 数据。
|
|
28
|
+
|
|
15
29
|
未声明 Agent Type 时,`next` 是 `itpay install --json`。已声明时,`next` 是保留同一类型的 `catalog list --json`。未知名称返回 `skill_not_found`;包内文件缺失或损坏返回 `skill_unavailable` 并要求重装同版本 CLI。
|
|
16
30
|
|
|
17
31
|
Skill 是操作和安全合同,不是服务端业务状态。执行时仍以每个命令当前 envelope 的 `result`、`instruction` 和 `next` 为准。
|
|
@@ -82,6 +82,40 @@ next: <one command>
|
|
|
82
82
|
- `provider_input_rejected` 只表示 Provider 明确声明输入无效;`provider_contract_mismatch` 表示响应无法按已发布契约解释,绝不能归咎于用户输入。两者都必须停止且没有自动 recovery。
|
|
83
83
|
- `backend_contract_incompatible` 只有在 Backend 返回合法 `minimum_cli_version` 时才能提供升级 recovery。npm 分发返回精确的 `npm install -g @itpay/cli@<version>`;平台 bundle 返回该平台的 Skill/plugin 更新动作。不得使用 `latest`、解析 message 猜版本或继续任何业务命令;升级后必须先用 `itpay --version` 核对完全一致,再重新运行 `readyz`。
|
|
84
84
|
|
|
85
|
+
## 网络传输失败与自动重试
|
|
86
|
+
|
|
87
|
+
CLI 只把“尚未收到完整 HTTP 响应”的临时传输异常映射为稳定错误码:
|
|
88
|
+
|
|
89
|
+
- `network_connection_reset`
|
|
90
|
+
- `network_timeout`
|
|
91
|
+
- `network_dns_temporary`
|
|
92
|
+
- `network_unreachable`
|
|
93
|
+
- `network_connection_refused`
|
|
94
|
+
- `network_socket_error`
|
|
95
|
+
- `network_transport_failed`
|
|
96
|
+
|
|
97
|
+
所有命令的 JSON 错误继续使用标准错误外壳,并额外返回:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"status": "error",
|
|
102
|
+
"error": { "code": "network_connection_reset", "message": "<bounded_transport_fact>" },
|
|
103
|
+
"result": { "attempts": 3, "automatic_retry_performed": true },
|
|
104
|
+
"instruction": "临时网络故障;CLI 已仅对可安全重放的操作完成有限自动重试,但仍未获得完整响应。按 recovery 查询同一资源的权威状态;不要创建替代 Checkout、Execution、Payment 或 Refund。",
|
|
105
|
+
"next": null,
|
|
106
|
+
"recovery": [{ "command": "itpay <same-resource-read-command>", "reason": "读取同一资源的权威状态" }]
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
重试合同必须同时满足以下边界:
|
|
111
|
+
|
|
112
|
+
- `GET`、携带稳定 `Idempotency-Key` 的写请求,以及 Backend 明确保证事务性安全重放的操作,使用短递增退避,最多自动重试两次(总共三次尝试)。
|
|
113
|
+
- 不具备上述合同的写请求绝不自动重放;错误中为 `attempts=1`、`automatic_retry_performed=false`,Agent 必须先查询同一资源状态。
|
|
114
|
+
- 每次尝试重新生成 Device 签名;不得复用过期时间、nonce 或 Authorization header。
|
|
115
|
+
- 首次 Device enrollment、Agent Instance 登记或 session challenge/verify 在生成请求签名前发生。它们的传输失败同样映射为稳定 `network_*` 错误,但这些内部 POST 没有外层业务请求的安全重放合同,因此不自动重试,并返回 `attempts=1`、`automatic_retry_performed=false`。Agent 修复网络后重跑原始命令,由 Device Authority 从已持久化状态安全恢复。
|
|
116
|
+
- `AbortSignal` 主动取消、证书/TLS 信任错误、已收到的 HTTP/业务错误和 Provider 业务失败不属于该重试。
|
|
117
|
+
- 稳定输出不得包含 Node/Undici cause、socket、DNS 地址、请求 header、token、签名 URL 或 Provider 原始响应。
|
|
118
|
+
|
|
85
119
|
## Instruction 模板
|
|
86
120
|
|
|
87
121
|
Instruction 只回答当前最重要的一件事:
|
|
@@ -14,6 +14,16 @@
|
|
|
14
14
|
- 所有 commerce 命令必须使用真实的 `--agent-type`,不得为刷新额度伪造类型。
|
|
15
15
|
- 每条命令只返回当前步骤所需事实、一条 instruction、一个首选 next;异常时最多返回两个 recovery。
|
|
16
16
|
|
|
17
|
+
## 根命令
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
itpay [--agent-type <agent_type>] [--version] [--help]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
不带子命令时显示帮助并退出,不访问 Backend、不读取或修改业务状态。`--version` 只输出当前 CLI 版本;`--help` 只输出命令树。全局 `--agent-type` 必须放在子命令前,并由每个返回的 ItPay `next.command` / `recovery.command` 原样保留其规范值。
|
|
24
|
+
|
|
25
|
+
Commander 自动提供的 `itpay help [command]` 与 `itpay <group> help [subcommand]` 只显示对应帮助,语义等同于在目标命令上使用 `--help`;它们不访问 Backend、不读写本地业务状态,也没有 JSON 输出合同。
|
|
26
|
+
|
|
17
27
|
## 命令目录
|
|
18
28
|
|
|
19
29
|
### 环境与发现
|
|
@@ -24,6 +34,7 @@
|
|
|
24
34
|
- [`itpay catalog list`](commands/catalog/list.md)
|
|
25
35
|
- [`itpay install`](commands/install.md) - 查看指定 Agent 的安装说明
|
|
26
36
|
- [`itpay skill show`](commands/skill.md) - 一次读取完整内置 ItPay Skill
|
|
37
|
+
- [`itpay device recover`](commands/device.md) - 仅恢复运营已确认重建的 Backend registration
|
|
27
38
|
- [`itpay docs`](commands/docs/index.md)
|
|
28
39
|
- [`itpay docs list`](commands/docs/list.md)
|
|
29
40
|
- [`itpay docs show`](commands/docs/show.md)
|