@itpay/cli 2.1.19 → 2.1.22
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/dist/src/commands/catalog.js +1 -0
- package/dist/src/commands/rail_catalog.js +160 -11
- package/dist/src/commands/services.js +215 -47
- package/dist/src/commands/skill.js +1 -1
- package/dist/src/main.js +7 -3
- package/dist/src/render/markdown.js +0 -3
- package/dist/src/state/account_auth.js +6 -2
- package/dist/src/state/cart_session.js +4 -1
- package/dist/src/state/config.js +1 -1
- package/dist/src/state/task_journal.js +2 -0
- package/docs/agent/buyer/cart-checkout.json +5 -4
- package/docs/agent/buyer/catalog-list.json +24 -6
- package/docs/agent/buyer/identity-and-sessions.json +15 -10
- package/docs/agent/buyer/install-and-setup.json +5 -4
- package/docs/agent/buyer/orders-refunds.json +4 -4
- package/docs/agent/buyer/payment-flow.json +3 -3
- package/docs/agent/buyer/purchased-content.json +17 -3
- package/docs/agent/buyer/quickstart.json +18 -11
- package/docs/agent/buyer/rail-booking.json +79 -166
- package/docs/agent/buyer/rail-fast-checkout.json +21 -44
- package/docs/agent/buyer/render-hosts.json +8 -5
- package/docs/agent/buyer/service-feedback.json +2 -1
- package/docs/cli-reference/agent-types.md +1 -1
- package/docs/cli-reference/commands/auth.md +2 -0
- package/docs/cli-reference/commands/buy.md +1 -1
- package/docs/cli-reference/commands/cart/add.md +1 -1
- package/docs/cli-reference/commands/cart/clear.md +1 -1
- package/docs/cli-reference/commands/cart/index.md +1 -1
- package/docs/cli-reference/commands/cart/next.md +1 -1
- package/docs/cli-reference/commands/cart/remove.md +1 -1
- package/docs/cli-reference/commands/cart/show.md +1 -1
- package/docs/cli-reference/commands/catalog/index.md +1 -1
- package/docs/cli-reference/commands/catalog/list.md +1 -1
- package/docs/cli-reference/commands/checkout.md +1 -1
- package/docs/cli-reference/commands/device.md +4 -2
- package/docs/cli-reference/commands/docs/index.md +1 -1
- package/docs/cli-reference/commands/docs/list.md +1 -1
- package/docs/cli-reference/commands/docs/search.md +1 -1
- package/docs/cli-reference/commands/docs/show.md +1 -1
- package/docs/cli-reference/commands/install.md +2 -2
- package/docs/cli-reference/commands/next.md +1 -1
- package/docs/cli-reference/commands/order.md +1 -1
- package/docs/cli-reference/commands/pay.md +1 -1
- package/docs/cli-reference/commands/readyz.md +1 -1
- package/docs/cli-reference/commands/refund/cancel.md +1 -1
- package/docs/cli-reference/commands/refund/create.md +1 -1
- package/docs/cli-reference/commands/refund/get.md +2 -2
- package/docs/cli-reference/commands/refund/index.md +1 -1
- package/docs/cli-reference/commands/refund/list.md +1 -1
- package/docs/cli-reference/commands/refund/watch.md +1 -1
- package/docs/cli-reference/commands/services/action.md +3 -3
- package/docs/cli-reference/commands/services/checkout.md +1 -1
- package/docs/cli-reference/commands/services/events.md +1 -1
- package/docs/cli-reference/commands/services/get.md +1 -1
- package/docs/cli-reference/commands/services/index.md +1 -1
- package/docs/cli-reference/commands/services/invoke.md +3 -11
- package/docs/cli-reference/commands/services/list.md +1 -1
- package/docs/cli-reference/commands/services/next.md +12 -10
- package/docs/cli-reference/commands/services/page.md +9 -7
- package/docs/cli-reference/commands/services/quote.md +1 -1
- package/docs/cli-reference/commands/services/read-result.md +8 -8
- package/docs/cli-reference/commands/services/run.md +2 -2
- package/docs/cli-reference/commands/services/start.md +1 -1
- package/docs/cli-reference/conventions.md +2 -2
- package/package.json +7 -5
- package/skills/itpay/SKILL.md +70 -238
- package/docs/reviews/agent-ux-handoff-2026-09-23/IMPLEMENTATION-EVIDENCE.md +0 -174
- package/docs/skill-bundle-rollout/01-mcp-authentication.md +0 -134
- package/docs/skill-bundle-rollout/02-platform-bundle-repositories.md +0 -210
- package/docs/skill-bundle-rollout/03-platform-publishing.md +0 -161
- package/docs/skill-bundle-rollout/04-first-wave-platforms.md +0 -70
- package/docs/skill-bundle-rollout/05-sync-operations.md +0 -62
- package/docs/skill-bundle-rollout/README.md +0 -151
- /package/bin/{itp → itp.mjs} +0 -0
|
@@ -30,6 +30,7 @@ function summarizeService(item) {
|
|
|
30
30
|
service_id: item.service_id ?? null,
|
|
31
31
|
title: item.title,
|
|
32
32
|
description: item.description ?? "",
|
|
33
|
+
...(item.service_id?.startsWith("itpay-rail-") ? { guide: "itpay docs show rail-booking --json" } : {}),
|
|
33
34
|
...(flow ? {
|
|
34
35
|
discovery: {
|
|
35
36
|
title: flow.discovery.title,
|
|
@@ -1,16 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
// The committed rail.catalog.v3 may ship positional rows (journeys, plans,
|
|
3
|
-
// profiles keyed by journey_columns plus shared ride_table / *_index tables)
|
|
4
|
-
// instead of per-journey objects. The summary path needs only ref + route
|
|
5
|
-
// text + default layer; the full packed document is passed through verbatim
|
|
6
|
-
// for JSON output — decoding is for the human-readable summary and selectors.
|
|
1
|
+
import { CLI_VERSION, cliDistribution } from "../state/config.js";
|
|
7
2
|
export class RailCatalogEncodingError extends Error {
|
|
8
3
|
constructor(encoding) {
|
|
9
|
-
super(`catalog encoding ${JSON.stringify(encoding)} is not supported by
|
|
10
|
-
`
|
|
4
|
+
super(`catalog encoding ${JSON.stringify(encoding)} is not supported by CLI ${CLI_VERSION} ` +
|
|
5
|
+
`(${cliDistribution()}); obtain a compatible exact version through this installation channel. ` +
|
|
6
|
+
`The committed catalog is intact`);
|
|
11
7
|
this.name = "rail_catalog_encoding";
|
|
12
8
|
}
|
|
13
9
|
}
|
|
10
|
+
function scopeFromProfiles(value) {
|
|
11
|
+
const profiles = Array.isArray(value) ? value : [];
|
|
12
|
+
const selected = profiles.find((p) => Array.isArray(p) && Array.isArray(p[1]) && p[1].includes("balanced")) ?? profiles[0];
|
|
13
|
+
if (!Array.isArray(selected))
|
|
14
|
+
return { origin: "legacy_unknown", destination: "legacy_unknown",
|
|
15
|
+
duration_basis: "legacy_unknown", arrival_basis: "legacy_unknown", cost_basis: "legacy_unknown",
|
|
16
|
+
duration_minutes: null, estimated_total_cost: null, cost_incomplete: true };
|
|
17
|
+
const extras = selected[10] && typeof selected[10] === "object" ? selected[10] : {};
|
|
18
|
+
const total = typeof selected[9] === "number" && extras.ci !== true ? selected[9] / 100 : null;
|
|
19
|
+
return {
|
|
20
|
+
origin: extras.os ?? "legacy_unknown", destination: extras.ds ?? "legacy_unknown",
|
|
21
|
+
duration_basis: extras.db ?? "legacy_unknown", arrival_basis: extras.ab ?? "legacy_unknown",
|
|
22
|
+
cost_basis: extras.cb ?? "legacy_unknown", duration_minutes: selected[8] ?? null,
|
|
23
|
+
estimated_total_cost: total, cost_incomplete: extras.ci === true,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
14
26
|
function asString(v) {
|
|
15
27
|
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
16
28
|
}
|
|
@@ -20,6 +32,125 @@ function asIndex(v, bound) {
|
|
|
20
32
|
}
|
|
21
33
|
return v;
|
|
22
34
|
}
|
|
35
|
+
function asObject(value) {
|
|
36
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
37
|
+
}
|
|
38
|
+
function datedTime(date, value) {
|
|
39
|
+
const raw = asString(value);
|
|
40
|
+
if (!raw)
|
|
41
|
+
return null;
|
|
42
|
+
if (/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}/.test(raw))
|
|
43
|
+
return raw.replace("T", " ").slice(0, 16);
|
|
44
|
+
const short = raw.match(/^(\d{2}:\d{2})(?:\+(\d+))?$/);
|
|
45
|
+
const day = asString(date);
|
|
46
|
+
if (!short || !day)
|
|
47
|
+
return null;
|
|
48
|
+
if (!short[2])
|
|
49
|
+
return `${day} ${short[1]}`;
|
|
50
|
+
const shifted = new Date(`${day}T00:00:00Z`);
|
|
51
|
+
if (Number.isNaN(shifted.getTime()))
|
|
52
|
+
return null;
|
|
53
|
+
shifted.setUTCDate(shifted.getUTCDate() + Number(short[2]));
|
|
54
|
+
return `${shifted.toISOString().slice(0, 10)} ${short[1]}`;
|
|
55
|
+
}
|
|
56
|
+
function elapsedMinutes(departure, arrival) {
|
|
57
|
+
if (!departure || !arrival)
|
|
58
|
+
return null;
|
|
59
|
+
const start = Date.parse(departure.replace(" ", "T") + "Z");
|
|
60
|
+
const end = Date.parse(arrival.replace(" ", "T") + "Z");
|
|
61
|
+
return Number.isFinite(start) && Number.isFinite(end) && end >= start ? (end - start) / 60_000 : null;
|
|
62
|
+
}
|
|
63
|
+
function seatOffer(row, names) {
|
|
64
|
+
if (!Array.isArray(row))
|
|
65
|
+
return null;
|
|
66
|
+
const extras = asObject(row[7]);
|
|
67
|
+
const code = asString(row[1]);
|
|
68
|
+
return {
|
|
69
|
+
offer_ref: row[0], seat_type: code ?? null, seat_name: code ? names[code] ?? code : null,
|
|
70
|
+
inventory_status: row[2] ?? null, quantity: row[3] ?? null, unit_price_fen: row[4] ?? null,
|
|
71
|
+
eligible: row[5] === true, requires_quote: row[6] === true,
|
|
72
|
+
price_source: extras.s ?? "sale_price", quote_price_unverified: extras.u === true,
|
|
73
|
+
conflicted: extras.c === true,
|
|
74
|
+
...(Array.isArray(extras.o) ? { price_observations_fen: extras.o } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function decodeRide(row, wait, stations, services) {
|
|
78
|
+
if (!Array.isArray(row)) {
|
|
79
|
+
const ride = asObject(row);
|
|
80
|
+
if (!Object.keys(ride).length)
|
|
81
|
+
throw new Error("rail catalog: malformed ride row");
|
|
82
|
+
const refs = Array.isArray(ride.service_refs) ? ride.service_refs : [];
|
|
83
|
+
const codes = [...new Set(refs.map((ref) => asObject(services[asString(ref) ?? ""]).tc)
|
|
84
|
+
.filter((code) => typeof code === "string" && code.length > 0))];
|
|
85
|
+
const departure = datedTime(ride.boarding_date, ride.departure_at ?? ride.departure);
|
|
86
|
+
const arrival = datedTime(ride.boarding_date, ride.arrival_at ?? ride.arrival);
|
|
87
|
+
return { train_codes: codes, train_code: codes.join("/"),
|
|
88
|
+
from_station: stations[asString(ride.from_station_code) ?? ""] ?? ride.from_station ?? null,
|
|
89
|
+
to_station: stations[asString(ride.to_station_code) ?? ""] ?? ride.to_station ?? null,
|
|
90
|
+
boarding_date: ride.boarding_date ?? null,
|
|
91
|
+
departure, arrival, duration_minutes: elapsedMinutes(departure, arrival),
|
|
92
|
+
wait_minutes: typeof wait === "number" ? wait : ride.wait_minutes ?? null,
|
|
93
|
+
same_run: ride.same_run === true, onboard_stops: Array.isArray(ride.onboard_stops) ? ride.onboard_stops : [],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const extras = asObject(row[8]);
|
|
97
|
+
const refs = Array.isArray(row[4]) ? row[4] : [];
|
|
98
|
+
const codes = [...new Set(refs.map((ref) => asObject(services[asString(ref) ?? ""]).tc)
|
|
99
|
+
.filter((code) => typeof code === "string" && code.length > 0))];
|
|
100
|
+
const base = row[0];
|
|
101
|
+
const departure = datedTime(base, extras.dep);
|
|
102
|
+
const arrival = datedTime(base, extras.arr);
|
|
103
|
+
return {
|
|
104
|
+
train_codes: codes, train_code: codes.join("/"),
|
|
105
|
+
from_station: stations[asString(row[2]) ?? ""] ?? row[2] ?? null,
|
|
106
|
+
to_station: stations[asString(row[3]) ?? ""] ?? row[3] ?? null,
|
|
107
|
+
boarding_date: base ?? null,
|
|
108
|
+
departure, arrival, duration_minutes: elapsedMinutes(departure, arrival),
|
|
109
|
+
wait_minutes: typeof wait === "number" ? wait : typeof row[5] === "number" ? row[5] : null,
|
|
110
|
+
same_run: row[6] === true, onboard_stops: Array.isArray(row[7]) ? row[7] : [],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function decodePlans(rawPlans, rawProfiles, packed, catalog, services, names, stations) {
|
|
114
|
+
const plans = Array.isArray(rawPlans) ? rawPlans : [];
|
|
115
|
+
const profiles = Array.isArray(rawProfiles) ? rawProfiles : [];
|
|
116
|
+
const preferred = profiles.find((profile) => Array.isArray(profile) && Array.isArray(profile[1]) && profile[1].includes("balanced")) ?? profiles[0];
|
|
117
|
+
const profile = Array.isArray(preferred) ? preferred : [];
|
|
118
|
+
const serviceIndex = Array.isArray(catalog.service_index) ? catalog.service_index : [];
|
|
119
|
+
const selectedPlan = packed && typeof profile[2] === "number" ? plans[asIndex(profile[2], plans.length)] :
|
|
120
|
+
plans.find((plan) => Array.isArray(plan) && plan[0] === profile[2]);
|
|
121
|
+
const selectedRef = Array.isArray(selectedPlan) ? selectedPlan[0] : null;
|
|
122
|
+
const decoded = plans.map((plan) => {
|
|
123
|
+
if (!Array.isArray(plan))
|
|
124
|
+
throw new Error("rail catalog: malformed ticket plan");
|
|
125
|
+
const refs = (Array.isArray(plan[1]) ? plan[1] : []).map((ref) => packed && typeof ref === "number" ? serviceIndex[asIndex(ref, serviceIndex.length)] : ref);
|
|
126
|
+
const extras = asObject(plan[7]);
|
|
127
|
+
const legs = refs.map((ref, index) => {
|
|
128
|
+
const service = asObject(services[asString(ref) ?? ""]);
|
|
129
|
+
const offers = Array.isArray(service.of) ? service.of : [];
|
|
130
|
+
const selectedOffer = plan[0] === selectedRef && Array.isArray(profile[3]) ? profile[3][index] : undefined;
|
|
131
|
+
const chosen = packed && typeof selectedOffer === "number" ? offers[asIndex(selectedOffer, offers.length)] :
|
|
132
|
+
offers.find((offer) => Array.isArray(offer) && offer[0] === selectedOffer);
|
|
133
|
+
return {
|
|
134
|
+
service_ref: ref ?? null, train_code: service.tc ?? null,
|
|
135
|
+
from_station: stations[asString(service.f) ?? ""] ?? service.f ?? null,
|
|
136
|
+
to_station: stations[asString(service.t) ?? ""] ?? service.t ?? null,
|
|
137
|
+
departure: datedTime(service.d, service.dep), arrival: datedTime(service.d, service.arr),
|
|
138
|
+
seat_options: offers.map((offer) => seatOffer(offer, names)).filter((offer) => offer !== null),
|
|
139
|
+
...(chosen ? { selected_seat: seatOffer(chosen, names) } : {}),
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
const amount = asObject(plan[3]);
|
|
143
|
+
return {
|
|
144
|
+
ticket_plan_ref: plan[0], passenger_count: plan[2] ?? null,
|
|
145
|
+
purchase_support: plan[6] ?? "none", rail_amount_verified: plan[4] === true,
|
|
146
|
+
rail_amount_fen: { default: amount.def ?? null, minimum: amount.min ?? null, maximum: amount.max ?? null },
|
|
147
|
+
service_fee_fen: plan[5] ?? null, quote_price_unverified: extras.qu === true,
|
|
148
|
+
legs,
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
const selected = decoded.find((plan) => plan.ticket_plan_ref === selectedRef);
|
|
152
|
+
return { plans: decoded, ...(selected ? { selected } : {}) };
|
|
153
|
+
}
|
|
23
154
|
// _cn_route_text port: "南头 C7608 → 广州南换乘71分 → G2944 重庆西" — kept
|
|
24
155
|
// byte-identical with the planner so a packed journey renders the same text
|
|
25
156
|
// its object form carried.
|
|
@@ -72,6 +203,9 @@ export function decodeRailCatalogJourneys(catalog) {
|
|
|
72
203
|
const encoding = catalog["encoding"];
|
|
73
204
|
const layerMap = catalog["choice_layers"]?.["journey_layer"] ?? {};
|
|
74
205
|
const layerOf = (ref) => layerMap[ref] === "backup" ? "backup" : "main";
|
|
206
|
+
const stations = asObject(catalog.stations);
|
|
207
|
+
const services = asObject(catalog.services);
|
|
208
|
+
const seatNames = asObject(catalog.seat_names);
|
|
75
209
|
if (encoding === undefined || encoding === null) {
|
|
76
210
|
const rows = Array.isArray(catalog["journeys"]) ? catalog["journeys"] : [];
|
|
77
211
|
return {
|
|
@@ -79,10 +213,17 @@ export function decodeRailCatalogJourneys(catalog) {
|
|
|
79
213
|
journeys: rows.map((j) => {
|
|
80
214
|
const rec = (j ?? {});
|
|
81
215
|
const ref = asString(rec["ref"]) ?? asString(rec["journey_id"]) ?? "?";
|
|
216
|
+
const calculationScope = scopeFromProfiles(rec["profiles"]);
|
|
217
|
+
const rides = (Array.isArray(rec.rides) ? rec.rides : []).map((ride) => decodeRide(ride, null, stations, services));
|
|
218
|
+
const ticketPlans = decodePlans(rec.plans, rec.profiles, false, catalog, services, seatNames, stations);
|
|
82
219
|
return {
|
|
83
220
|
ref,
|
|
84
221
|
route: asString(rec["route"]) ?? asString(rec["route_text"]) ?? asString(rec["summary"]) ?? "",
|
|
85
222
|
defaultLayer: layerOf(ref),
|
|
223
|
+
rides,
|
|
224
|
+
...(calculationScope ? { calculation_scope: calculationScope } : {}),
|
|
225
|
+
ticket_plans: ticketPlans.plans,
|
|
226
|
+
...(ticketPlans.selected ? { selected_plan: ticketPlans.selected } : {}),
|
|
86
227
|
};
|
|
87
228
|
}),
|
|
88
229
|
};
|
|
@@ -95,8 +236,6 @@ export function decodeRailCatalogJourneys(catalog) {
|
|
|
95
236
|
throw new RailCatalogEncodingError(`${String(encoding)} (unexpected journey_columns)`);
|
|
96
237
|
}
|
|
97
238
|
const rideTable = (Array.isArray(catalog["ride_table"]) ? catalog["ride_table"] : []);
|
|
98
|
-
const stations = (catalog["stations"] ?? {});
|
|
99
|
-
const services = (catalog["services"] ?? {});
|
|
100
239
|
const journeys = (Array.isArray(catalog["journeys"]) ? catalog["journeys"] : []);
|
|
101
240
|
return {
|
|
102
241
|
packed: true,
|
|
@@ -106,7 +245,17 @@ export function decodeRailCatalogJourneys(catalog) {
|
|
|
106
245
|
const ref = asString(row[0]) ?? "?";
|
|
107
246
|
const route = asString(row[1]) ??
|
|
108
247
|
(Array.isArray(row[2]) ? regenerateRoute(row[2], rideTable, stations, services) : "");
|
|
109
|
-
|
|
248
|
+
const rides = (Array.isArray(row[2]) ? row[2] : []).map((pair) => {
|
|
249
|
+
if (!Array.isArray(pair) || pair.length !== 2)
|
|
250
|
+
throw new Error("shared_rows: malformed ride pair");
|
|
251
|
+
return decodeRide(rideTable[asIndex(pair[0], rideTable.length)], pair[1], stations, services);
|
|
252
|
+
});
|
|
253
|
+
const calculationScope = scopeFromProfiles(row[6]);
|
|
254
|
+
const ticketPlans = decodePlans(row[3], row[6], true, catalog, services, seatNames, stations);
|
|
255
|
+
return { ref, route, defaultLayer: layerOf(ref), rides,
|
|
256
|
+
...(calculationScope ? { calculation_scope: calculationScope } : {}),
|
|
257
|
+
ticket_plans: ticketPlans.plans,
|
|
258
|
+
...(ticketPlans.selected ? { selected_plan: ticketPlans.selected } : {}) };
|
|
110
259
|
}),
|
|
111
260
|
};
|
|
112
261
|
}
|