@itpay/cli 2.1.18 → 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.
Files changed (75) hide show
  1. package/dist/src/commands/catalog.js +1 -0
  2. package/dist/src/commands/rail_catalog.js +160 -11
  3. package/dist/src/commands/services.js +289 -71
  4. package/dist/src/commands/skill.js +1 -1
  5. package/dist/src/main.js +54 -19
  6. package/dist/src/render/markdown.js +0 -3
  7. package/dist/src/state/account_auth.js +6 -2
  8. package/dist/src/state/cart_session.js +4 -1
  9. package/dist/src/state/config.js +1 -1
  10. package/dist/src/state/device_authority.js +69 -13
  11. package/dist/src/state/task_journal.js +2 -0
  12. package/docs/agent/buyer/cart-checkout.json +5 -4
  13. package/docs/agent/buyer/catalog-list.json +24 -6
  14. package/docs/agent/buyer/identity-and-sessions.json +15 -10
  15. package/docs/agent/buyer/install-and-setup.json +7 -6
  16. package/docs/agent/buyer/orders-refunds.json +4 -4
  17. package/docs/agent/buyer/payment-flow.json +3 -3
  18. package/docs/agent/buyer/purchased-content.json +17 -3
  19. package/docs/agent/buyer/quickstart.json +18 -11
  20. package/docs/agent/buyer/rail-booking.json +79 -167
  21. package/docs/agent/buyer/rail-fast-checkout.json +21 -43
  22. package/docs/agent/buyer/render-hosts.json +8 -5
  23. package/docs/agent/buyer/service-feedback.json +2 -1
  24. package/docs/cli-reference/agent-types.md +1 -1
  25. package/docs/cli-reference/commands/auth.md +2 -0
  26. package/docs/cli-reference/commands/buy.md +1 -1
  27. package/docs/cli-reference/commands/cart/add.md +1 -1
  28. package/docs/cli-reference/commands/cart/clear.md +1 -1
  29. package/docs/cli-reference/commands/cart/index.md +1 -1
  30. package/docs/cli-reference/commands/cart/next.md +1 -1
  31. package/docs/cli-reference/commands/cart/remove.md +1 -1
  32. package/docs/cli-reference/commands/cart/show.md +1 -1
  33. package/docs/cli-reference/commands/catalog/index.md +1 -1
  34. package/docs/cli-reference/commands/catalog/list.md +1 -1
  35. package/docs/cli-reference/commands/checkout.md +1 -1
  36. package/docs/cli-reference/commands/device.md +4 -2
  37. package/docs/cli-reference/commands/docs/index.md +1 -1
  38. package/docs/cli-reference/commands/docs/list.md +1 -1
  39. package/docs/cli-reference/commands/docs/search.md +1 -1
  40. package/docs/cli-reference/commands/docs/show.md +1 -1
  41. package/docs/cli-reference/commands/install.md +2 -2
  42. package/docs/cli-reference/commands/next.md +1 -1
  43. package/docs/cli-reference/commands/order.md +1 -1
  44. package/docs/cli-reference/commands/pay.md +1 -1
  45. package/docs/cli-reference/commands/readyz.md +1 -1
  46. package/docs/cli-reference/commands/refund/cancel.md +1 -1
  47. package/docs/cli-reference/commands/refund/create.md +1 -1
  48. package/docs/cli-reference/commands/refund/get.md +2 -2
  49. package/docs/cli-reference/commands/refund/index.md +1 -1
  50. package/docs/cli-reference/commands/refund/list.md +1 -1
  51. package/docs/cli-reference/commands/refund/watch.md +1 -1
  52. package/docs/cli-reference/commands/services/action.md +3 -3
  53. package/docs/cli-reference/commands/services/checkout.md +1 -1
  54. package/docs/cli-reference/commands/services/events.md +1 -1
  55. package/docs/cli-reference/commands/services/get.md +1 -1
  56. package/docs/cli-reference/commands/services/index.md +1 -1
  57. package/docs/cli-reference/commands/services/invoke.md +3 -11
  58. package/docs/cli-reference/commands/services/list.md +1 -1
  59. package/docs/cli-reference/commands/services/next.md +12 -10
  60. package/docs/cli-reference/commands/services/page.md +9 -7
  61. package/docs/cli-reference/commands/services/quote.md +1 -1
  62. package/docs/cli-reference/commands/services/read-result.md +8 -8
  63. package/docs/cli-reference/commands/services/run.md +2 -2
  64. package/docs/cli-reference/commands/services/start.md +1 -1
  65. package/docs/cli-reference/conventions.md +2 -2
  66. package/package.json +7 -5
  67. package/skills/itpay/SKILL.md +70 -186
  68. package/docs/reviews/agent-ux-handoff-2026-09-23/IMPLEMENTATION-EVIDENCE.md +0 -174
  69. package/docs/skill-bundle-rollout/01-mcp-authentication.md +0 -134
  70. package/docs/skill-bundle-rollout/02-platform-bundle-repositories.md +0 -210
  71. package/docs/skill-bundle-rollout/03-platform-publishing.md +0 -161
  72. package/docs/skill-bundle-rollout/04-first-wave-platforms.md +0 -70
  73. package/docs/skill-bundle-rollout/05-sync-operations.md +0 -62
  74. package/docs/skill-bundle-rollout/README.md +0 -151
  75. /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
- // shared_rows.v1 catalog decode for the rail planning full-catalog read.
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 this CLI — ` +
10
- `run npm install -g @itpay/cli to upgrade; the committed catalog is intact`);
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
- return { ref, route, defaultLayer: layerOf(ref) };
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
  }