@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
|
@@ -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
|
|
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
|
|
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((
|
|
102
|
+
instructions: ideImageAttachBlock(plan.ideImageAttach).filter((line) => line.length > 0),
|
|
68
103
|
},
|
|
69
104
|
}
|
|
70
105
|
: {}),
|
|
71
106
|
};
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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) {
|
package/dist/src/state/config.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// CLI configuration loader.
|
|
2
|
-
//
|
|
1
|
+
// CLI configuration loader. Production defaults to app.itpay.ai; the only
|
|
2
|
+
// allowed override is the official dev Backend. Checkout
|
|
3
3
|
// display-token persistence belongs to the cart session file, protected with
|
|
4
4
|
// owner-only permissions. Provider secrets are explicitly out of scope here.
|
|
5
5
|
import { homedir } from "node:os";
|
|
@@ -11,21 +11,61 @@ import { declaredAgentType } from "./agent_type.js";
|
|
|
11
11
|
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
|
-
export const
|
|
14
|
+
export const DEV_BASE_URL = "https://dev.itpay.ai";
|
|
15
|
+
export const CLI_VERSION = "2.0.16";
|
|
15
16
|
export const API_CONTRACT_REVISION = "sha256:7f4c40b082292bf823631bcd37d452f4a8537153e30636d5eb3a2b24a77ce602";
|
|
16
17
|
const CART_SESSION_DEFAULT_DIR = ".itpay-v3";
|
|
17
18
|
const CART_SESSION_FILENAME = "cart.json";
|
|
18
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
|
+
}
|
|
27
|
+
export class BackendOverrideError extends Error {
|
|
28
|
+
code = "backend_override_forbidden";
|
|
29
|
+
constructor() {
|
|
30
|
+
super(`ITPAY_BACKEND_URL only supports ${DEFAULT_BASE_URL} or ${DEV_BASE_URL}`);
|
|
31
|
+
this.name = "BackendOverrideError";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function resolveBackendURL(env = process.env) {
|
|
35
|
+
const requested = env.ITPAY_BACKEND_URL?.trim();
|
|
36
|
+
if (!requested || requested === DEFAULT_BASE_URL || requested === `${DEFAULT_BASE_URL}/`)
|
|
37
|
+
return DEFAULT_BASE_URL;
|
|
38
|
+
if (requested === DEV_BASE_URL || requested === `${DEV_BASE_URL}/`)
|
|
39
|
+
return DEV_BASE_URL;
|
|
40
|
+
throw new BackendOverrideError();
|
|
41
|
+
}
|
|
42
|
+
export function qualifyBackendCommand(command, env = process.env) {
|
|
43
|
+
const requested = env.ITPAY_BACKEND_URL?.trim();
|
|
44
|
+
if (requested !== DEV_BASE_URL && requested !== `${DEV_BASE_URL}/`)
|
|
45
|
+
return command;
|
|
46
|
+
if (!command.startsWith("itpay ") || command.startsWith(`ITPAY_BACKEND_URL=${DEV_BASE_URL} `))
|
|
47
|
+
return command;
|
|
48
|
+
return `ITPAY_BACKEND_URL=${DEV_BASE_URL} ${command}`;
|
|
49
|
+
}
|
|
50
|
+
function stateFilename(filename, baseURL) {
|
|
51
|
+
if (baseURL !== DEV_BASE_URL)
|
|
52
|
+
return filename;
|
|
53
|
+
const dot = filename.lastIndexOf(".");
|
|
54
|
+
return dot < 0 ? `${filename}.dev` : `${filename.slice(0, dot)}.dev${filename.slice(dot)}`;
|
|
55
|
+
}
|
|
56
|
+
function stateDir(env) {
|
|
57
|
+
return resolve(env.HOME || homedir(), CART_SESSION_DEFAULT_DIR);
|
|
58
|
+
}
|
|
19
59
|
export function cartSessionPath(env = process.env) {
|
|
20
60
|
if (env.ITPAY_CART_SESSION_PATH) {
|
|
21
61
|
return resolve(env.ITPAY_CART_SESSION_PATH);
|
|
22
62
|
}
|
|
23
|
-
const dir =
|
|
63
|
+
const dir = stateDir(env);
|
|
24
64
|
mkdirSync(dir, { recursive: true });
|
|
25
|
-
return resolve(dir, CART_SESSION_FILENAME);
|
|
65
|
+
return resolve(dir, stateFilename(CART_SESSION_FILENAME, resolveBackendURL(env)));
|
|
26
66
|
}
|
|
27
67
|
export function loadConfig(env = process.env) {
|
|
28
|
-
const baseURL =
|
|
68
|
+
const baseURL = resolveBackendURL(env);
|
|
29
69
|
const bearerToken = env.ITPAY_BEARER_TOKEN || undefined;
|
|
30
70
|
const agentType = declaredAgentType(env);
|
|
31
71
|
const checkoutCurrency = env.ITPAY_CURRENCY || "CNY";
|
|
@@ -34,10 +74,11 @@ export function loadConfig(env = process.env) {
|
|
|
34
74
|
const ideImageDirOverride = env.ITPAY_IDE_IMAGE_DIR_OVERRIDE;
|
|
35
75
|
return {
|
|
36
76
|
baseURL,
|
|
77
|
+
environment: baseURL === DEV_BASE_URL ? "development" : "production",
|
|
37
78
|
...(agentType ? { agentType } : {}),
|
|
38
79
|
checkoutCurrency,
|
|
39
80
|
idempotencyKey,
|
|
40
|
-
...(!env.ITPAY_IDEMPOTENCY_KEY ? { operationJournal: new OperationJournal(resolve(
|
|
81
|
+
...(!env.ITPAY_IDEMPOTENCY_KEY ? { operationJournal: new OperationJournal(resolve(stateDir(env), stateFilename(OPERATION_JOURNAL_FILENAME, baseURL))) } : {}),
|
|
41
82
|
ideImageAttach,
|
|
42
83
|
...(ideImageDirOverride ? { ideImageDirOverride } : {}),
|
|
43
84
|
...(bearerToken ? { bearerToken } : {}),
|
|
@@ -4,16 +4,16 @@
|
|
|
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": "identity-and-sessions",
|
|
6
6
|
"title": "Device Identity And Session Recovery",
|
|
7
|
-
"purpose": "Explain stable local identity,
|
|
7
|
+
"purpose": "Explain stable local identity, official Backend-scoped registrations, Agent Type instances, and bounded automatic session recovery.",
|
|
8
8
|
"when_to_use": [
|
|
9
9
|
"A different Agent window or runtime starts using an existing CLI installation.",
|
|
10
10
|
"The CLI reports device_state_unwritable, or the Backend reports agent_identity_required, agent_device_session_required, or agent_device_revoked.",
|
|
11
|
-
"An operator confirms that the
|
|
11
|
+
"An operator confirms that the currently selected official Backend registration database was reset."
|
|
12
12
|
],
|
|
13
13
|
"identity_model": {
|
|
14
14
|
"local_key": "One owner-only Ed25519 private key for the local installation.",
|
|
15
|
-
"backend_registration": "One
|
|
16
|
-
"agent_instance": "One instance per agent_type under
|
|
15
|
+
"backend_registration": "One registration per official Backend: app.itpay.ai by default and dev.itpay.ai only with the exact test override.",
|
|
16
|
+
"agent_instance": "One instance per agent_type under each Backend registration; same-type windows and chats reuse it.",
|
|
17
17
|
"presentation": "Host and target route output only and never identify the Agent or provide business input."
|
|
18
18
|
},
|
|
19
19
|
"automatic_recovery": [
|
|
@@ -34,27 +34,27 @@
|
|
|
34
34
|
"success_signal": "the full Skill is returned and next keeps the same Agent Type"
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
|
-
"intent": "confirm
|
|
37
|
+
"intent": "confirm the selected official Backend is available",
|
|
38
38
|
"command": "itpay --agent-type <agent_type> readyz --json",
|
|
39
39
|
"success_signal": "Backend is available and onboarding returns to the Skill"
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
|
-
"intent": "recover after an operator-confirmed reset of the
|
|
42
|
+
"intent": "recover after an operator-confirmed reset of the selected official Backend registration database",
|
|
43
43
|
"command": "itpay --agent-type <agent_type> device recover --confirm-backend-reset --json",
|
|
44
|
-
"success_signal": "only the
|
|
44
|
+
"success_signal": "only the selected Backend registration is removed and the private key and other Backend registration remain"
|
|
45
45
|
}
|
|
46
46
|
],
|
|
47
47
|
"agent_rules": [
|
|
48
48
|
"Use the real runtime type and keep it stable across every command in the flow.",
|
|
49
49
|
"Keep the same CLI/Node launcher and Host-approved permission context for the whole flow.",
|
|
50
50
|
"Treat a new window, chat, task, or process of the same runtime as the same Agent Type, not a new identity.",
|
|
51
|
-
"Expect
|
|
52
|
-
"After automatic recovery fails, stop and report the exact code,
|
|
51
|
+
"Expect production traffic to use https://app.itpay.ai. Use https://dev.itpay.ai only for an explicit test and keep the complete returned Backend-prefixed command for that entire flow.",
|
|
52
|
+
"After automatic recovery fails, stop and report the exact code, selected official Backend, Agent Type, and command family without exposing private keys or tokens."
|
|
53
53
|
],
|
|
54
54
|
"forbidden": [
|
|
55
55
|
"Do not delete ~/.itpay-v3, rotate the key, switch Agent Type, or repeatedly retry to obtain fresh quota.",
|
|
56
56
|
"Do not use device recover for session expiry, revocation, quota recovery, or an unconfirmed Backend failure.",
|
|
57
|
-
"Do not redirect the CLI to
|
|
57
|
+
"Do not redirect the CLI to test, local, an IP, or any custom Backend; only the exact official dev.itpay.ai test override is allowed.",
|
|
58
58
|
"Do not use --target as identity or service input."
|
|
59
59
|
],
|
|
60
60
|
"next_docs": [
|
|
@@ -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": "
|
|
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
|
|
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,21 +32,20 @@
|
|
|
32
32
|
}
|
|
33
33
|
],
|
|
34
34
|
"agent_rules": [
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"If backend_contract_incompatible includes result.required_cli_version, stop all ItPay business commands and
|
|
35
|
+
"Use the CLI distribution already provided by the current npm package, Skill, or plugin; install only through that distribution's own instructions.",
|
|
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 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
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"Agent Type identifies the runtime. Host controls rendering and target only identifies a presentation destination.",
|
|
39
|
+
"Use one exact type: codex-desktop, codex-cli, claude-code-desktop, claude-code-cli, workbuddy, kimi-code, or openclaw.",
|
|
40
|
+
"One local private key is reused, while Device registrations and quota lineage remain separate for app.itpay.ai and dev.itpay.ai.",
|
|
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. OpenClaw requires an explicit Host and IM target; Kimi Code uses the standard terminal CLI contract.",
|
|
44
43
|
"Keep the exact Agent Type in every next or recovery command; do not fall back to a type previously used by another runtime.",
|
|
45
44
|
"Do not change Agent Type or rotate local identity to reset quota or recover a failed command."
|
|
46
45
|
],
|
|
47
46
|
"forbidden": [
|
|
48
47
|
"Do not use codex, terminal, claude-code, or plain-chat as Agent Types.",
|
|
49
|
-
"Do not set or suggest
|
|
48
|
+
"Do not set or suggest any Backend except the default https://app.itpay.ai or the exact test override https://dev.itpay.ai.",
|
|
50
49
|
"Do not claim that install writes host configuration or registers a device; it only prints instructions.",
|
|
51
50
|
"Do not create a new identity for a different window, task, chat, or process of the same Agent Type."
|
|
52
51
|
],
|
|
@@ -67,6 +66,8 @@
|
|
|
67
66
|
"backend",
|
|
68
67
|
"codex",
|
|
69
68
|
"claude code",
|
|
70
|
-
"workbuddy"
|
|
69
|
+
"workbuddy",
|
|
70
|
+
"kimi code",
|
|
71
|
+
"openclaw"
|
|
71
72
|
]
|
|
72
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
|
],
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"required_state": {
|
|
13
13
|
"needs": [
|
|
14
14
|
"the real supported Agent Type",
|
|
15
|
-
"
|
|
15
|
+
"one official Backend: production https://app.itpay.ai by default, or explicit test https://dev.itpay.ai"
|
|
16
16
|
],
|
|
17
17
|
"must_not_need": [
|
|
18
18
|
"Admin access",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"commands": [
|
|
24
24
|
{
|
|
25
|
-
"intent": "verify
|
|
25
|
+
"intent": "verify the selected official Backend availability",
|
|
26
26
|
"command": "itpay --agent-type <agent_type> readyz --json",
|
|
27
27
|
"success_signal": "status is ready and next points to the complete packaged ItPay Skill"
|
|
28
28
|
},
|
|
@@ -57,7 +57,9 @@
|
|
|
57
57
|
"Treat result as current facts, instruction as how to use those facts, next as the preferred executable action, and recovery as exceptional paths.",
|
|
58
58
|
"Run one state-changing command at a time. Treat next.command as the preferred continuation: execute it unchanged after filling required user data only when the current result has not already satisfied the user's goal.",
|
|
59
59
|
"Keep the same explicit Agent Type through next and recovery commands; same-type windows reuse one Backend Agent Instance.",
|
|
60
|
+
"When testing dev, execute every returned command unchanged so its ITPAY_BACKEND_URL=https://dev.itpay.ai prefix is preserved.",
|
|
60
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.",
|
|
61
63
|
"Start a separate Service Execution for each independent service intent; quota remains shared according to Backend identity policy.",
|
|
62
64
|
"Ask the user for required email or contact values and explain their purpose; never invent them.",
|
|
63
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
|
|
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
|
}
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
| `claude-code-desktop` | `claude-code` | 返回桌面对话可展示的 Markdown 图片和付款链接,要求先展示再等待。 |
|
|
16
16
|
| `claude-code-cli` | `terminal` | 在用户可见终端输出二维码和链接,不声称已在桌面对话展示。 |
|
|
17
17
|
| `workbuddy` | `plain-chat` | 只返回 HTTPS 二维码和付款链接;要求 Agent 用 `present_files` 在右侧预览打开二维码,不返回或检查本地图片路径。 |
|
|
18
|
+
| `kimi-code` | `terminal` | 使用标准 CLI 引导,在用户可见终端渲染二维码和付款链接。 |
|
|
19
|
+
| `openclaw` | 无;必须显式传入 | `--host telegram` 使用 OpenClaw 原生 `message` action;其他入口返回标准 HTTPS 二维码和付款链接。 |
|
|
18
20
|
|
|
19
21
|
## 通用规则
|
|
20
22
|
|
|
@@ -24,8 +26,11 @@
|
|
|
24
26
|
- 显式 `--host` 覆盖默认 Host,但不改变已登记的 Agent Type。
|
|
25
27
|
- `--target` 只路由人类展示,不是身份,也不是 capability 业务输入。
|
|
26
28
|
- Host 只影响 `instruction` 和 `handoff`,不得改变金额、订单、权限、quota 或交付状态。
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
+
- 七种 Agent Type 使用同一命令输入和 JSON 外壳;OpenClaw 只在 `handoff` 的既有扩展位置增加平台 action,不改变交易字段。
|
|
30
|
+
- 非展示命令在七种 Agent Type 下返回相同业务结果,只允许 `instruction` 措辞不同。只有 Host 客观无法展示某种媒介时,`handoff` 才按既有可选字段做最小裁剪。
|
|
31
|
+
- `openclaw` 没有默认 Host。展示命令必须显式传 `--host`;缺少时在任何 Checkout 创建或状态迁移前返回 `host_required`。
|
|
32
|
+
- OpenClaw 的 IM Host 必须提供 `--target`。缺失时在任何 Checkout 创建前返回 `target_required`。
|
|
33
|
+
- `kimi-code` 是 CLI 型 Agent,复用 `terminal` Host 和现有 CLI 展示,不增加 Kimi 专属交易协议。
|
|
29
34
|
- session 失效时 CLI 只续期并重试原请求一次;再次失败立即返回。revoked v2 Device 不自动换身份。
|
|
30
35
|
|
|
31
36
|
## Checkout Handoff 最小合同
|
|
@@ -62,5 +67,10 @@
|
|
|
62
67
|
| `codex-cli / terminal` | `url`;非 JSON 输出另外渲染终端二维码 |
|
|
63
68
|
| `claude-code-cli / terminal` | `url`;非 JSON 输出另外渲染终端二维码 |
|
|
64
69
|
| `workbuddy / plain-chat` | `url, qr_image_url` |
|
|
70
|
+
| `kimi-code / terminal` | `url`;非 JSON 输出另外渲染终端二维码 |
|
|
71
|
+
| `openclaw / telegram` | `url, qr_image_url, agent_action` |
|
|
72
|
+
| `openclaw / other` | `url, qr_image_url` |
|
|
65
73
|
|
|
66
74
|
WorkBuddy instruction 只在 `handoff.qr_image_url` 存在时要求读取其完整字符串并作为 `files` 数组唯一元素调用 `present_files`。如果该可选字段不存在,必须直接发送 `handoff.url`,明确不要调用 `present_files`。两种情况都要停止等待;不能检查本地文件、下载或重建二维码、调用 `pay` 或创建替代付款资源。显式 `--host` 仍覆盖默认展示方式,因此只有 `workbuddy + plain-chat` 使用该规则。
|
|
75
|
+
|
|
76
|
+
OpenClaw Telegram 的 `handoff.agent_action` 使用原生 `message` tool,包含当前 `target`、二维码 media、付款链接和 typed Presentation actions。URL 按钮使用 `action.type=url`;只读查询按钮使用 `action.type=callback`,callback 只携带 Checkout ID,不携带 display token,也不构成付款证明。
|
|
@@ -62,8 +62,8 @@ itpay buy \
|
|
|
62
62
|
| `--contact-email` | 条件必填 | 普通 Cart 或兼容旧 Quote 缺少所需邮箱时使用,值必须来自用户;新版 Service Quote 会自动携带已确认邮箱。 |
|
|
63
63
|
| `--contact-phone` | 条件必填 | `--require-contact` 包含 `phone` 时必填,值必须来自用户。 |
|
|
64
64
|
| `--require-contact` | 否 | 只接受 `email`、`phone`;缺失时先询问用户,禁止 Agent 编造。 |
|
|
65
|
-
| `--host` |
|
|
66
|
-
| `--target` | 条件必填 |
|
|
65
|
+
| `--host` | 条件必填 | 通常由 `--agent-type` 推导;`openclaw` 必须显式传当前入口。只改变 handoff 展示,不改变交易事实。 |
|
|
66
|
+
| `--target` | 条件必填 | 要求目标会话的 IM Host 必须提供;OpenClaw 从当前可信会话上下文传入。 |
|
|
67
67
|
| `--qr-format` | 否 | 非 JSON 的终端渲染选项。 |
|
|
68
68
|
| `--qr-file` | 否 | 非 JSON handoff 的明确二维码文件路径。 |
|
|
69
69
|
| `--pay` | 否 | 创建 Payment Intent 的集成/运维入口;普通 Agent 流程只展示 Checkout。 |
|
|
@@ -91,7 +91,8 @@ itpay buy \
|
|
|
91
91
|
"url": "<tokenized_checkout_url>",
|
|
92
92
|
"qr_local_path": "<desktop_optional_host_ready_file>",
|
|
93
93
|
"markdown": "<desktop_only_optional_markdown>",
|
|
94
|
-
"qr_image_url": "<
|
|
94
|
+
"qr_image_url": "<chat_host_optional_absolute_https_png>",
|
|
95
|
+
"agent_action": "<openclaw_telegram_only_native_message_action>"
|
|
95
96
|
},
|
|
96
97
|
"instruction": "<current_host_instruction>",
|
|
97
98
|
"next": {
|
|
@@ -102,7 +103,7 @@ itpay buy \
|
|
|
102
103
|
}
|
|
103
104
|
```
|
|
104
105
|
|
|
105
|
-
`handoff` 只保留当前 Host 可以使用的字段。不得返回二维码 base64、镜像路径数组、renderer
|
|
106
|
+
`handoff` 只保留当前 Host 可以使用的字段。不得返回二维码 base64、镜像路径数组、renderer 状态或原始后端 DTO。`agent_action` 只允许出现在 `openclaw + telegram`。
|
|
106
107
|
|
|
107
108
|
**Instruction:** 必须使用下方 Agent Type 表定义的展示动作;展示完成或失败后停止。只有用户明确表示已付款或要求查询时才执行 `next.command`,并以后端状态为准。
|
|
108
109
|
|
|
@@ -165,5 +166,9 @@ itpay buy \
|
|
|
165
166
|
| `claude-code-desktop` | `claude-code` | `url`、可用时 `qr_local_path` 和 `markdown` | 把 Markdown handoff 发到当前桌面对话,不能只输出本地路径。 |
|
|
166
167
|
| `claude-code-cli` | `terminal` | `url` | 在用户可见终端展示;不能声称桌面对话已收到图片。 |
|
|
167
168
|
| `workbuddy` | `plain-chat` | `url, qr_image_url?` | 有 `qr_image_url` 时调用 `present_files`;没有时只发送金额和 `url` 且不调用工具。两者都停止,不读取本地文件。 |
|
|
169
|
+
| `kimi-code` | `terminal` | `url` | 使用标准 CLI 非 JSON 终端二维码和链接。 |
|
|
170
|
+
| `openclaw` | 必须显式 | Telegram 返回 `url,qr_image_url,agent_action`;其他入口返回 `url,qr_image_url` | Telegram 执行原生 `message` action;其他入口直接展示图片和链接。 |
|
|
168
171
|
|
|
169
172
|
显式 `--host` 可以覆盖展示方式,但不会改变 Agent Type、设备身份、金额、权限或交易状态。
|
|
173
|
+
|
|
174
|
+
OpenClaw 缺少 `--host`,或 IM Host 缺少 `--target` 时,必须在创建 Cart/Checkout 前分别返回 `host_required` 或 `target_required`。
|
|
@@ -85,4 +85,4 @@ Quote 模式返回:
|
|
|
85
85
|
|
|
86
86
|
## Agent Type / Host
|
|
87
87
|
|
|
88
|
-
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`
|
|
88
|
+
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种类型都写入真实 Agent Type。默认 Host 分别是 `codex`、`terminal`、`claude-code`、`terminal`、`plain-chat`。业务输出合同相同;此命令本身不显示二维码。
|
|
@@ -52,4 +52,4 @@ itpay cart clear [--local] [--json]
|
|
|
52
52
|
|
|
53
53
|
## Agent Type / Host
|
|
54
54
|
|
|
55
|
-
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`
|
|
55
|
+
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 行为相同。
|
|
@@ -29,4 +29,4 @@ itpay cart --help
|
|
|
29
29
|
|
|
30
30
|
## Agent Type / Host
|
|
31
31
|
|
|
32
|
-
Cart 事实在 `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`
|
|
32
|
+
Cart 事实在 `codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 下相同;`add` 创建 client context 时记录真实 Agent Type 和 Host。
|
|
@@ -70,4 +70,4 @@ itpay buy --cart <cart_id> --json
|
|
|
70
70
|
|
|
71
71
|
## Agent Type / Host
|
|
72
72
|
|
|
73
|
-
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`
|
|
73
|
+
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 的业务字段、instruction 和 next 相同。本命令不产生二维码或 Host handoff。
|
|
@@ -18,7 +18,7 @@ itpay cart remove --local --variant <catalog_variant_id> --offer <offer_id> [--j
|
|
|
18
18
|
|
|
19
19
|
| 参数 | 必填 | 说明 |
|
|
20
20
|
| --- | --- | --- |
|
|
21
|
-
| `--line <cart_item_id>` | 否 | canonical Cart line
|
|
21
|
+
| `--line <cart_item_id>` | 否 | canonical Cart line;省略时使用当前官方 Backend 对应本地状态中最后保存的 line。 |
|
|
22
22
|
| `--local` | 否 | 只修改显式本地草稿,不请求 Backend。 |
|
|
23
23
|
| `--variant <catalog_variant_id>` | local 时是 | 与 `--offer` 一起标识本地草稿 line。 |
|
|
24
24
|
| `--offer <offer_id>` | local 时是 | 与 `--variant` 一起标识本地草稿 line。 |
|
|
@@ -52,4 +52,4 @@ quote/checkout 已锁定时返回 `cart_item_locked`,要求继续已有 Checko
|
|
|
52
52
|
|
|
53
53
|
## Agent Type / Host
|
|
54
54
|
|
|
55
|
-
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`
|
|
55
|
+
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 行为相同。
|
|
@@ -64,4 +64,4 @@ canonical 输出不得包含 Catalog variant/offer、line input、Service quote
|
|
|
64
64
|
|
|
65
65
|
## Agent Type / Host
|
|
66
66
|
|
|
67
|
-
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`
|
|
67
|
+
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 行为相同。
|
|
@@ -25,4 +25,4 @@ itpay catalog --help
|
|
|
25
25
|
|
|
26
26
|
## Agent Type / Host
|
|
27
27
|
|
|
28
|
-
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`
|
|
28
|
+
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种支持类型的目录事实完全相同。instruction 可按 Agent Type 调整措辞,但不得改变服务排序、价格或可用能力。
|
|
@@ -18,7 +18,7 @@ itpay checkout [--id <checkout_id>] [--token <display_token>]
|
|
|
18
18
|
|
|
19
19
|
省略 `--id/--token` 时只能使用本机保存的一组完整句柄;不得把其他 Checkout 的 token 拼接使用。
|
|
20
20
|
|
|
21
|
-
`--host` 默认由 `--agent-type`
|
|
21
|
+
`--host` 默认由 `--agent-type` 决定;`openclaw` 必须显式传当前入口。IM Host 必须提供 `--target`。`--json` 输出机器可读合同,不内嵌二维码字符画或图片二进制。
|
|
22
22
|
|
|
23
23
|
## 等待付款输出
|
|
24
24
|
|
|
@@ -26,7 +26,7 @@ itpay checkout [--id <checkout_id>] [--token <display_token>]
|
|
|
26
26
|
{
|
|
27
27
|
"status": "human_checkout_required",
|
|
28
28
|
"result": { "checkout_id": "<checkout_id>", "payment": "pending", "amount": "<amount> <currency>" },
|
|
29
|
-
"handoff": { "url": "<checkout_url>", "qr_local_path": "<desktop_optional_path>", "qr_image_url": "<
|
|
29
|
+
"handoff": { "url": "<checkout_url>", "qr_local_path": "<desktop_optional_path>", "qr_image_url": "<chat_optional_absolute_https_png>", "markdown": "<desktop_optional_markdown>", "agent_action": "<openclaw_telegram_optional_native_message_action>" },
|
|
30
30
|
"instruction": "<exact_agent_type_instruction>",
|
|
31
31
|
"next": { "command": "itpay checkout --id <checkout_id> --token <display_token> --json", "reason": "稍后只查询同一 Checkout" },
|
|
32
32
|
"recovery": []
|
|
@@ -72,5 +72,7 @@ token 缺失或不匹配时使用本机句柄恢复。只有请求的 Checkout
|
|
|
72
72
|
| `claude-code-desktop` | `url, qr_local_path, markdown`;原样发送 Markdown。 |
|
|
73
73
|
| `claude-code-cli` | `url`;普通文本模式渲染终端二维码。 |
|
|
74
74
|
| `workbuddy` | `url, qr_image_url?`;有二维码 URL 时按 `services checkout` 相同规则调用 `present_files`,没有时只发送 Checkout URL,不生成本地文件。 |
|
|
75
|
+
| `kimi-code` | `url`;普通文本模式渲染标准终端二维码。 |
|
|
76
|
+
| `openclaw` | Telegram 为 `url,qr_image_url,agent_action`;其他显式 Host 为 `url,qr_image_url`。 |
|
|
75
77
|
|
|
76
|
-
|
|
78
|
+
完成、退款或失效状态下所有 Agent Type 都只返回同一状态和下一步,不渲染二维码。OpenClaw 的 `--target` 必须传入展示层,不能被 CLI 参数解析后丢弃。
|
|
@@ -10,6 +10,6 @@
|
|
|
10
10
|
itpay --agent-type <agent_type> device recover --confirm-backend-reset --json
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
命令只作用于当前官方 Backend 的 Device registration,并保留本地 Ed25519 私钥、Cart 和业务资源。默认是 `https://app.itpay.ai`;显式测试可使用准确的 `ITPAY_BACKEND_URL=https://dev.itpay.ai`。该命令不访问 Backend、不自动创建新身份;返回的只读 `services list` 会保留同一 Backend,是重新登记入口。
|
|
14
14
|
|
|
15
15
|
缺少确认参数返回 `backend_reset_confirmation_required`。普通 session 失效由 CLI 自动续期;revoked、quota、权限或未知 Backend 故障不得使用本命令。所有 Agent Type 使用相同输入和输出合同。
|
|
@@ -27,4 +27,4 @@ itpay docs --help
|
|
|
27
27
|
|
|
28
28
|
## Agent Type / Host
|
|
29
29
|
|
|
30
|
-
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`
|
|
30
|
+
`codex-desktop`、`codex-cli`、`claude-code-desktop`、`claude-code-cli`、`workbuddy`、`kimi-code`、`openclaw` 七种 Agent Type 使用同一文档源;topic 内容可以包含各类型专属 section。
|