@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.
- package/README.md +148 -447
- package/bin/itp +1 -150
- package/dist/src/client/backend.js +154 -0
- package/dist/src/client/http.js +76 -0
- package/dist/src/client/types.js +4 -0
- package/dist/src/commands/buy.js +351 -0
- package/dist/src/commands/cart.js +264 -0
- package/dist/src/commands/catalog.js +26 -0
- package/dist/src/commands/checkout.js +106 -0
- package/dist/src/commands/docs.js +61 -0
- package/dist/src/commands/guidance.js +422 -0
- package/dist/src/commands/install.js +95 -0
- package/dist/src/commands/order.js +78 -0
- package/dist/src/commands/orders.js +22 -0
- package/dist/src/commands/pay.js +26 -0
- package/dist/src/commands/readyz.js +8 -0
- package/dist/src/commands/refund.js +20 -0
- package/dist/src/commands/services.js +317 -0
- package/dist/src/main.js +606 -0
- package/dist/src/render/feishu.js +201 -0
- package/dist/src/render/ide.js +321 -0
- package/dist/src/render/index.js +57 -0
- package/dist/src/render/interaction.js +49 -0
- package/dist/src/render/markdown.js +83 -0
- package/dist/src/render/output.js +42 -0
- package/dist/src/render/plain_chat.js +60 -0
- package/dist/src/render/plan.js +31 -0
- package/dist/src/render/qr.js +32 -0
- package/dist/src/render/sink.js +6 -0
- package/dist/src/render/status.js +37 -0
- package/dist/src/render/telegram.js +172 -0
- package/dist/src/render/terminal.js +148 -0
- package/dist/src/render/terminal_image.js +19 -0
- package/dist/src/state/cart_session.js +151 -0
- package/dist/src/state/client_context.js +73 -0
- package/dist/src/state/config.js +82 -0
- package/dist/src/state/device_authority.js +217 -0
- package/dist/src/state/operation_journal.js +80 -0
- package/docs/agent/buyer/cart-checkout.json +56 -94
- package/docs/agent/buyer/catalog-list.json +47 -0
- package/docs/agent/buyer/install-and-setup.json +82 -0
- package/docs/agent/buyer/orders-refunds.json +76 -0
- package/docs/agent/buyer/payment-flow.json +77 -0
- package/docs/agent/buyer/quickstart.json +143 -75
- package/docs/agent/buyer/render-hosts.json +79 -0
- package/package.json +32 -13
- package/skills/itpay-buyer/SKILL.md +107 -238
- package/docs/agent/buyer/account-portal.json +0 -81
- package/docs/agent/buyer/catalog-search.json +0 -106
- package/docs/agent/buyer/human-claim-ui.json +0 -77
- package/docs/agent/buyer/payment-qr.json +0 -97
- package/docs/agent/buyer/payment-wait.json +0 -84
- package/docs/agent/buyer/product-recommendation.json +0 -80
- package/docs/agent/buyer/qr-refresh.json +0 -67
- package/docs/agent/buyer/recovery.json +0 -85
- package/docs/agent/buyer/safety-policy.json +0 -70
- package/docs/agent/buyer/secure-delivery.json +0 -90
- package/docs/agent/buyer/vault-agent-read.json +0 -95
- package/install.ps1 +0 -65
- package/install.sh +0 -66
- package/lib/account-status.js +0 -157
- package/lib/buyer.js +0 -2332
- package/lib/client-context.js +0 -126
- package/lib/docs.js +0 -200
- package/lib/env.js +0 -723
- package/lib/http.js +0 -151
- package/lib/ops.js +0 -135
- package/lib/render-human.js +0 -718
- package/lib/runtime.js +0 -1456
package/dist/src/main.js
ADDED
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
// V3 CLI entrypoint. Each command maps 1:1 to a route family in
|
|
2
|
+
// services/backend/internal/httpapi/handlers/*.go. Commands only
|
|
3
|
+
// orchestrate; HTTP and rendering live in src/client and src/render.
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { loadConfig, cartSessionPath, newBackendClient } from "./state/config.js";
|
|
6
|
+
import { CartSession } from "./state/cart_session.js";
|
|
7
|
+
import { defaultHostForAgentType, normalizeHost } from "./state/client_context.js";
|
|
8
|
+
import { HttpError } from "./client/http.js";
|
|
9
|
+
import { runReadyz } from "./commands/readyz.js";
|
|
10
|
+
import { runBuy } from "./commands/buy.js";
|
|
11
|
+
import { runCatalogList } from "./commands/catalog.js";
|
|
12
|
+
import { runCheckoutPresentation } from "./commands/checkout.js";
|
|
13
|
+
import { runPay } from "./commands/pay.js";
|
|
14
|
+
import { runOrder } from "./commands/order.js";
|
|
15
|
+
import { runListOrders } from "./commands/orders.js";
|
|
16
|
+
import { runRefund } from "./commands/refund.js";
|
|
17
|
+
import { runCartAdd, runCartAddServer, runCartAbandonServer, runCartClear, runCartNext, runCartRemove, runCartRemoveServer, runCartShowServer, } from "./commands/cart.js";
|
|
18
|
+
import { printErrorRecovery } from "./commands/guidance.js";
|
|
19
|
+
import { runDocsList, runDocsShow, runDocsSearch } from "./commands/docs.js";
|
|
20
|
+
import { runInstall } from "./commands/install.js";
|
|
21
|
+
import { collectOption, parseKeyValueList, runServicesAction, runServicesCheckout, runServicesEvents, runServicesGet, runServicesInvoke, runServicesList, runServicesNext, runServicesReadResult, runServicesStart, } from "./commands/services.js";
|
|
22
|
+
const program = new Command();
|
|
23
|
+
program
|
|
24
|
+
.name("itpay")
|
|
25
|
+
.description("V3 ItPay CLI — checkout, payment, order, and refund commands")
|
|
26
|
+
.option("--agent-type <type>", "agent runtime type used for device enrollment and client-specific guidance")
|
|
27
|
+
.version("2.0.0-rc.1");
|
|
28
|
+
function withHost(value) {
|
|
29
|
+
const host = normalizeHost(value);
|
|
30
|
+
if (!host) {
|
|
31
|
+
throw new Error(`invalid --host "${value ?? ""}". Supported: terminal, codex, claude-code, telegram, discord, whatsapp, feishu, lark, plain-chat`);
|
|
32
|
+
}
|
|
33
|
+
return host;
|
|
34
|
+
}
|
|
35
|
+
function parseRequiredContactFields(value) {
|
|
36
|
+
if (!value) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const parsed = value
|
|
40
|
+
.split(",")
|
|
41
|
+
.map((item) => item.trim().toLowerCase())
|
|
42
|
+
.filter((item) => item === "email" || item === "phone");
|
|
43
|
+
return parsed.length > 0 ? parsed : undefined;
|
|
44
|
+
}
|
|
45
|
+
function resolveCheckoutPresentationArgs(input) {
|
|
46
|
+
if (input.requestedCheckoutID && input.requestedDisplayToken) {
|
|
47
|
+
return { checkoutID: input.requestedCheckoutID, displayToken: input.requestedDisplayToken };
|
|
48
|
+
}
|
|
49
|
+
if (input.requestedCheckoutID && !input.requestedDisplayToken) {
|
|
50
|
+
if (input.savedCheckoutID === input.requestedCheckoutID && input.savedDisplayToken) {
|
|
51
|
+
return { checkoutID: input.requestedCheckoutID, displayToken: input.savedDisplayToken };
|
|
52
|
+
}
|
|
53
|
+
const savedHint = input.savedCheckoutID ? ` Saved checkout is ${input.savedCheckoutID}.` : "";
|
|
54
|
+
throw new Error(`display token is required for checkout ${input.requestedCheckoutID}.${savedHint} ` +
|
|
55
|
+
"Pass --token for that checkout or run `itpay checkout` without --id to use the saved checkout.");
|
|
56
|
+
}
|
|
57
|
+
if (input.requestedDisplayToken) {
|
|
58
|
+
if (input.savedCheckoutID) {
|
|
59
|
+
return { checkoutID: input.savedCheckoutID, displayToken: input.requestedDisplayToken };
|
|
60
|
+
}
|
|
61
|
+
throw new Error("checkout id is required when --token is provided and no saved checkout exists");
|
|
62
|
+
}
|
|
63
|
+
if (input.savedCheckoutID && input.savedDisplayToken) {
|
|
64
|
+
return { checkoutID: input.savedCheckoutID, displayToken: input.savedDisplayToken };
|
|
65
|
+
}
|
|
66
|
+
throw new Error("checkout id and display token are required; pass --id/--token or create a checkout first");
|
|
67
|
+
}
|
|
68
|
+
function reportCLIError(error) {
|
|
69
|
+
if (error instanceof HttpError) {
|
|
70
|
+
process.stderr.write(`[${error.status}] ${error.code}: ${error.message}\n`);
|
|
71
|
+
printErrorRecovery(error, (text) => process.stderr.write(text));
|
|
72
|
+
process.exitCode = 1;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (error instanceof Error) {
|
|
76
|
+
process.stderr.write(`${error.message}\n`);
|
|
77
|
+
process.exitCode = 1;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
program
|
|
83
|
+
.command("readyz")
|
|
84
|
+
.description("Probe the V3 backend readiness endpoint")
|
|
85
|
+
.action(async () => {
|
|
86
|
+
await withBackend(async (backend) => runReadyz(backend));
|
|
87
|
+
});
|
|
88
|
+
program
|
|
89
|
+
.command("next")
|
|
90
|
+
.description("Show the next recommended agent action from remembered server handles")
|
|
91
|
+
.option("--json", "output JSON instead of terminal text")
|
|
92
|
+
.action(async (options) => {
|
|
93
|
+
const config = loadConfig();
|
|
94
|
+
const backend = newBackendClient(config);
|
|
95
|
+
const session = CartSession.loadFromFile(cartSessionPath(), config.checkoutCurrency);
|
|
96
|
+
try {
|
|
97
|
+
await runCartNext(backend, session, { jsonOutput: Boolean(options.json) });
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
reportCLIError(error);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
// --- catalog --------------------------------------------------------------
|
|
104
|
+
const catalogCmd = program.command("catalog").description("Browse V3 service catalog");
|
|
105
|
+
catalogCmd
|
|
106
|
+
.command("list")
|
|
107
|
+
.description("List all available services from the published catalog manifest")
|
|
108
|
+
.option("--json", "output JSON instead of terminal text")
|
|
109
|
+
.action(async (options) => {
|
|
110
|
+
await withBackend(async (backend) => runCatalogList(backend, { jsonOutput: Boolean(options.json) }));
|
|
111
|
+
});
|
|
112
|
+
// --- install --------------------------------------------------------------
|
|
113
|
+
const installCmd = program.command("install").description("Show setup instructions for each agent host");
|
|
114
|
+
installCmd
|
|
115
|
+
.argument("[target]", "install target: claude-code, codex, terminal, telegram, feishu")
|
|
116
|
+
.description("Show agent-specific installation and configuration instructions")
|
|
117
|
+
.action((target) => {
|
|
118
|
+
runInstall(target);
|
|
119
|
+
});
|
|
120
|
+
// --- docs -----------------------------------------------------------------
|
|
121
|
+
const docsCmd = program.command("docs").description("Browse agent documentation");
|
|
122
|
+
docsCmd
|
|
123
|
+
.command("list")
|
|
124
|
+
.description("List all available agent doc topics")
|
|
125
|
+
.action(() => {
|
|
126
|
+
runDocsList();
|
|
127
|
+
});
|
|
128
|
+
docsCmd
|
|
129
|
+
.command("show")
|
|
130
|
+
.description("Show a specific doc topic")
|
|
131
|
+
.argument("<topic>", "doc topic name")
|
|
132
|
+
.action((topic) => {
|
|
133
|
+
runDocsShow(topic);
|
|
134
|
+
});
|
|
135
|
+
docsCmd
|
|
136
|
+
.command("search")
|
|
137
|
+
.description("Search doc topics by keyword")
|
|
138
|
+
.argument("<query>", "search query")
|
|
139
|
+
.action((query) => {
|
|
140
|
+
runDocsSearch(query);
|
|
141
|
+
});
|
|
142
|
+
// --- cart ----------------------------------------------------------------
|
|
143
|
+
const cart = program.command("cart").description("V3 canonical server cart");
|
|
144
|
+
cart
|
|
145
|
+
.command("add")
|
|
146
|
+
.description("Add a variant/offer/quantity to the canonical server cart")
|
|
147
|
+
.requiredOption("--item <catalog_item_id>")
|
|
148
|
+
.requiredOption("--variant <catalog_variant_id>")
|
|
149
|
+
.requiredOption("--offer <offer_id>")
|
|
150
|
+
.option("--quantity <n>", "quantity", (value) => Number.parseInt(value, 10), 1)
|
|
151
|
+
.option("--input <json>")
|
|
152
|
+
.option("--host <host>", "client host (terminal, codex, telegram, feishu, lark, ...)", "terminal")
|
|
153
|
+
.option("--target <target>", "chat id / channel id / open id for IM hosts")
|
|
154
|
+
.option("--json", "output JSON instead of terminal text")
|
|
155
|
+
.option("--local", "only add to the local draft cache; not valid for service-backed flows")
|
|
156
|
+
.action(async (options) => {
|
|
157
|
+
const config = loadConfig();
|
|
158
|
+
const sessionPath = cartSessionPath();
|
|
159
|
+
const session = CartSession.loadFromFile(sessionPath, config.checkoutCurrency);
|
|
160
|
+
const addOptions = {
|
|
161
|
+
catalogItemID: options.item,
|
|
162
|
+
catalogVariantID: options.variant,
|
|
163
|
+
offerID: options.offer,
|
|
164
|
+
quantity: options.quantity,
|
|
165
|
+
...(options.input ? { input: JSON.parse(options.input) } : {}),
|
|
166
|
+
};
|
|
167
|
+
try {
|
|
168
|
+
if (options.local) {
|
|
169
|
+
runCartAdd(session, addOptions);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
const backend = newBackendClient(config);
|
|
173
|
+
await runCartAddServer({
|
|
174
|
+
...addOptions,
|
|
175
|
+
backend,
|
|
176
|
+
config,
|
|
177
|
+
session,
|
|
178
|
+
host: withHost(options.host),
|
|
179
|
+
...(options.target ? { target: options.target } : {}),
|
|
180
|
+
jsonOutput: Boolean(options.json),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
reportCLIError(error);
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
session.saveToFile(sessionPath);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
cart
|
|
192
|
+
.command("next")
|
|
193
|
+
.description("Show the next recommended agent action for the remembered server cart")
|
|
194
|
+
.option("--json", "output JSON instead of terminal text")
|
|
195
|
+
.action(async (options) => {
|
|
196
|
+
const config = loadConfig();
|
|
197
|
+
const backend = newBackendClient(config);
|
|
198
|
+
const session = CartSession.loadFromFile(cartSessionPath(), config.checkoutCurrency);
|
|
199
|
+
try {
|
|
200
|
+
await runCartNext(backend, session, { jsonOutput: Boolean(options.json) });
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
reportCLIError(error);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
cart
|
|
207
|
+
.command("remove")
|
|
208
|
+
.description("Remove a line from the canonical server cart")
|
|
209
|
+
.option("--line <cart_item_id>", "server cart item id; defaults to last remembered cart item")
|
|
210
|
+
.option("--variant <catalog_variant_id>", "local draft variant id, only with --local")
|
|
211
|
+
.option("--offer <offer_id>", "local draft offer id, only with --local")
|
|
212
|
+
.option("--local", "only remove from the explicit local draft cache")
|
|
213
|
+
.action(async (options) => {
|
|
214
|
+
const config = loadConfig();
|
|
215
|
+
const sessionPath = cartSessionPath();
|
|
216
|
+
const session = CartSession.loadFromFile(sessionPath, config.checkoutCurrency);
|
|
217
|
+
try {
|
|
218
|
+
if (options.local) {
|
|
219
|
+
if (!options.variant || !options.offer) {
|
|
220
|
+
throw new Error("--local remove requires --variant and --offer");
|
|
221
|
+
}
|
|
222
|
+
runCartRemove(session, {
|
|
223
|
+
catalogVariantID: options.variant,
|
|
224
|
+
offerID: options.offer,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
const backend = newBackendClient(config);
|
|
229
|
+
await runCartRemoveServer(backend, session, options.line);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
reportCLIError(error);
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
session.saveToFile(sessionPath);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
cart
|
|
240
|
+
.command("show")
|
|
241
|
+
.description("Print the canonical server cart or local draft fallback")
|
|
242
|
+
.action(async () => {
|
|
243
|
+
const config = loadConfig();
|
|
244
|
+
const backend = newBackendClient(config);
|
|
245
|
+
const sessionPath = cartSessionPath();
|
|
246
|
+
const session = CartSession.loadFromFile(sessionPath, config.checkoutCurrency);
|
|
247
|
+
try {
|
|
248
|
+
await runCartShowServer(backend, session);
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
reportCLIError(error);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
cart
|
|
255
|
+
.command("clear")
|
|
256
|
+
.description("Abandon the canonical server cart or clear local draft fallback")
|
|
257
|
+
.option("--local", "only clear local handles and explicit local draft")
|
|
258
|
+
.action(async (options) => {
|
|
259
|
+
const config = loadConfig();
|
|
260
|
+
const sessionPath = cartSessionPath();
|
|
261
|
+
const session = CartSession.loadFromFile(sessionPath, config.checkoutCurrency);
|
|
262
|
+
try {
|
|
263
|
+
if (options.local) {
|
|
264
|
+
runCartClear(session);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
const backend = newBackendClient(config);
|
|
268
|
+
await runCartAbandonServer(backend, session);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
reportCLIError(error);
|
|
273
|
+
}
|
|
274
|
+
finally {
|
|
275
|
+
session.saveToFile(sessionPath);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
// --- buy / checkout ------------------------------------------------------
|
|
279
|
+
program
|
|
280
|
+
.command("buy")
|
|
281
|
+
.description("Create a V3 cart and checkout, then render the checkout QR for the host")
|
|
282
|
+
.option("--host <host>", "client host (terminal, telegram, feishu, lark, ...)", "terminal")
|
|
283
|
+
.option("--target <target>", "chat id / channel id / open id for IM hosts")
|
|
284
|
+
.option("--item <catalog_item_id>")
|
|
285
|
+
.option("--variant <catalog_variant_id>")
|
|
286
|
+
.option("--offer <offer_id>")
|
|
287
|
+
.option("--cart <cart_id>", "existing canonical server cart id")
|
|
288
|
+
.option("--quantity <n>", "quantity", (value) => Number.parseInt(value, 10), 1)
|
|
289
|
+
.option("--ref <client_reference_id>")
|
|
290
|
+
.option("--contact-email <email>")
|
|
291
|
+
.option("--contact-phone <phone>")
|
|
292
|
+
.option("--require-contact <fields>", "comma-separated required contact fields: email,phone")
|
|
293
|
+
.option("--qr-format <format>", "unicode|utf8|ansi|terminal")
|
|
294
|
+
.option("--qr-file <path>", "explicit QR file path")
|
|
295
|
+
.option("--pay", "also create a payment intent and optionally wait for verification")
|
|
296
|
+
.option("--method <alipay|wechatpay>", "payment method for --pay", "alipay")
|
|
297
|
+
.option("--no-wait", "do not wait for payment verification after --pay")
|
|
298
|
+
.option("--timeout <seconds>", "max seconds to wait for payment", (value) => Number.parseInt(value, 10), 120)
|
|
299
|
+
.option("--json", "output JSON instead of terminal text")
|
|
300
|
+
.action(async (options) => {
|
|
301
|
+
const config = loadConfig();
|
|
302
|
+
const backend = newBackendClient(config);
|
|
303
|
+
const sessionPath = cartSessionPath();
|
|
304
|
+
const session = CartSession.loadFromFile(sessionPath, config.checkoutCurrency);
|
|
305
|
+
const host = withHost(options.host);
|
|
306
|
+
if (options.item && options.variant && options.offer) {
|
|
307
|
+
runCartAdd(session, {
|
|
308
|
+
catalogItemID: options.item,
|
|
309
|
+
catalogVariantID: options.variant,
|
|
310
|
+
offerID: options.offer,
|
|
311
|
+
quantity: options.quantity,
|
|
312
|
+
});
|
|
313
|
+
session.saveToFile(sessionPath);
|
|
314
|
+
}
|
|
315
|
+
const contact = {};
|
|
316
|
+
if (options.contactEmail)
|
|
317
|
+
contact.email = options.contactEmail;
|
|
318
|
+
if (options.contactPhone)
|
|
319
|
+
contact.phone = options.contactPhone;
|
|
320
|
+
const requiredContactFields = parseRequiredContactFields(options.requireContact);
|
|
321
|
+
const method = options.method === "wechatpay" ? "wechatpay" : "alipay";
|
|
322
|
+
const buyOptions = {
|
|
323
|
+
cartSession: session,
|
|
324
|
+
host,
|
|
325
|
+
...(options.cart ? { cartID: options.cart } : {}),
|
|
326
|
+
...(options.target ? { target: options.target } : {}),
|
|
327
|
+
...(options.ref ? { clientReferenceID: options.ref } : {}),
|
|
328
|
+
...(Object.keys(contact).length > 0 ? { contact } : {}),
|
|
329
|
+
...(requiredContactFields ? { requiredContactFields } : {}),
|
|
330
|
+
...(options.qrFormat ? { qrFormat: options.qrFormat } : {}),
|
|
331
|
+
...(options.qrFile ? { qrFilePath: options.qrFile } : {}),
|
|
332
|
+
...(options.pay ? { pay: true, payMethod: method, noWait: options.wait === false, payTimeoutSec: options.timeout } : {}),
|
|
333
|
+
...(options.json ? { jsonOutput: true } : {}),
|
|
334
|
+
};
|
|
335
|
+
try {
|
|
336
|
+
await runBuy(backend, config, buyOptions);
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
reportCLIError(error);
|
|
340
|
+
}
|
|
341
|
+
finally {
|
|
342
|
+
session.saveToFile(sessionPath);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
program
|
|
346
|
+
.command("checkout")
|
|
347
|
+
.description("Read the canonical V3 checkout presentation by checkout_id + display_token")
|
|
348
|
+
.option("--host <host>", "client host")
|
|
349
|
+
.option("--target <target>")
|
|
350
|
+
.option("--id <checkout_id>")
|
|
351
|
+
.option("--token <display_token>")
|
|
352
|
+
.action(async (options) => {
|
|
353
|
+
const config = loadConfig();
|
|
354
|
+
const host = withHost(options.host ?? defaultHostForAgentType(config.agentType));
|
|
355
|
+
const session = CartSession.loadFromFile(cartSessionPath(), config.checkoutCurrency);
|
|
356
|
+
const snap = session.show();
|
|
357
|
+
const { checkoutID, displayToken } = resolveCheckoutPresentationArgs({
|
|
358
|
+
...(options.id ? { requestedCheckoutID: options.id } : {}),
|
|
359
|
+
...(options.token ? { requestedDisplayToken: options.token } : {}),
|
|
360
|
+
...(snap.lastCheckoutID ? { savedCheckoutID: snap.lastCheckoutID } : {}),
|
|
361
|
+
...(snap.lastDisplayToken ? { savedDisplayToken: snap.lastDisplayToken } : {}),
|
|
362
|
+
});
|
|
363
|
+
const backend = newBackendClient(config);
|
|
364
|
+
try {
|
|
365
|
+
await runCheckoutPresentation(backend, {
|
|
366
|
+
checkoutID,
|
|
367
|
+
displayToken,
|
|
368
|
+
host,
|
|
369
|
+
baseURL: config.baseURL,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
if (error instanceof HttpError && error.status === 404 && snap.lastServiceExecutionID) {
|
|
374
|
+
process.stderr.write(`[${error.status}] ${error.code}: ${error.message}\n`);
|
|
375
|
+
process.stderr.write("recovery:\n");
|
|
376
|
+
process.stderr.write(" - Reissue the existing Service Execution checkout handoff\n");
|
|
377
|
+
process.stderr.write(` itpay services checkout ${snap.lastServiceExecutionID} --resume --json\n`);
|
|
378
|
+
process.exitCode = 1;
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
reportCLIError(error);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
program
|
|
385
|
+
.command("pay")
|
|
386
|
+
.description("Create a V3 payment intent (CLI escape hatch — usually done by the checkout page)")
|
|
387
|
+
.requiredOption("--checkout <checkout_id>")
|
|
388
|
+
.requiredOption("--method <alipay|wechatpay>")
|
|
389
|
+
.option("--provider <name>")
|
|
390
|
+
.option("--buyer <buyer_id>")
|
|
391
|
+
.option("--refresh", "request a fresh provider payment action for the existing intent")
|
|
392
|
+
.action(async (options) => {
|
|
393
|
+
const config = loadConfig();
|
|
394
|
+
const backend = newBackendClient(config);
|
|
395
|
+
const method = options.method === "wechatpay" ? "wechatpay" : "alipay";
|
|
396
|
+
const payOptions = {
|
|
397
|
+
checkoutID: options.checkout,
|
|
398
|
+
method,
|
|
399
|
+
...(options.provider ? { preferredProvider: options.provider } : {}),
|
|
400
|
+
...(options.buyer ? { buyerID: options.buyer } : {}),
|
|
401
|
+
...(options.refresh ? { refreshAction: true } : {}),
|
|
402
|
+
};
|
|
403
|
+
await runPay(backend, config, payOptions);
|
|
404
|
+
});
|
|
405
|
+
program
|
|
406
|
+
.command("order")
|
|
407
|
+
.description("Read a V3 order by id")
|
|
408
|
+
.argument("<order_id>")
|
|
409
|
+
.option("--host <host>", "client host")
|
|
410
|
+
.action(async (orderID, options) => {
|
|
411
|
+
const host = withHost(options.host ?? "terminal");
|
|
412
|
+
await withBackend(async (backend) => runOrder(backend, orderID, { host }));
|
|
413
|
+
});
|
|
414
|
+
program
|
|
415
|
+
.command("orders")
|
|
416
|
+
.description("List V3 orders for the account-scoped bearer session")
|
|
417
|
+
.option("--limit <n>", "max orders", (value) => Number.parseInt(value, 10), 20)
|
|
418
|
+
.option("--status <status>")
|
|
419
|
+
.action(async (options) => {
|
|
420
|
+
const config = loadConfig();
|
|
421
|
+
const backend = newBackendClient(config);
|
|
422
|
+
await runListOrders(backend, config, {
|
|
423
|
+
limit: options.limit,
|
|
424
|
+
status: options.status,
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
program
|
|
428
|
+
.command("refund")
|
|
429
|
+
.description("Create a V3 refund request for an order")
|
|
430
|
+
.requiredOption("--order <order_id>")
|
|
431
|
+
.requiredOption("--payment-intent <payment_intent_id>")
|
|
432
|
+
.requiredOption("--amount-minor <n>", "amount in minor units", (value) => Number.parseInt(value, 10))
|
|
433
|
+
.requiredOption("--currency <currency>")
|
|
434
|
+
.option("--reason <reason>")
|
|
435
|
+
.option("--created-by <created_by>")
|
|
436
|
+
.action(async (options) => {
|
|
437
|
+
const config = loadConfig();
|
|
438
|
+
const backend = newBackendClient(config);
|
|
439
|
+
const refundOptions = {
|
|
440
|
+
orderID: options.order,
|
|
441
|
+
paymentIntentID: options.paymentIntent,
|
|
442
|
+
amountMinor: options.amountMinor,
|
|
443
|
+
currency: options.currency,
|
|
444
|
+
...(options.reason ? { reason: options.reason } : {}),
|
|
445
|
+
...(options.createdBy ? { createdBy: options.createdBy } : {}),
|
|
446
|
+
};
|
|
447
|
+
await runRefund(backend, config, refundOptions);
|
|
448
|
+
});
|
|
449
|
+
// --- service execution ----------------------------------------------------
|
|
450
|
+
const services = program.command("services").description("Generic V3 Service Execution commands");
|
|
451
|
+
services
|
|
452
|
+
.command("start")
|
|
453
|
+
.description("Start a contract-backed service execution")
|
|
454
|
+
.argument("<service_id>")
|
|
455
|
+
.option("--host <host>", "client host")
|
|
456
|
+
.option("--target <target>")
|
|
457
|
+
.option("--buyer <buyer_id>")
|
|
458
|
+
.action(async (serviceID, options) => {
|
|
459
|
+
const config = loadConfig();
|
|
460
|
+
const backend = newBackendClient(config);
|
|
461
|
+
await runServicesStart(backend, config, serviceID, {
|
|
462
|
+
host: withHost(options.host ?? defaultHostForAgentType(config.agentType)),
|
|
463
|
+
...(options.target ? { target: options.target } : {}),
|
|
464
|
+
...(options.buyer ? { buyerID: options.buyer } : {}),
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
services
|
|
468
|
+
.command("invoke")
|
|
469
|
+
.description("Invoke an agent-visible service capability")
|
|
470
|
+
.argument("<service_execution_id>")
|
|
471
|
+
.requiredOption("--capability <capability_id>")
|
|
472
|
+
.option("--input <key=value>", "redacted input summary", collectOption, [])
|
|
473
|
+
.option("--json", "output JSON")
|
|
474
|
+
.action(async (serviceExecutionID, options) => {
|
|
475
|
+
const config = loadConfig();
|
|
476
|
+
const backend = newBackendClient(config);
|
|
477
|
+
await runServicesInvoke(backend, config, serviceExecutionID, options.capability, parseKeyValueList(options.input), { jsonOutput: Boolean(options.json) });
|
|
478
|
+
});
|
|
479
|
+
services
|
|
480
|
+
.command("action")
|
|
481
|
+
.description("Record a service execution action or human handoff result")
|
|
482
|
+
.argument("<service_execution_id>")
|
|
483
|
+
.requiredOption("--action <action_type>")
|
|
484
|
+
.option("--actor-type <actor_type>")
|
|
485
|
+
.option("--actor-id <actor_id>")
|
|
486
|
+
.option("--status <status>", "pending, approved, rejected, expired, or cancelled")
|
|
487
|
+
.option("--candidate <rank>", "select a displayed candidate by its rank", Number)
|
|
488
|
+
.option("--result-item <service_capability_result_item_id>")
|
|
489
|
+
.option("--selected-candidate-hash <hash>")
|
|
490
|
+
.option("--required-before <step>")
|
|
491
|
+
.option("--input <key=value>", "action input snapshot", collectOption, [])
|
|
492
|
+
.action(async (serviceExecutionID, options) => {
|
|
493
|
+
const config = loadConfig();
|
|
494
|
+
const backend = newBackendClient(config);
|
|
495
|
+
await runServicesAction(backend, serviceExecutionID, options.action, parseKeyValueList(options.input), {
|
|
496
|
+
...(options.actorType ? { actorType: options.actorType } : {}),
|
|
497
|
+
...(options.actorId ? { actorID: options.actorId } : {}),
|
|
498
|
+
...(options.status ? { status: options.status } : {}),
|
|
499
|
+
...(options.candidate !== undefined ? { candidateRank: options.candidate } : {}),
|
|
500
|
+
...(options.resultItem ? { resultItemID: options.resultItem } : {}),
|
|
501
|
+
...(options.selectedCandidateHash ? { selectedCandidateHash: options.selectedCandidateHash } : {}),
|
|
502
|
+
...(options.requiredBefore ? { requiredBefore: options.requiredBefore } : {}),
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
services
|
|
506
|
+
.command("checkout")
|
|
507
|
+
.description("Create checkout from a service execution and render the ItPay checkout handoff")
|
|
508
|
+
.argument("<service_execution_id>")
|
|
509
|
+
.option("--capability <capability_id>")
|
|
510
|
+
.option("--email <delivery_email>")
|
|
511
|
+
.option("--resume", "reissue the existing checkout handoff without creating another checkout")
|
|
512
|
+
.option("--host <host>", "client host (terminal, codex, telegram, feishu, lark, ...)")
|
|
513
|
+
.option("--target <target>", "chat id / channel id / open id for IM hosts")
|
|
514
|
+
.option("--qr-format <format>", "unicode|utf8|ansi|terminal")
|
|
515
|
+
.option("--qr-file <path>", "explicit QR file path")
|
|
516
|
+
.option("--json", "output JSON instead of terminal text")
|
|
517
|
+
.action(async (serviceExecutionID, options) => {
|
|
518
|
+
const config = loadConfig();
|
|
519
|
+
const backend = newBackendClient(config);
|
|
520
|
+
const sessionPath = cartSessionPath();
|
|
521
|
+
const session = CartSession.loadFromFile(sessionPath, config.checkoutCurrency);
|
|
522
|
+
await runServicesCheckout(backend, config, serviceExecutionID, options.capability, {
|
|
523
|
+
...(options.email ? { email: options.email } : {}),
|
|
524
|
+
resume: Boolean(options.resume),
|
|
525
|
+
host: withHost(options.host ?? defaultHostForAgentType(config.agentType)),
|
|
526
|
+
...(options.target ? { target: options.target } : {}),
|
|
527
|
+
...(options.qrFormat ? { qrFormat: options.qrFormat } : {}),
|
|
528
|
+
...(options.qrFile ? { qrFilePath: options.qrFile } : {}),
|
|
529
|
+
jsonOutput: Boolean(options.json),
|
|
530
|
+
persistHandoff: (handoff) => {
|
|
531
|
+
session.rememberCheckout({
|
|
532
|
+
cartID: handoff.cartID,
|
|
533
|
+
checkoutID: handoff.checkoutID,
|
|
534
|
+
displayToken: handoff.displayToken,
|
|
535
|
+
checkoutURL: handoff.checkoutURL,
|
|
536
|
+
serviceExecutionID: handoff.serviceExecutionID,
|
|
537
|
+
});
|
|
538
|
+
session.saveToFile(sessionPath);
|
|
539
|
+
},
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
services
|
|
543
|
+
.command("list")
|
|
544
|
+
.description("Recover service executions visible to this enrolled device or account")
|
|
545
|
+
.option("--limit <number>", "maximum executions", "50")
|
|
546
|
+
.option("--json", "output compact JSON")
|
|
547
|
+
.action(async (options) => {
|
|
548
|
+
const config = loadConfig();
|
|
549
|
+
const backend = newBackendClient(config);
|
|
550
|
+
await runServicesList(backend, { limit: Number.parseInt(options.limit, 10), jsonOutput: Boolean(options.json) });
|
|
551
|
+
});
|
|
552
|
+
services
|
|
553
|
+
.command("get")
|
|
554
|
+
.description("Read a service execution timeline")
|
|
555
|
+
.argument("<service_execution_id>")
|
|
556
|
+
.option("--json", "output compact JSON")
|
|
557
|
+
.action(async (serviceExecutionID, options) => {
|
|
558
|
+
const config = loadConfig();
|
|
559
|
+
const backend = newBackendClient(config);
|
|
560
|
+
await runServicesGet(backend, serviceExecutionID, { jsonOutput: Boolean(options.json) });
|
|
561
|
+
});
|
|
562
|
+
services
|
|
563
|
+
.command("next")
|
|
564
|
+
.description("Show the next recommended agent action for a Service Execution")
|
|
565
|
+
.argument("<service_execution_id>")
|
|
566
|
+
.option("--json", "output JSON instead of terminal text")
|
|
567
|
+
.action(async (serviceExecutionID, options) => {
|
|
568
|
+
const config = loadConfig();
|
|
569
|
+
const backend = newBackendClient(config);
|
|
570
|
+
await runServicesNext(backend, serviceExecutionID, { jsonOutput: Boolean(options.json) });
|
|
571
|
+
});
|
|
572
|
+
services
|
|
573
|
+
.command("read-result")
|
|
574
|
+
.description("Read a human-granted service result for this agent")
|
|
575
|
+
.argument("<service_execution_id>")
|
|
576
|
+
.action(async (serviceExecutionID) => {
|
|
577
|
+
const config = loadConfig();
|
|
578
|
+
const backend = newBackendClient(config);
|
|
579
|
+
await runServicesReadResult(backend, serviceExecutionID);
|
|
580
|
+
});
|
|
581
|
+
services
|
|
582
|
+
.command("events")
|
|
583
|
+
.description("List redacted service execution events")
|
|
584
|
+
.argument("<service_execution_id>")
|
|
585
|
+
.action(async (serviceExecutionID) => {
|
|
586
|
+
const config = loadConfig();
|
|
587
|
+
const backend = newBackendClient(config);
|
|
588
|
+
await runServicesEvents(backend, serviceExecutionID);
|
|
589
|
+
});
|
|
590
|
+
async function withBackend(action) {
|
|
591
|
+
const config = loadConfig();
|
|
592
|
+
const backend = newBackendClient(config);
|
|
593
|
+
try {
|
|
594
|
+
await action(backend);
|
|
595
|
+
}
|
|
596
|
+
catch (error) {
|
|
597
|
+
if (error instanceof HttpError) {
|
|
598
|
+
reportCLIError(error);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
throw error;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
program.parseAsync(process.argv).catch((error) => {
|
|
605
|
+
reportCLIError(error);
|
|
606
|
+
});
|