@itpay/cli 2.0.3 → 2.0.7
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 +96 -142
- package/dist/src/client/backend.js +26 -8
- package/dist/src/client/http.js +29 -23
- package/dist/src/commands/buy.js +84 -132
- package/dist/src/commands/cart.js +274 -169
- package/dist/src/commands/catalog.js +64 -38
- package/dist/src/commands/checkout.js +128 -79
- package/dist/src/commands/docs.js +97 -51
- package/dist/src/commands/guidance.js +112 -16
- package/dist/src/commands/install.js +50 -87
- package/dist/src/commands/next.js +45 -0
- package/dist/src/commands/order.js +44 -69
- package/dist/src/commands/orders.js +43 -15
- package/dist/src/commands/pay.js +51 -22
- package/dist/src/commands/readyz.js +8 -4
- package/dist/src/commands/refund.js +132 -11
- package/dist/src/commands/services.js +799 -147
- package/dist/src/commands/skill.js +55 -0
- package/dist/src/main.js +820 -193
- package/dist/src/render/output.js +2 -3
- package/dist/src/state/agent_type.js +19 -0
- package/dist/src/state/cart_session.js +13 -17
- package/dist/src/state/client_context.js +4 -2
- package/dist/src/state/config.js +5 -15
- package/dist/src/state/device_authority.js +175 -57
- package/docs/agent/buyer/cart-checkout.json +27 -83
- package/docs/agent/buyer/catalog-list.json +2 -1
- package/docs/agent/buyer/identity-and-sessions.json +64 -0
- package/docs/agent/buyer/install-and-setup.json +35 -65
- package/docs/agent/buyer/orders-refunds.json +31 -53
- package/docs/agent/buyer/payment-flow.json +28 -57
- package/docs/agent/buyer/quickstart.json +46 -161
- package/docs/agent/buyer/render-hosts.json +43 -57
- package/docs/cli-reference/agent-types.md +51 -0
- package/docs/cli-reference/commands/buy.md +167 -0
- package/docs/cli-reference/commands/cart/add.md +86 -0
- package/docs/cli-reference/commands/cart/clear.md +53 -0
- package/docs/cli-reference/commands/cart/index.md +30 -0
- package/docs/cli-reference/commands/cart/next.md +71 -0
- package/docs/cli-reference/commands/cart/remove.md +53 -0
- package/docs/cli-reference/commands/cart/show.md +65 -0
- package/docs/cli-reference/commands/catalog/index.md +26 -0
- package/docs/cli-reference/commands/catalog/list.md +45 -0
- package/docs/cli-reference/commands/checkout.md +74 -0
- package/docs/cli-reference/commands/device.md +13 -0
- package/docs/cli-reference/commands/docs/index.md +28 -0
- package/docs/cli-reference/commands/docs/list.md +51 -0
- package/docs/cli-reference/commands/docs/search.md +69 -0
- package/docs/cli-reference/commands/docs/show.md +68 -0
- package/docs/cli-reference/commands/install.md +114 -0
- package/docs/cli-reference/commands/next.md +87 -0
- package/docs/cli-reference/commands/order.md +92 -0
- package/docs/cli-reference/commands/orders.md +83 -0
- package/docs/cli-reference/commands/pay.md +103 -0
- package/docs/cli-reference/commands/readyz.md +38 -0
- package/docs/cli-reference/commands/refund/cancel.md +62 -0
- package/docs/cli-reference/commands/refund/create.md +85 -0
- package/docs/cli-reference/commands/refund/get.md +60 -0
- package/docs/cli-reference/commands/refund/index.md +33 -0
- package/docs/cli-reference/commands/refund/list.md +68 -0
- package/docs/cli-reference/commands/refund/watch.md +73 -0
- package/docs/cli-reference/commands/services/action.md +48 -0
- package/docs/cli-reference/commands/services/checkout.md +82 -0
- package/docs/cli-reference/commands/services/events.md +73 -0
- package/docs/cli-reference/commands/services/get.md +66 -0
- package/docs/cli-reference/commands/services/index.md +45 -0
- package/docs/cli-reference/commands/services/invoke.md +67 -0
- package/docs/cli-reference/commands/services/list.md +61 -0
- package/docs/cli-reference/commands/services/next.md +181 -0
- package/docs/cli-reference/commands/services/quote.md +63 -0
- package/docs/cli-reference/commands/services/read-result.md +98 -0
- package/docs/cli-reference/commands/services/start.md +55 -0
- package/docs/cli-reference/commands/skill.md +17 -0
- package/docs/cli-reference/conventions.md +97 -0
- package/docs/cli-reference/index.md +65 -0
- package/package.json +1 -1
- package/skills/itpay-buyer/SKILL.md +71 -110
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// CLI cart commands. Server cart is canonical for agent purchase/free-trial
|
|
2
2
|
// flows; local cart helpers are kept for explicit local draft compatibility.
|
|
3
|
-
import {
|
|
3
|
+
import { formatMoney } from "../render/output.js";
|
|
4
4
|
import { resolveOutput } from "../render/sink.js";
|
|
5
|
-
import {
|
|
5
|
+
import { writeCommandEnvelope } from "./guidance.js";
|
|
6
6
|
export function runCartAdd(session, options) {
|
|
7
7
|
const out = resolveOutput(options.output);
|
|
8
8
|
const item = {
|
|
@@ -13,11 +13,25 @@ export function runCartAdd(session, options) {
|
|
|
13
13
|
...(options.input ? { input: options.input } : {}),
|
|
14
14
|
};
|
|
15
15
|
session.add(item);
|
|
16
|
-
|
|
16
|
+
const envelope = {
|
|
17
|
+
status: "added_local",
|
|
18
|
+
result: {
|
|
19
|
+
catalog_item_id: item.catalogItemID,
|
|
20
|
+
catalog_variant_id: item.catalogVariantID,
|
|
21
|
+
offer_id: item.offerID,
|
|
22
|
+
quantity: item.quantity,
|
|
23
|
+
},
|
|
24
|
+
instruction: "仅写入本地兼容草稿,未验证目录、价格或服务合同;不要把它当作 canonical Cart。",
|
|
25
|
+
next: { command: "itpay cart show --local", reason: "检查本地草稿" },
|
|
26
|
+
recovery: [],
|
|
27
|
+
};
|
|
28
|
+
writeCommandEnvelope(envelope, {
|
|
29
|
+
...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
|
|
30
|
+
output: out,
|
|
31
|
+
});
|
|
17
32
|
}
|
|
18
33
|
export async function runCartAddServer(options) {
|
|
19
34
|
const out = resolveOutput(options.output);
|
|
20
|
-
const agentDeviceID = options.session.ensureAgentDeviceID(options.config.agentDeviceID);
|
|
21
35
|
const item = {
|
|
22
36
|
catalog_item_id: options.catalogItemID,
|
|
23
37
|
catalog_variant_id: options.catalogVariantID,
|
|
@@ -28,17 +42,14 @@ export async function runCartAddServer(options) {
|
|
|
28
42
|
const clientContext = {
|
|
29
43
|
host: options.host,
|
|
30
44
|
...(options.target ? { target: options.target } : {}),
|
|
31
|
-
agent_device_id: agentDeviceID,
|
|
32
45
|
};
|
|
33
46
|
const cart = options.session.lastCartID
|
|
34
47
|
? await options.backend.addCartItem(options.session.lastCartID, {
|
|
35
48
|
...item,
|
|
36
|
-
agent_device_id: agentDeviceID,
|
|
37
49
|
client_context: clientContext,
|
|
38
50
|
})
|
|
39
51
|
: await options.backend.createCart({
|
|
40
52
|
currency: options.config.checkoutCurrency,
|
|
41
|
-
agent_device_id: agentDeviceID,
|
|
42
53
|
client_context: clientContext,
|
|
43
54
|
items: [item],
|
|
44
55
|
});
|
|
@@ -47,29 +58,99 @@ export async function runCartAddServer(options) {
|
|
|
47
58
|
cartID: cart.cart_id,
|
|
48
59
|
...(line?.cart_item_id ? { cartItemID: line.cart_item_id } : {}),
|
|
49
60
|
...(line?.service_execution_id ? { serviceExecutionID: line.service_execution_id } : {}),
|
|
50
|
-
agentDeviceID,
|
|
51
61
|
});
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
: undefined;
|
|
55
|
-
const guidance = buildCartGuidance(cart, serviceModel);
|
|
56
|
-
if (options.jsonOutput) {
|
|
57
|
-
out(JSON.stringify(attachAgentGuidance(cart, guidance), null, 2) + "\n");
|
|
62
|
+
if (!line?.cart_item_id) {
|
|
63
|
+
throw new Error("backend did not return the added cart item");
|
|
58
64
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
const serviceExecutionID = line.service_execution_id;
|
|
66
|
+
const envelope = {
|
|
67
|
+
status: "added",
|
|
68
|
+
result: {
|
|
69
|
+
cart_id: cart.cart_id,
|
|
70
|
+
cart_item_id: line.cart_item_id,
|
|
71
|
+
...(serviceExecutionID ? { service_execution_id: serviceExecutionID } : {}),
|
|
72
|
+
title: line.title,
|
|
73
|
+
amount: formatMoney(line.amount_minor, line.currency),
|
|
74
|
+
},
|
|
75
|
+
instruction: serviceExecutionID
|
|
76
|
+
? "服务型项目已创建 Service Execution;先读取其当前步骤,不要直接进入普通 buy。"
|
|
77
|
+
: "普通项目已加入 canonical Cart;先检查购物车,再创建 Checkout。",
|
|
78
|
+
next: serviceExecutionID
|
|
79
|
+
? { command: `itpay services next ${serviceExecutionID} --json`, reason: "读取服务执行的当前步骤" }
|
|
80
|
+
: { command: "itpay cart next --json", reason: "检查 canonical Cart" },
|
|
81
|
+
recovery: [],
|
|
82
|
+
};
|
|
83
|
+
writeCommandEnvelope(envelope, {
|
|
84
|
+
...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
|
|
85
|
+
output: out,
|
|
86
|
+
plainResult: Object.entries(envelope.result).map(([key, value]) => `${key}: ${String(value)}`),
|
|
87
|
+
});
|
|
88
|
+
return cart;
|
|
89
|
+
}
|
|
90
|
+
export async function runCartAddQuoteServer(options) {
|
|
91
|
+
const out = resolveOutput(options.output);
|
|
92
|
+
const clientContext = {
|
|
93
|
+
host: options.host,
|
|
94
|
+
...(options.target ? { target: options.target } : {}),
|
|
95
|
+
};
|
|
96
|
+
const quoteItem = { service_quote_lock_id: options.serviceQuoteLockID };
|
|
97
|
+
const cart = options.session.lastCartID
|
|
98
|
+
? await options.backend.addCartItem(options.session.lastCartID, {
|
|
99
|
+
...quoteItem,
|
|
100
|
+
client_context: clientContext,
|
|
101
|
+
})
|
|
102
|
+
: await options.backend.createCart({
|
|
103
|
+
currency: options.config.checkoutCurrency,
|
|
104
|
+
client_context: clientContext,
|
|
105
|
+
items: [quoteItem],
|
|
106
|
+
});
|
|
107
|
+
const line = cart.items[cart.items.length - 1];
|
|
108
|
+
if (!line?.cart_item_id) {
|
|
109
|
+
throw new Error("backend did not return the added cart item");
|
|
66
110
|
}
|
|
111
|
+
options.session.rememberServerCart({
|
|
112
|
+
cartID: cart.cart_id,
|
|
113
|
+
cartItemID: line.cart_item_id,
|
|
114
|
+
...(line.service_execution_id ? { serviceExecutionID: line.service_execution_id } : {}),
|
|
115
|
+
});
|
|
116
|
+
const envelope = {
|
|
117
|
+
status: "quote_added",
|
|
118
|
+
result: {
|
|
119
|
+
cart_id: cart.cart_id,
|
|
120
|
+
item_count: cart.items.length,
|
|
121
|
+
total: formatMoney(cart.amount_minor, cart.currency),
|
|
122
|
+
},
|
|
123
|
+
instruction: "付费服务报价已加入同一 Cart;每个项目仍保持独立 Execution 和交付。",
|
|
124
|
+
next: { command: `itpay buy --cart ${cart.cart_id} --json`, reason: "确认项目齐全后创建一次合并付款" },
|
|
125
|
+
recovery: [{ command: "itpay cart show --json", reason: "检查当前合并 Cart" }],
|
|
126
|
+
};
|
|
127
|
+
writeCommandEnvelope(envelope, {
|
|
128
|
+
...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
|
|
129
|
+
output: out,
|
|
130
|
+
plainResult: [
|
|
131
|
+
`cart_id: ${cart.cart_id}`,
|
|
132
|
+
`item_count: ${cart.items.length}`,
|
|
133
|
+
`total: ${formatMoney(cart.amount_minor, cart.currency)}`,
|
|
134
|
+
],
|
|
135
|
+
});
|
|
67
136
|
return cart;
|
|
68
137
|
}
|
|
69
138
|
export function runCartRemove(session, options) {
|
|
70
139
|
const out = resolveOutput(options.output);
|
|
71
140
|
session.remove(options.catalogVariantID, options.offerID);
|
|
72
|
-
|
|
141
|
+
writeCommandEnvelope({
|
|
142
|
+
status: "removed_local",
|
|
143
|
+
result: {
|
|
144
|
+
catalog_variant_id: options.catalogVariantID,
|
|
145
|
+
offer_id: options.offerID,
|
|
146
|
+
},
|
|
147
|
+
instruction: "本地兼容草稿已更新;这不代表任何 canonical Cart 或 Service Execution 已变更。",
|
|
148
|
+
next: { command: "itpay cart show --local --json", reason: "检查剩余本地草稿" },
|
|
149
|
+
recovery: [],
|
|
150
|
+
}, {
|
|
151
|
+
...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
|
|
152
|
+
output: out,
|
|
153
|
+
});
|
|
73
154
|
}
|
|
74
155
|
export async function runCartRemoveServer(backend, session, cartItemID, options = {}) {
|
|
75
156
|
const out = resolveOutput(options.output);
|
|
@@ -82,183 +163,207 @@ export async function runCartRemoveServer(backend, session, cartItemID, options
|
|
|
82
163
|
throw new Error("missing cart item id; pass --line <cart_item_id>");
|
|
83
164
|
}
|
|
84
165
|
const cart = await backend.removeCartItem(cartID, lineID);
|
|
85
|
-
|
|
86
|
-
|
|
166
|
+
const latestLine = cart.items[cart.items.length - 1];
|
|
167
|
+
session.rememberServerCart({
|
|
168
|
+
cartID: cart.cart_id,
|
|
169
|
+
...(latestLine?.cart_item_id ? { cartItemID: latestLine.cart_item_id } : {}),
|
|
170
|
+
...(latestLine?.service_execution_id ? { serviceExecutionID: latestLine.service_execution_id } : {}),
|
|
171
|
+
});
|
|
172
|
+
const envelope = {
|
|
173
|
+
status: "removed",
|
|
174
|
+
result: {
|
|
175
|
+
cart_id: cart.cart_id,
|
|
176
|
+
cart_item_id: lineID,
|
|
177
|
+
remaining_item_count: cart.items.length,
|
|
178
|
+
},
|
|
179
|
+
instruction: "canonical Cart 已更新;被删除的最后一个 service-backed line 对应执行会由服务端一致性事务取消。",
|
|
180
|
+
next: { command: "itpay cart next --json", reason: "检查剩余内容" },
|
|
181
|
+
recovery: [],
|
|
182
|
+
};
|
|
183
|
+
writeCommandEnvelope(envelope, {
|
|
184
|
+
...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
|
|
185
|
+
output: out,
|
|
186
|
+
});
|
|
87
187
|
return cart;
|
|
88
188
|
}
|
|
89
189
|
export function runCartShow(session, options = {}) {
|
|
90
|
-
const out = resolveOutput(options.output);
|
|
91
190
|
const snap = session.show();
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
191
|
+
const items = snap.items.map((item) => ({
|
|
192
|
+
catalog_item_id: item.catalogItemID,
|
|
193
|
+
catalog_variant_id: item.catalogVariantID,
|
|
194
|
+
offer_id: item.offerID,
|
|
195
|
+
quantity: item.quantity,
|
|
196
|
+
}));
|
|
197
|
+
const envelope = {
|
|
198
|
+
status: items.length > 0 ? "shown_local" : "local_empty",
|
|
199
|
+
result: { currency: snap.currency, items },
|
|
200
|
+
instruction: items.length > 0
|
|
201
|
+
? "这是未验证的本地兼容草稿,只能用于明确的普通商品流程;不要把它当作 canonical Cart。"
|
|
202
|
+
: "本地兼容草稿为空;从已发布目录重新选择项目。",
|
|
203
|
+
next: items.length > 0
|
|
204
|
+
? { command: "itpay buy --json", reason: "将普通本地草稿提交为 canonical Cart" }
|
|
205
|
+
: { command: "itpay catalog list --json", reason: "读取已发布目录" },
|
|
206
|
+
recovery: [],
|
|
207
|
+
};
|
|
208
|
+
writeCommandEnvelope(envelope, {
|
|
209
|
+
...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
|
|
210
|
+
...(options.output ? { output: options.output } : {}),
|
|
211
|
+
});
|
|
108
212
|
}
|
|
109
213
|
export async function runCartShowServer(backend, session, options = {}) {
|
|
110
|
-
const out = resolveOutput(options.output);
|
|
111
214
|
if (!session.lastCartID) {
|
|
112
|
-
|
|
215
|
+
writeCommandEnvelope({
|
|
216
|
+
status: "cart_handle_missing",
|
|
217
|
+
result: {},
|
|
218
|
+
instruction: "本地没有 canonical Cart 句柄;不要把本地草稿默认为服务端 Cart。",
|
|
219
|
+
next: { command: "itpay catalog list --json", reason: "读取已发布目录" },
|
|
220
|
+
recovery: [{ command: "itpay cart show --local --json", reason: "仅在明确需要时检查本地兼容草稿" }],
|
|
221
|
+
}, {
|
|
222
|
+
...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
|
|
223
|
+
...(options.output ? { output: options.output } : {}),
|
|
224
|
+
});
|
|
113
225
|
return undefined;
|
|
114
226
|
}
|
|
115
227
|
const cart = await backend.getCart(session.lastCartID);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
228
|
+
const items = cart.items.map((item) => ({
|
|
229
|
+
...(item.cart_item_id ? { cart_item_id: item.cart_item_id } : {}),
|
|
230
|
+
title: item.title,
|
|
231
|
+
quantity: item.quantity,
|
|
232
|
+
...(item.service_execution_id ? { service_execution_id: item.service_execution_id } : {}),
|
|
233
|
+
}));
|
|
234
|
+
const envelope = {
|
|
235
|
+
status: "shown",
|
|
236
|
+
result: {
|
|
237
|
+
cart_id: cart.cart_id,
|
|
238
|
+
status: cart.status,
|
|
239
|
+
amount: formatMoney(cart.amount_minor, cart.currency),
|
|
240
|
+
items,
|
|
241
|
+
},
|
|
242
|
+
instruction: items.length > 0
|
|
243
|
+
? "使用 line 或 execution 句柄继续;不要使用内部 quote lock ID。"
|
|
244
|
+
: "canonical Cart 当前为空;从已发布目录选择项目。",
|
|
245
|
+
next: items.length > 0
|
|
246
|
+
? { command: "itpay cart next --json", reason: "取得当前首选动作" }
|
|
247
|
+
: { command: "itpay catalog list --json", reason: "读取已发布目录" },
|
|
248
|
+
recovery: [],
|
|
249
|
+
};
|
|
250
|
+
writeCommandEnvelope(envelope, {
|
|
251
|
+
...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
|
|
252
|
+
...(options.output ? { output: options.output } : {}),
|
|
253
|
+
plainResult: [
|
|
254
|
+
`cart_id: ${cart.cart_id}`,
|
|
255
|
+
`state: ${cart.status}`,
|
|
256
|
+
`amount: ${formatMoney(cart.amount_minor, cart.currency)}`,
|
|
257
|
+
...items.map((item) => `${item.cart_item_id ?? "line"}: ${item.title} x${item.quantity}${item.service_execution_id ? ` service_execution=${item.service_execution_id}` : ""}`),
|
|
258
|
+
],
|
|
259
|
+
});
|
|
133
260
|
return cart;
|
|
134
261
|
}
|
|
135
262
|
export function runCartClear(session, options = {}) {
|
|
136
|
-
const out = resolveOutput(options.output);
|
|
137
263
|
session.clear();
|
|
138
|
-
|
|
264
|
+
const envelope = {
|
|
265
|
+
status: "cleared_local",
|
|
266
|
+
result: { server_abandoned: false, local_state_cleared: true },
|
|
267
|
+
instruction: "仅清除了本地草稿和恢复句柄;Backend Cart、Checkout 和 Service Execution 均未改变。",
|
|
268
|
+
next: { command: "itpay catalog list --json", reason: "仅在用户提出新需求时重新选择" },
|
|
269
|
+
recovery: [],
|
|
270
|
+
};
|
|
271
|
+
writeCommandEnvelope(envelope, {
|
|
272
|
+
...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
|
|
273
|
+
...(options.output ? { output: options.output } : {}),
|
|
274
|
+
});
|
|
139
275
|
}
|
|
140
276
|
export async function runCartAbandonServer(backend, session, options = {}) {
|
|
141
|
-
const out = resolveOutput(options.output);
|
|
142
277
|
if (!session.lastCartID) {
|
|
143
|
-
|
|
278
|
+
writeCommandEnvelope({
|
|
279
|
+
status: "cart_handle_missing",
|
|
280
|
+
result: {},
|
|
281
|
+
instruction: "本地没有 canonical Cart 句柄;未修改任何 Backend 或本地资源。",
|
|
282
|
+
next: { command: "itpay next --json", reason: "检查其他可恢复句柄" },
|
|
283
|
+
recovery: [{ command: "itpay cart clear --local --json", reason: "仅在明确放弃本地草稿和句柄时执行" }],
|
|
284
|
+
}, {
|
|
285
|
+
...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
|
|
286
|
+
...(options.output ? { output: options.output } : {}),
|
|
287
|
+
});
|
|
144
288
|
return undefined;
|
|
145
289
|
}
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
cart = await backend.abandonCart(session.lastCartID);
|
|
149
|
-
}
|
|
150
|
-
catch (error) {
|
|
151
|
-
if (error instanceof HttpError && (error.code === "cart_item_locked" || error.status === 409)) {
|
|
152
|
-
const cartID = session.lastCartID;
|
|
153
|
-
session.clear();
|
|
154
|
-
out(`cart local handle cleared; server cart ${cartID} is locked by quote/checkout\n`);
|
|
155
|
-
return undefined;
|
|
156
|
-
}
|
|
157
|
-
throw error;
|
|
158
|
-
}
|
|
290
|
+
const cart = await backend.abandonCart(session.lastCartID);
|
|
159
291
|
session.clear();
|
|
160
|
-
|
|
292
|
+
const envelope = {
|
|
293
|
+
status: "abandoned",
|
|
294
|
+
result: { cart_id: cart.cart_id, server_abandoned: true },
|
|
295
|
+
instruction: "canonical Cart 已放弃;Backend 已在同一事务中软删除 active lines,并取消其未付款 Service Execution。",
|
|
296
|
+
next: { command: "itpay catalog list --json", reason: "仅在用户提出新需求时重新选择" },
|
|
297
|
+
recovery: [],
|
|
298
|
+
};
|
|
299
|
+
writeCommandEnvelope(envelope, {
|
|
300
|
+
...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
|
|
301
|
+
...(options.output ? { output: options.output } : {}),
|
|
302
|
+
plainResult: [`cart_id: ${cart.cart_id}`, "server_abandoned: true"],
|
|
303
|
+
});
|
|
161
304
|
return cart;
|
|
162
305
|
}
|
|
163
306
|
export async function runCartNext(backend, session, options = {}) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
printAgentGuidance(guidance, options.output);
|
|
173
|
-
}
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
const snap = session.show();
|
|
177
|
-
if (snap.lastServiceExecutionID) {
|
|
178
|
-
const model = await getServiceReadModel(backend, snap.lastServiceExecutionID);
|
|
179
|
-
const guidance = model
|
|
180
|
-
? buildCartGuidance({
|
|
181
|
-
cart_id: snap.lastCartID ?? "<unknown_cart>",
|
|
182
|
-
status: "unknown",
|
|
183
|
-
amount_minor: 0,
|
|
184
|
-
currency: snap.currency,
|
|
185
|
-
items: [{
|
|
186
|
-
title: "Service Execution",
|
|
187
|
-
quantity: 1,
|
|
188
|
-
amount_minor: 0,
|
|
189
|
-
currency: snap.currency,
|
|
190
|
-
service_execution_id: snap.lastServiceExecutionID,
|
|
191
|
-
}],
|
|
192
|
-
}, model)
|
|
193
|
-
: buildCartGuidance({
|
|
194
|
-
cart_id: snap.lastCartID ?? "<unknown_cart>",
|
|
195
|
-
status: "unknown",
|
|
196
|
-
amount_minor: 0,
|
|
197
|
-
currency: snap.currency,
|
|
198
|
-
items: [{
|
|
199
|
-
title: "Service Execution",
|
|
200
|
-
quantity: 1,
|
|
201
|
-
amount_minor: 0,
|
|
202
|
-
currency: snap.currency,
|
|
203
|
-
service_execution_id: snap.lastServiceExecutionID,
|
|
204
|
-
}],
|
|
205
|
-
});
|
|
206
|
-
if (options.jsonOutput) {
|
|
207
|
-
out(JSON.stringify(guidance, null, 2) + "\n");
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
printAgentGuidance(guidance, options.output);
|
|
211
|
-
}
|
|
307
|
+
if (!session.lastCartID) {
|
|
308
|
+
writeCartNextEnvelope({
|
|
309
|
+
status: "cart_handle_missing",
|
|
310
|
+
result: {},
|
|
311
|
+
instruction: "本地没有 canonical Cart 句柄;先恢复已有资源,不要创建重复 Cart。",
|
|
312
|
+
next: { command: "itpay next --json", reason: "检查其他可恢复句柄" },
|
|
313
|
+
recovery: [{ command: "itpay services list --json", reason: "从服务端恢复当前设备的执行" }],
|
|
314
|
+
}, options);
|
|
212
315
|
return;
|
|
213
316
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
317
|
+
const cart = await backend.getCart(session.lastCartID);
|
|
318
|
+
const unquotedServiceLine = [...cart.items].reverse().find((item) => item.service_execution_id && !item.service_quote_lock_id);
|
|
319
|
+
let envelope;
|
|
320
|
+
if (unquotedServiceLine?.service_execution_id) {
|
|
321
|
+
envelope = {
|
|
322
|
+
status: "action_available",
|
|
323
|
+
result: {
|
|
324
|
+
cart_id: cart.cart_id,
|
|
325
|
+
cart_status: cart.status,
|
|
326
|
+
service_execution_id: unquotedServiceLine.service_execution_id,
|
|
327
|
+
},
|
|
328
|
+
instruction: "该 Cart 包含 service-backed line;继续 Service Execution,不要从 Cart 猜 capability。",
|
|
329
|
+
next: {
|
|
330
|
+
command: `itpay services next ${unquotedServiceLine.service_execution_id} --json`,
|
|
331
|
+
reason: "读取服务端最新执行状态",
|
|
332
|
+
},
|
|
225
333
|
recovery: [],
|
|
226
334
|
};
|
|
227
|
-
if (options.jsonOutput) {
|
|
228
|
-
out(JSON.stringify(guidance, null, 2) + "\n");
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
printAgentGuidance(guidance, options.output);
|
|
232
|
-
}
|
|
233
|
-
return;
|
|
234
335
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}],
|
|
244
|
-
recovery: [],
|
|
245
|
-
};
|
|
246
|
-
if (options.jsonOutput) {
|
|
247
|
-
out(JSON.stringify(guidance, null, 2) + "\n");
|
|
336
|
+
else if (cart.items.length === 0) {
|
|
337
|
+
envelope = {
|
|
338
|
+
status: "cart_empty",
|
|
339
|
+
result: { cart_id: cart.cart_id, cart_status: cart.status },
|
|
340
|
+
instruction: "Cart 当前为空;先读取已发布目录,不要猜测 item 或 variant。",
|
|
341
|
+
next: { command: "itpay catalog list --json", reason: "选择已发布服务" },
|
|
342
|
+
recovery: [],
|
|
343
|
+
};
|
|
248
344
|
}
|
|
249
345
|
else {
|
|
250
|
-
|
|
346
|
+
envelope = {
|
|
347
|
+
status: "action_available",
|
|
348
|
+
result: {
|
|
349
|
+
cart_id: cart.cart_id,
|
|
350
|
+
cart_status: cart.status,
|
|
351
|
+
item_count: cart.items.length,
|
|
352
|
+
amount_minor: cart.amount_minor,
|
|
353
|
+
currency: cart.currency,
|
|
354
|
+
},
|
|
355
|
+
instruction: cart.items.some((item) => item.service_quote_lock_id)
|
|
356
|
+
? "服务报价已锁定输入和价格;确认项目齐全后使用同一 Cart 创建一次 Checkout。"
|
|
357
|
+
: "该 Cart 是普通购买流程;使用同一 Cart 创建 Checkout,不要重复添加商品。",
|
|
358
|
+
next: { command: `itpay buy --cart ${cart.cart_id} --json`, reason: "继续 canonical Cart 结算" },
|
|
359
|
+
recovery: [],
|
|
360
|
+
};
|
|
251
361
|
}
|
|
362
|
+
writeCartNextEnvelope(envelope, options);
|
|
252
363
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
try {
|
|
259
|
-
return await backend.getServiceExecution(serviceExecutionID);
|
|
260
|
-
}
|
|
261
|
-
catch {
|
|
262
|
-
return undefined;
|
|
263
|
-
}
|
|
364
|
+
function writeCartNextEnvelope(envelope, options) {
|
|
365
|
+
writeCommandEnvelope(envelope, {
|
|
366
|
+
...(options.jsonOutput !== undefined ? { jsonOutput: options.jsonOutput } : {}),
|
|
367
|
+
...(options.output ? { output: options.output } : {}),
|
|
368
|
+
});
|
|
264
369
|
}
|
|
@@ -1,46 +1,72 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { writeCommandEnvelope } from "./guidance.js";
|
|
2
2
|
export async function runCatalogList(backend, options = {}) {
|
|
3
3
|
const manifest = await backend.getCatalogManifest();
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
4
|
+
const services = manifest.manifest.items.map(summarizeService);
|
|
5
|
+
const firstServiceID = manifest.manifest.items.find((item) => item.service_id)?.service_id;
|
|
6
|
+
const empty = services.length === 0;
|
|
7
|
+
const jsonFlag = options.jsonOutput ? " --json" : "";
|
|
8
|
+
writeCommandEnvelope({
|
|
9
|
+
status: empty ? "catalog_empty" : "listed",
|
|
10
|
+
result: { catalog_version: manifest.version, services },
|
|
11
|
+
instruction: empty
|
|
12
|
+
? "当前没有已发布服务;稍后重试,不要猜测 service_id。"
|
|
13
|
+
: "向用户解释主服务、辅助步骤和价格;得到用户意图后再启动对应 service_id。",
|
|
14
|
+
next: empty
|
|
15
|
+
? { command: `itpay catalog list${jsonFlag}`, reason: "稍后重新读取已发布目录" }
|
|
16
|
+
: {
|
|
17
|
+
command: `itpay services start ${services.length === 1 && firstServiceID ? firstServiceID : "<service_id>"}${jsonFlag}`,
|
|
18
|
+
reason: "启动用户选择的服务",
|
|
19
|
+
},
|
|
20
|
+
recovery: [],
|
|
21
|
+
}, {
|
|
22
|
+
...options,
|
|
23
|
+
plainResult: catalogPlainLines(manifest.version, services),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function summarizeService(item) {
|
|
27
|
+
const flow = item.service_flow;
|
|
28
|
+
return {
|
|
29
|
+
service_id: item.service_id ?? null,
|
|
30
|
+
title: item.title,
|
|
31
|
+
description: item.description ?? "",
|
|
32
|
+
...(flow ? {
|
|
33
|
+
discovery: {
|
|
34
|
+
title: flow.discovery.title,
|
|
35
|
+
description: flow.discovery.description,
|
|
36
|
+
...(flow.discovery.free_quota_limit !== undefined ? { free_quota: flow.discovery.free_quota_limit } : {}),
|
|
37
|
+
...(flow.discovery.paid_continuation ? {
|
|
38
|
+
paid_price: formatProductMoney(flow.discovery.paid_continuation.amount_minor, flow.discovery.paid_continuation.currency),
|
|
39
|
+
} : {}),
|
|
40
|
+
},
|
|
41
|
+
primary_offer: {
|
|
42
|
+
title: flow.primary_service.title,
|
|
43
|
+
description: flow.primary_service.description,
|
|
44
|
+
price: formatProductMoney(flow.primary_service.amount_minor, flow.primary_service.currency),
|
|
45
|
+
},
|
|
46
|
+
} : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function catalogPlainLines(version, services) {
|
|
50
|
+
const lines = [`catalog_version: ${version}`];
|
|
51
|
+
for (const service of services) {
|
|
52
|
+
lines.push(`service: ${String(service.title)}`);
|
|
53
|
+
lines.push(` service_id: ${String(service.service_id ?? "unavailable")}`);
|
|
54
|
+
if (service.description)
|
|
55
|
+
lines.push(` description: ${String(service.description)}`);
|
|
56
|
+
const discovery = service.discovery;
|
|
57
|
+
if (discovery) {
|
|
58
|
+
const quota = discovery.free_quota !== undefined ? `; free_quota: ${String(discovery.free_quota)}` : "";
|
|
59
|
+
const paid = discovery.paid_price ? `; paid_price: ${String(discovery.paid_price)}` : "";
|
|
60
|
+
lines.push(` discovery: ${String(discovery.title)}${quota}${paid}`);
|
|
61
|
+
lines.push(` ${String(discovery.description)}`);
|
|
31
62
|
}
|
|
32
|
-
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (item.variants.length > 0) {
|
|
37
|
-
out(` 可购买项目:\n`);
|
|
38
|
-
for (const variant of item.variants) {
|
|
39
|
-
out(` - ${variant.catalog_variant_id} ${variant.title} ${variant.offer_id} ${formatProductMoney(variant.amount_minor, variant.currency)}\n`);
|
|
40
|
-
}
|
|
63
|
+
const primary = service.primary_offer;
|
|
64
|
+
if (primary) {
|
|
65
|
+
lines.push(` primary_offer: ${String(primary.title)}; price: ${String(primary.price)}`);
|
|
66
|
+
lines.push(` ${String(primary.description)}`);
|
|
41
67
|
}
|
|
42
|
-
out("\n");
|
|
43
68
|
}
|
|
69
|
+
return lines;
|
|
44
70
|
}
|
|
45
71
|
function formatProductMoney(amountMinor, currency) {
|
|
46
72
|
return new Intl.NumberFormat("zh-CN", {
|