@itpay/cli 0.2.17 → 2.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +148 -447
  2. package/bin/itp +1 -150
  3. package/dist/src/client/backend.js +154 -0
  4. package/dist/src/client/http.js +76 -0
  5. package/dist/src/client/types.js +4 -0
  6. package/dist/src/commands/buy.js +351 -0
  7. package/dist/src/commands/cart.js +264 -0
  8. package/dist/src/commands/catalog.js +26 -0
  9. package/dist/src/commands/checkout.js +106 -0
  10. package/dist/src/commands/docs.js +61 -0
  11. package/dist/src/commands/guidance.js +422 -0
  12. package/dist/src/commands/install.js +95 -0
  13. package/dist/src/commands/order.js +78 -0
  14. package/dist/src/commands/orders.js +22 -0
  15. package/dist/src/commands/pay.js +26 -0
  16. package/dist/src/commands/readyz.js +8 -0
  17. package/dist/src/commands/refund.js +20 -0
  18. package/dist/src/commands/services.js +317 -0
  19. package/dist/src/main.js +606 -0
  20. package/dist/src/render/feishu.js +201 -0
  21. package/dist/src/render/ide.js +321 -0
  22. package/dist/src/render/index.js +57 -0
  23. package/dist/src/render/interaction.js +49 -0
  24. package/dist/src/render/markdown.js +83 -0
  25. package/dist/src/render/output.js +42 -0
  26. package/dist/src/render/plain_chat.js +60 -0
  27. package/dist/src/render/plan.js +31 -0
  28. package/dist/src/render/qr.js +32 -0
  29. package/dist/src/render/sink.js +6 -0
  30. package/dist/src/render/status.js +37 -0
  31. package/dist/src/render/telegram.js +172 -0
  32. package/dist/src/render/terminal.js +148 -0
  33. package/dist/src/render/terminal_image.js +19 -0
  34. package/dist/src/state/cart_session.js +151 -0
  35. package/dist/src/state/client_context.js +73 -0
  36. package/dist/src/state/config.js +82 -0
  37. package/dist/src/state/device_authority.js +217 -0
  38. package/dist/src/state/operation_journal.js +80 -0
  39. package/docs/agent/buyer/cart-checkout.json +56 -94
  40. package/docs/agent/buyer/catalog-list.json +47 -0
  41. package/docs/agent/buyer/install-and-setup.json +82 -0
  42. package/docs/agent/buyer/orders-refunds.json +76 -0
  43. package/docs/agent/buyer/payment-flow.json +77 -0
  44. package/docs/agent/buyer/quickstart.json +143 -75
  45. package/docs/agent/buyer/render-hosts.json +79 -0
  46. package/package.json +32 -13
  47. package/skills/itpay-buyer/SKILL.md +107 -238
  48. package/docs/agent/buyer/account-portal.json +0 -81
  49. package/docs/agent/buyer/catalog-search.json +0 -106
  50. package/docs/agent/buyer/human-claim-ui.json +0 -77
  51. package/docs/agent/buyer/payment-qr.json +0 -97
  52. package/docs/agent/buyer/payment-wait.json +0 -84
  53. package/docs/agent/buyer/product-recommendation.json +0 -80
  54. package/docs/agent/buyer/qr-refresh.json +0 -67
  55. package/docs/agent/buyer/recovery.json +0 -85
  56. package/docs/agent/buyer/safety-policy.json +0 -70
  57. package/docs/agent/buyer/secure-delivery.json +0 -90
  58. package/docs/agent/buyer/vault-agent-read.json +0 -95
  59. package/install.ps1 +0 -65
  60. package/install.sh +0 -66
  61. package/lib/account-status.js +0 -157
  62. package/lib/buyer.js +0 -2332
  63. package/lib/client-context.js +0 -126
  64. package/lib/docs.js +0 -200
  65. package/lib/env.js +0 -723
  66. package/lib/http.js +0 -151
  67. package/lib/ops.js +0 -135
  68. package/lib/render-human.js +0 -718
  69. package/lib/runtime.js +0 -1456
@@ -0,0 +1,106 @@
1
+ // Reads the canonical V3 checkout presentation. Requires both checkout_id
2
+ // and the checkout-scoped display_token. Supports terminal and agent markdown.
3
+ import { formatMoney } from "../render/output.js";
4
+ import { hintFor } from "../render/status.js";
5
+ import { resolveOutput } from "../render/sink.js";
6
+ import { ensureIdeImageAttach, ideImageAttachBlock } from "../render/ide.js";
7
+ import { DEFAULT_BASE_URL } from "../state/config.js";
8
+ export async function runCheckoutPresentation(backend, options) {
9
+ const out = resolveOutput(options.output);
10
+ const presentation = await backend.getCheckoutPresentation(options.checkoutID, options.displayToken);
11
+ const checkoutURL = checkoutPageURL(options.baseURL, options.checkoutID, options.displayToken);
12
+ const qrPNGURL = presentation.qr_png_url ?? checkoutQRPNGURL(options.baseURL, options.checkoutID, options.displayToken);
13
+ const plan = {
14
+ kind: "checkout_qr",
15
+ host: (options.host ?? "terminal"),
16
+ summary: "checkout presentation",
17
+ url: checkoutURL,
18
+ preferredQRSources: [qrPNGURL],
19
+ platform: {
20
+ text: "checkout presentation",
21
+ links: [{ label: "打开付款页面", url: checkoutURL }],
22
+ buttons: [],
23
+ blocks: [],
24
+ },
25
+ };
26
+ await ensureIdeImageAttach(plan, {
27
+ ...(options.baseURL ? { baseURL: options.baseURL } : {}),
28
+ });
29
+ if (options.host === "codex" || options.host === "claude-code" || options.host === "trae") {
30
+ out(renderCheckoutMarkdown(presentation, plan) + "\n");
31
+ }
32
+ else {
33
+ out(renderCheckoutText(presentation) + "\n");
34
+ if (plan.ideImageAttach) {
35
+ out(ideImageAttachBlock(plan.ideImageAttach).filter((l) => l.length > 0).join("\n") + "\n");
36
+ }
37
+ out(`hint: ${hintFor("checkout", presentation.checkout.status)}\n`);
38
+ }
39
+ }
40
+ function renderCheckoutText(presentation) {
41
+ const lines = [];
42
+ const c = presentation.checkout;
43
+ lines.push(`checkout ${c.checkout_id}`);
44
+ lines.push(` status: ${c.status}`);
45
+ lines.push(` next_action: ${c.next_action}`);
46
+ lines.push(` amount: ${formatMoney(c.amount_minor, c.currency)}`);
47
+ lines.push(` buyer: ${presentation.buyer_session.state}`);
48
+ if (presentation.items.length > 0) {
49
+ lines.push(" items:");
50
+ for (const item of presentation.items) {
51
+ lines.push(` - ${item.title} × ${item.quantity} (${formatMoney(item.amount_minor, item.currency)})`);
52
+ }
53
+ }
54
+ if (presentation.payment_intents.length > 0) {
55
+ lines.push(" payment_intents:");
56
+ for (const intent of presentation.payment_intents) {
57
+ lines.push(` - ${intent.payment_intent_id} ${intent.status} (${intent.payment_method_type}, ${formatMoney(intent.amount_minor, intent.currency)})`);
58
+ }
59
+ }
60
+ return lines.join("\n");
61
+ }
62
+ function renderCheckoutMarkdown(presentation, plan) {
63
+ const c = presentation.checkout;
64
+ const lines = [];
65
+ lines.push(`## :mag: Checkout ${c.checkout_id}`);
66
+ lines.push("");
67
+ lines.push(`| 字段 | 值 |`);
68
+ lines.push(`|------|-----|`);
69
+ lines.push(`| 状态 | ${c.status} |`);
70
+ lines.push(`| 操作 | ${c.next_action} |`);
71
+ lines.push(`| 金额 | ${formatMoney(c.amount_minor, c.currency)} |`);
72
+ lines.push(`| 买家 | ${presentation.buyer_session.state} |`);
73
+ lines.push("");
74
+ if (presentation.items.length > 0) {
75
+ lines.push(`| 项目 | 数量 | 单价 |`);
76
+ lines.push(`|------|:----:|------|`);
77
+ for (const item of presentation.items) {
78
+ lines.push(`| ${item.title} | ${item.quantity} | ${formatMoney(item.amount_minor, item.currency)} |`);
79
+ }
80
+ lines.push("");
81
+ }
82
+ if (presentation.payment_intents.length > 0) {
83
+ lines.push(`### :credit_card: 支付`);
84
+ lines.push("");
85
+ for (const intent of presentation.payment_intents) {
86
+ lines.push(`- \`${intent.payment_intent_id}\` — ${intent.payment_method_type} — ${intent.status} — ${formatMoney(intent.amount_minor, intent.currency)}`);
87
+ }
88
+ lines.push("");
89
+ }
90
+ if (plan.ideImageAttach) {
91
+ lines.push(...ideImageAttachBlock(plan.ideImageAttach));
92
+ }
93
+ lines.push(`> :bulb: ${hintFor("checkout", c.status)}`);
94
+ return lines.join("\n");
95
+ }
96
+ function checkoutPageURL(baseURL, checkoutID, displayToken) {
97
+ const root = publicRoot(baseURL);
98
+ return `${root}/checkout/${encodeURIComponent(checkoutID)}?display_token=${encodeURIComponent(displayToken)}`;
99
+ }
100
+ function checkoutQRPNGURL(baseURL, checkoutID, displayToken) {
101
+ const root = publicRoot(baseURL);
102
+ return `${root}/v1/checkouts/${encodeURIComponent(checkoutID)}/qr.png?display_token=${encodeURIComponent(displayToken)}`;
103
+ }
104
+ function publicRoot(baseURL) {
105
+ return (baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "");
106
+ }
@@ -0,0 +1,61 @@
1
+ import { readFileSync, readdirSync, existsSync } from "node:fs";
2
+ import { resolve, dirname } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+ function findDocsDir() {
6
+ if (process.env.ITPAY_CLI_DOCS_DIR) {
7
+ return process.env.ITPAY_CLI_DOCS_DIR;
8
+ }
9
+ // dist/src/commands → ../../../docs/agent/buyer = <pkg>/docs/agent/buyer
10
+ const pkgPath = resolve(__dirname, "..", "..", "..", "docs", "agent", "buyer");
11
+ if (existsSync(pkgPath))
12
+ return pkgPath;
13
+ // src/commands → ../../docs/agent/buyer = <pkg>/docs/agent/buyer (dev mode)
14
+ const devPath = resolve(__dirname, "..", "..", "docs", "agent", "buyer");
15
+ if (existsSync(devPath))
16
+ return devPath;
17
+ return pkgPath;
18
+ }
19
+ const DOCS_DIR = findDocsDir();
20
+ function loadDocs() {
21
+ const files = readdirSync(DOCS_DIR).filter((f) => f.endsWith(".json"));
22
+ return files.map((file) => {
23
+ const raw = readFileSync(resolve(DOCS_DIR, file), "utf-8");
24
+ return JSON.parse(raw);
25
+ });
26
+ }
27
+ export function runDocsList() {
28
+ const docs = loadDocs();
29
+ process.stdout.write(`Agent docs (${docs.length} topics):\n\n`);
30
+ for (const doc of docs) {
31
+ process.stdout.write(` ${doc.topic}\n`);
32
+ process.stdout.write(` title: ${doc.title}\n`);
33
+ process.stdout.write(` purpose: ${doc.purpose}\n\n`);
34
+ }
35
+ }
36
+ export function runDocsShow(topic) {
37
+ const docs = loadDocs();
38
+ const doc = docs.find((d) => d.topic === topic);
39
+ if (!doc) {
40
+ process.stderr.write(`doc topic "${topic}" not found. Use "itpay docs list" to see available topics.\n`);
41
+ process.exitCode = 1;
42
+ return;
43
+ }
44
+ process.stdout.write(JSON.stringify(doc, null, 2) + "\n");
45
+ }
46
+ export function runDocsSearch(query) {
47
+ const docs = loadDocs();
48
+ const lower = query.toLowerCase();
49
+ const results = docs.filter((doc) => {
50
+ const text = [doc.topic, doc.title, doc.purpose, ...(doc.search_terms ?? [])].join(" ").toLowerCase();
51
+ return text.includes(lower);
52
+ });
53
+ if (results.length === 0) {
54
+ process.stdout.write(`no docs match "${query}"\n`);
55
+ return;
56
+ }
57
+ process.stdout.write(`${results.length} matching docs:\n\n`);
58
+ for (const doc of results) {
59
+ process.stdout.write(` ${doc.topic} — ${doc.title}\n`);
60
+ }
61
+ }
@@ -0,0 +1,422 @@
1
+ import { HttpError } from "../client/http.js";
2
+ import { resolveOutput } from "../render/sink.js";
3
+ export function attachAgentGuidance(payload, guidance) {
4
+ return {
5
+ ...payload,
6
+ agent_guidance: guidance,
7
+ };
8
+ }
9
+ export function printAgentGuidance(guidance, output) {
10
+ const out = resolveOutput(output);
11
+ out(`${guidance.summary}\n`);
12
+ if (guidance.visible_results?.length) {
13
+ out("results:\n");
14
+ for (const item of guidance.visible_results)
15
+ out(` ${item.rank}. ${item.title}\n`);
16
+ }
17
+ if (guidance.next_actions.length === 0) {
18
+ out("next actions: none\n");
19
+ }
20
+ else {
21
+ out("next actions:\n");
22
+ for (const action of guidance.next_actions) {
23
+ out(` - ${action.label}\n`);
24
+ out(` ${action.command}\n`);
25
+ if (action.requires_human)
26
+ out(" requires human confirmation\n");
27
+ if (action.reason)
28
+ out(` reason: ${action.reason}\n`);
29
+ }
30
+ }
31
+ if (guidance.recovery.length > 0) {
32
+ out("recovery:\n");
33
+ for (const action of guidance.recovery) {
34
+ out(` - ${action.label}\n`);
35
+ out(` ${action.command}\n`);
36
+ }
37
+ }
38
+ }
39
+ export function buildCartGuidance(cart, serviceModel) {
40
+ const serviceLine = latestServiceLine(cart);
41
+ if (serviceLine?.service_execution_id) {
42
+ const serviceGuidance = serviceModel
43
+ ? buildServiceReadModelGuidance(serviceModel)
44
+ : buildServiceHandleGuidance(serviceLine.service_execution_id, serviceLine.service_capability_id);
45
+ return {
46
+ ...serviceGuidance,
47
+ kind: "cart_service_execution",
48
+ summary: `cart ${cart.cart_id}: service-backed line ${lineID(serviceLine)} is ready for Service Execution`,
49
+ state: {
50
+ ...serviceGuidance.state,
51
+ cart_id: cart.cart_id,
52
+ cart_item_id: lineID(serviceLine),
53
+ service_capability_id: serviceLine.service_capability_id,
54
+ },
55
+ };
56
+ }
57
+ if (cart.items.length === 0) {
58
+ return {
59
+ kind: "empty_cart",
60
+ summary: `cart ${cart.cart_id}: empty`,
61
+ state: { cart_id: cart.cart_id, status: cart.status },
62
+ next_actions: [
63
+ {
64
+ id: "browse_catalog",
65
+ label: "Browse services",
66
+ command: "itpay catalog list",
67
+ },
68
+ ],
69
+ recovery: [],
70
+ };
71
+ }
72
+ return {
73
+ kind: "cart_checkout_ready",
74
+ summary: `cart ${cart.cart_id}: ready for checkout`,
75
+ state: { cart_id: cart.cart_id, status: cart.status, amount_minor: cart.amount_minor, currency: cart.currency },
76
+ next_actions: [
77
+ {
78
+ id: "checkout_cart",
79
+ label: "Create ItPay checkout",
80
+ command: `itpay buy --cart ${cart.cart_id} --host <client> --contact-email <email>`,
81
+ requires_human: true,
82
+ reason: "The human must review and pay on the ItPay checkout page.",
83
+ },
84
+ ],
85
+ recovery: [
86
+ {
87
+ id: "show_cart",
88
+ label: "Inspect current server cart",
89
+ command: "itpay cart show",
90
+ },
91
+ ],
92
+ };
93
+ }
94
+ export function buildServiceStartedGuidance(response) {
95
+ return buildServiceGuidance({
96
+ execution: response.execution,
97
+ capabilities: response.capabilities,
98
+ });
99
+ }
100
+ export function buildServiceReadModelGuidance(model) {
101
+ return buildServiceGuidance({
102
+ execution: model.execution,
103
+ capabilities: model.capabilities,
104
+ resultItems: model.result_items,
105
+ checkoutBindings: model.checkout_bindings,
106
+ deliveryBindings: model.delivery_bindings,
107
+ });
108
+ }
109
+ export function buildServiceInvokedGuidance(response) {
110
+ return buildServiceGuidance({
111
+ execution: response.execution,
112
+ resultItems: response.result_items,
113
+ ...(response.next_actions ? { backendNextActions: response.next_actions } : {}),
114
+ ...(response.effective_quota ? { effectiveQuota: response.effective_quota } : {}),
115
+ providerCalled: response.provider_called,
116
+ });
117
+ }
118
+ export function buildServiceActionGuidance(action) {
119
+ const serviceExecutionID = action.service_execution_id;
120
+ return {
121
+ kind: "service_action_recorded",
122
+ summary: `service execution ${serviceExecutionID}: action ${action.action_type} recorded`,
123
+ state: {
124
+ service_execution_id: serviceExecutionID,
125
+ action_type: action.action_type,
126
+ status: action.status,
127
+ result_item_id: action.result_item_id,
128
+ selected_candidate_hash: action.selected_candidate_hash,
129
+ },
130
+ next_actions: [
131
+ {
132
+ id: "inspect_service_execution",
133
+ label: "Read updated Service Execution state",
134
+ command: `itpay services next ${serviceExecutionID} --json`,
135
+ },
136
+ ],
137
+ recovery: [
138
+ {
139
+ id: "timeline",
140
+ label: "Inspect full timeline",
141
+ command: `itpay services get ${serviceExecutionID}`,
142
+ },
143
+ ],
144
+ };
145
+ }
146
+ export function buildServiceHandleGuidance(serviceExecutionID, checkoutCapabilityID) {
147
+ const actions = [
148
+ {
149
+ id: "inspect_service_execution",
150
+ label: "Read Service Execution state and capabilities",
151
+ command: `itpay services next ${serviceExecutionID} --json`,
152
+ },
153
+ ];
154
+ if (checkoutCapabilityID) {
155
+ actions.push({
156
+ id: "checkout_service",
157
+ label: "Create checkout after human confirmation",
158
+ command: `itpay services checkout ${serviceExecutionID} --capability ${checkoutCapabilityID} --email <email> --json`,
159
+ requires_human: true,
160
+ reason: "Only use after the service contract has the required human confirmation or quote lock.",
161
+ });
162
+ }
163
+ return {
164
+ kind: "service_execution_handle",
165
+ summary: `service execution ${serviceExecutionID}: inspect before invoking`,
166
+ state: { service_execution_id: serviceExecutionID },
167
+ next_actions: actions,
168
+ recovery: [],
169
+ };
170
+ }
171
+ export function errorRecoveryActions(error) {
172
+ if (!(error instanceof HttpError))
173
+ return [];
174
+ if (error.code === "agent_identity_required") {
175
+ return [
176
+ {
177
+ id: "set_agent_identity",
178
+ label: "Set a stable agent device id",
179
+ command: "export ITPAY_AGENT_DEVICE_ID=<stable_agent_device_id>",
180
+ },
181
+ ];
182
+ }
183
+ if (error.code === "quota_exhausted" || error.code === "checkout_required") {
184
+ return [
185
+ {
186
+ id: "inspect_service_execution",
187
+ label: "Inspect Service Execution before checkout",
188
+ command: "itpay services next <service_execution_id> --json",
189
+ },
190
+ ];
191
+ }
192
+ if (error.code === "cart_item_locked" || error.status === 409) {
193
+ return [
194
+ {
195
+ id: "show_cart",
196
+ label: "Inspect the canonical server cart",
197
+ command: "itpay cart show",
198
+ },
199
+ {
200
+ id: "continue_checkout",
201
+ label: "Continue the last locally remembered checkout",
202
+ command: "itpay checkout",
203
+ },
204
+ {
205
+ id: "recover_service_execution",
206
+ label: "List recoverable Service Executions if the local handoff is missing",
207
+ command: "itpay services list",
208
+ },
209
+ ];
210
+ }
211
+ if (error.status === 404) {
212
+ return [
213
+ {
214
+ id: "recover_service_executions",
215
+ label: "List visible Service Executions and follow their next instruction",
216
+ command: "itpay services list",
217
+ },
218
+ ];
219
+ }
220
+ if (error.status === 502 || error.status === 503 || error.status === 504) {
221
+ return [
222
+ {
223
+ id: "retry_after_backend_recovers",
224
+ label: "Retry after the ItPay backend is reachable",
225
+ command: "itpay readyz",
226
+ },
227
+ {
228
+ id: "check_backend_url",
229
+ label: "Check the configured backend URL",
230
+ command: "echo $ITPAY_BACKEND_URL",
231
+ },
232
+ ];
233
+ }
234
+ return [];
235
+ }
236
+ export function printErrorRecovery(error, output) {
237
+ const recovery = errorRecoveryActions(error);
238
+ if (recovery.length === 0)
239
+ return;
240
+ const out = resolveOutput(output);
241
+ out("recovery:\n");
242
+ for (const action of recovery) {
243
+ out(` - ${action.label}\n`);
244
+ out(` ${action.command}\n`);
245
+ }
246
+ }
247
+ function buildServiceGuidance(input) {
248
+ const execution = input.execution;
249
+ const capabilities = input.capabilities ?? [];
250
+ const prePurchase = firstPrePurchaseCapability(capabilities);
251
+ const paid = firstPaidCapability(capabilities);
252
+ const resultItem = input.resultItems?.[0];
253
+ const checkoutID = input.checkoutBindings?.at(-1)?.checkout_id;
254
+ const delivery = input.deliveryBindings?.[0];
255
+ const backendCheckout = input.backendNextActions?.find((action) => action.kind === "create_checkout");
256
+ const nextActions = [];
257
+ const recovery = [
258
+ {
259
+ id: "timeline",
260
+ label: "Inspect full timeline",
261
+ command: `itpay services get ${execution.service_execution_id}`,
262
+ },
263
+ ];
264
+ if (execution.status === "completed" || execution.next_action === "completed") {
265
+ nextActions.push({
266
+ id: "inspect_order_or_grant",
267
+ label: "Inspect order, claim, or grant from the checkout/order owner",
268
+ command: "itpay orders --limit 20",
269
+ requires_human: true,
270
+ reason: "Delivery visibility is controlled by the human account and grant flow.",
271
+ });
272
+ }
273
+ else if (execution.phase === "delivery" || execution.next_action === "view_delivery") {
274
+ const grantStatus = String(delivery?.grant_status ?? "");
275
+ if (grantStatus === "active") {
276
+ nextActions.push({
277
+ id: "read_granted_result",
278
+ label: "Read the human-granted service result",
279
+ command: `itpay services read-result ${execution.service_execution_id}`,
280
+ reason: "The human granted temporary agent access to this delivery.",
281
+ });
282
+ }
283
+ else {
284
+ nextActions.push({
285
+ id: grantStatus === "expired" ? "ask_human_to_reauthorize" : "wait_for_human_agent_grant",
286
+ label: grantStatus === "expired" ? "Ask the human to authorize AI access again" : "Wait for human AI-access authorization",
287
+ command: `itpay services get ${execution.service_execution_id}`,
288
+ requires_human: true,
289
+ reason: grantStatus === "expired"
290
+ ? "The 15 minute human grant expired."
291
+ : "The human must approve AI access on the Credential page.",
292
+ });
293
+ }
294
+ }
295
+ else if (execution.next_action === "pay_checkout" || execution.status === "checkout_pending") {
296
+ nextActions.push({
297
+ id: "open_existing_checkout",
298
+ label: "Open the existing ItPay checkout handoff",
299
+ command: `itpay services checkout ${execution.service_execution_id} --resume --json`,
300
+ requires_human: true,
301
+ reason: checkoutID
302
+ ? `Checkout ${checkoutID} already exists; reissue a short-lived handoff without creating another checkout.`
303
+ : "Recover the existing checkout handoff from Service Execution owner facts.",
304
+ });
305
+ }
306
+ else if (execution.checkout_required || execution.next_action === "create_checkout") {
307
+ const capabilityID = backendCheckout?.capability_id ?? paid?.capability_id ?? input.checkoutCapabilityID ?? execution.current_capability_id;
308
+ if (capabilityID) {
309
+ nextActions.push({
310
+ id: "checkout_service",
311
+ label: "Create ItPay checkout for the paid service capability",
312
+ command: `itpay services checkout ${execution.service_execution_id} --capability ${capabilityID} --email <email> --json`,
313
+ requires_human: true,
314
+ reason: "The human must provide delivery contact and pay on the ItPay checkout page.",
315
+ });
316
+ }
317
+ else {
318
+ nextActions.push({
319
+ id: "inspect_capabilities",
320
+ label: "Inspect capabilities before checkout",
321
+ command: `itpay services get ${execution.service_execution_id}`,
322
+ });
323
+ }
324
+ }
325
+ else if (input.providerCalled && (input.resultItems?.length ?? 0) === 0) {
326
+ nextActions.push({
327
+ id: "refine_search",
328
+ label: "Ask for a more specific company name before another lookup",
329
+ command: `itpay services invoke ${execution.service_execution_id} --capability ${execution.current_capability_id ?? "<capability_id>"} --input keyword=<more_specific_company_name>`,
330
+ requires_human: true,
331
+ reason: "The provider returned no candidates. Do not repeat the same query.",
332
+ });
333
+ }
334
+ else if (needsHumanSelection(execution, resultItem)) {
335
+ nextActions.push({
336
+ id: "select_result_item",
337
+ label: "Ask the human to select a result item, then submit the selection",
338
+ command: `itpay services action ${execution.service_execution_id} --action select_candidate --actor-type human --status approved --candidate <rank>`,
339
+ requires_human: true,
340
+ reason: "Do not choose a candidate without explicit human confirmation.",
341
+ });
342
+ }
343
+ else if (prePurchase) {
344
+ const action = {
345
+ id: "invoke_capability",
346
+ label: `Invoke ${prePurchase.capability_id}`,
347
+ command: `itpay services invoke ${execution.service_execution_id} --capability ${prePurchase.capability_id} --input key=value --json`,
348
+ };
349
+ if (prePurchase.free_quota_limit) {
350
+ action.reason = `Free quota limit: ${prePurchase.free_quota_limit} per ${prePurchase.quota_subject || "subject"}.`;
351
+ }
352
+ nextActions.push(action);
353
+ }
354
+ else {
355
+ nextActions.push({
356
+ id: "inspect_service_execution",
357
+ label: "Read Service Execution state",
358
+ command: `itpay services next ${execution.service_execution_id} --json`,
359
+ });
360
+ }
361
+ return {
362
+ kind: "service_execution",
363
+ summary: `service execution ${execution.service_execution_id}: ${execution.status}/${execution.phase}${input.effectiveQuota ? `, quota ${input.effectiveQuota.remaining}/${input.effectiveQuota.limit}` : ""}`,
364
+ state: {
365
+ service_execution_id: execution.service_execution_id,
366
+ service_id: execution.service_id,
367
+ status: execution.status,
368
+ phase: execution.phase,
369
+ current_capability_id: execution.current_capability_id,
370
+ checkout_required: execution.checkout_required,
371
+ next_action: execution.next_action,
372
+ delivery: delivery
373
+ ? {
374
+ status: delivery.status,
375
+ vault_artifact_id: delivery.vault_artifact_id,
376
+ vault_status: delivery.vault_status,
377
+ vault_payload_state: delivery.vault_payload_state,
378
+ reveal_status: delivery.reveal_status,
379
+ grant_status: delivery.grant_status ?? "missing",
380
+ grant_expires_at: delivery.grant_expires_at,
381
+ }
382
+ : undefined,
383
+ capabilities: capabilities.map((capability) => ({
384
+ capability_id: capability.capability_id,
385
+ agent_visible: capability.agent_visible,
386
+ requires_payment: capability.requires_payment,
387
+ free_quota_limit: capability.free_quota_limit,
388
+ })),
389
+ result_items: (input.resultItems ?? []).map((item) => ({
390
+ result_item_id: item.service_capability_result_item_id,
391
+ stable_hash: item.stable_hash,
392
+ rank: item.rank,
393
+ display_title: item.display_title,
394
+ })),
395
+ effective_quota: input.effectiveQuota,
396
+ },
397
+ next_actions: nextActions,
398
+ recovery,
399
+ ...(input.resultItems?.length
400
+ ? { visible_results: input.resultItems.map((item) => ({ rank: item.rank, title: item.display_title })) }
401
+ : {}),
402
+ };
403
+ }
404
+ function firstPrePurchaseCapability(capabilities) {
405
+ return capabilities.find((capability) => capability.agent_visible && !capability.requires_payment)
406
+ ?? capabilities.find((capability) => capability.agent_visible);
407
+ }
408
+ function firstPaidCapability(capabilities) {
409
+ return (capabilities.find((capability) => capability.requires_payment && capability.vault_required) ??
410
+ capabilities.find((capability) => capability.requires_payment));
411
+ }
412
+ function needsHumanSelection(execution, resultItem) {
413
+ if (execution.phase !== "pre_purchase")
414
+ return false;
415
+ return execution.next_action === "select_candidate" || execution.next_action === "human_action_required" || resultItem !== undefined;
416
+ }
417
+ function latestServiceLine(cart) {
418
+ return [...cart.items].reverse().find((item) => item.service_execution_id);
419
+ }
420
+ function lineID(line) {
421
+ return line.cart_item_id ?? line.line_item_id ?? line.checkout_item_id ?? "<unknown_line>";
422
+ }
@@ -0,0 +1,95 @@
1
+ import { DEFAULT_BASE_URL } from "../state/config.js";
2
+ const INSTALL_TARGETS = {
3
+ "claude-code": {
4
+ name: "Claude Code",
5
+ configFile: "~/.claude/settings.json",
6
+ instructions: [
7
+ "1. Ensure itpay is installed: npm install -g @itpay/cli",
8
+ `2. Production API (default): ${DEFAULT_BASE_URL}`,
9
+ "3. Use --agent-type claude-code-cli or claude-code-desktop",
10
+ "4. Use --host claude-code for human-facing output",
11
+ "5. The CLI renders checkout QR as markdown images and links",
12
+ ],
13
+ },
14
+ codex: {
15
+ name: "Codex / Trae",
16
+ configFile: "~/.codex/config.toml",
17
+ instructions: [
18
+ "1. Ensure itpay is installed: npm install -g @itpay/cli",
19
+ `2. Production API (default): ${DEFAULT_BASE_URL}`,
20
+ "3. Use --agent-type codex-cli or codex-desktop",
21
+ "4. Use --host trae or --host codex for human-facing output",
22
+ "5. Attach the emitted QR image and show the checkout link",
23
+ "6. Collect missing contact fields from the user; never invent them",
24
+ ],
25
+ },
26
+ terminal: {
27
+ name: "Terminal",
28
+ configFile: "shell profile (~/.zshrc, ~/.bashrc)",
29
+ instructions: [
30
+ "1. Install globally: npm install -g @itpay/cli",
31
+ `2. Production API (default): ${DEFAULT_BASE_URL}`,
32
+ "3. Set the real runtime type with --agent-type <type>",
33
+ "4. Use --host terminal for text/QR output in terminal",
34
+ "5. Override ITPAY_BACKEND_URL only for local or test backends",
35
+ ],
36
+ },
37
+ telegram: {
38
+ name: "Telegram",
39
+ configFile: "OpenClaw gateway config",
40
+ instructions: [
41
+ "1. Install itpay: npm install -g @itpay/cli",
42
+ `2. Production API (default): ${DEFAULT_BASE_URL}`,
43
+ "3. Set the real OpenClaw runtime with --agent-type <type>",
44
+ "4. Use --host telegram --target <chat_id> for human-facing output",
45
+ "5. The CLI emits openclaw_message payloads with buttons and QR images",
46
+ ],
47
+ },
48
+ feishu: {
49
+ name: "Feishu / Lark",
50
+ configFile: "Feishu bot config",
51
+ instructions: [
52
+ "1. Install itpay: npm install -g @itpay/cli",
53
+ `2. Production API (default): ${DEFAULT_BASE_URL}`,
54
+ "3. Set the real agent runtime with --agent-type <type>",
55
+ "4. Use --host feishu --target <open_id> or --host lark --target <open_id>",
56
+ "5. The CLI emits Interactive Card JSON with buttons and QR images",
57
+ ],
58
+ },
59
+ };
60
+ export function runInstall(target) {
61
+ if (!target || target === "list") {
62
+ listTargets();
63
+ return;
64
+ }
65
+ const normalized = target.toLowerCase();
66
+ if (normalized === "trae") {
67
+ // Trae uses the codex install target
68
+ printInstall("codex");
69
+ return;
70
+ }
71
+ if (INSTALL_TARGETS[normalized]) {
72
+ printInstall(normalized);
73
+ }
74
+ else {
75
+ process.stderr.write(`unknown target "${target}". Available: ${Object.keys(INSTALL_TARGETS).join(", ")}\n`);
76
+ process.exitCode = 1;
77
+ }
78
+ }
79
+ function printInstall(key) {
80
+ const target = INSTALL_TARGETS[key];
81
+ if (!target)
82
+ return;
83
+ process.stdout.write(`\n=== ItPay V3 CLI — Install for ${target.name} ===\n`);
84
+ process.stdout.write(`Config file: ${target.configFile}\n\n`);
85
+ for (const line of target.instructions) {
86
+ process.stdout.write(`${line}\n`);
87
+ }
88
+ process.stdout.write("\n");
89
+ }
90
+ function listTargets() {
91
+ process.stdout.write("Available install targets:\n\n");
92
+ for (const [key, target] of Object.entries(INSTALL_TARGETS)) {
93
+ process.stdout.write(` ${key.padEnd(14)} ${target.name.padEnd(18)} ${target.configFile}\n`);
94
+ }
95
+ }