@itpay/cli 2.1.19 → 2.1.29

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 (86) hide show
  1. package/README.md +2 -2
  2. package/dist/src/commands/buy.js +3 -3
  3. package/dist/src/commands/cart.js +15 -15
  4. package/dist/src/commands/catalog.js +10 -7
  5. package/dist/src/commands/checkout.js +8 -11
  6. package/dist/src/commands/docs.js +3 -14
  7. package/dist/src/commands/guidance.js +41 -25
  8. package/dist/src/commands/install.js +2 -2
  9. package/dist/src/commands/order.js +4 -3
  10. package/dist/src/commands/orders.js +4 -9
  11. package/dist/src/commands/pay.js +1 -1
  12. package/dist/src/commands/rail_catalog.js +160 -11
  13. package/dist/src/commands/refund.js +5 -5
  14. package/dist/src/commands/services.js +658 -361
  15. package/dist/src/commands/skill.js +1 -1
  16. package/dist/src/commands/vault.js +27 -20
  17. package/dist/src/main.js +39 -24
  18. package/dist/src/render/markdown.js +0 -3
  19. package/dist/src/state/account_auth.js +9 -5
  20. package/dist/src/state/cart_session.js +4 -1
  21. package/dist/src/state/config.js +1 -1
  22. package/dist/src/state/task_journal.js +2 -0
  23. package/docs/agent/buyer/cart-checkout.json +5 -4
  24. package/docs/agent/buyer/catalog-list.json +24 -6
  25. package/docs/agent/buyer/identity-and-sessions.json +16 -15
  26. package/docs/agent/buyer/install-and-setup.json +5 -4
  27. package/docs/agent/buyer/orders-refunds.json +11 -5
  28. package/docs/agent/buyer/payment-flow.json +3 -3
  29. package/docs/agent/buyer/purchased-content.json +18 -4
  30. package/docs/agent/buyer/quickstart.json +22 -14
  31. package/docs/agent/buyer/rail-booking.json +125 -171
  32. package/docs/agent/buyer/rail-fast-checkout.json +21 -44
  33. package/docs/agent/buyer/render-hosts.json +9 -6
  34. package/docs/agent/buyer/service-feedback.json +2 -1
  35. package/docs/cli-reference/agent-types.md +1 -1
  36. package/docs/cli-reference/commands/auth.md +2 -0
  37. package/docs/cli-reference/commands/buy.md +1 -1
  38. package/docs/cli-reference/commands/cart/add.md +1 -1
  39. package/docs/cli-reference/commands/cart/clear.md +1 -1
  40. package/docs/cli-reference/commands/cart/index.md +1 -1
  41. package/docs/cli-reference/commands/cart/next.md +1 -1
  42. package/docs/cli-reference/commands/cart/remove.md +1 -1
  43. package/docs/cli-reference/commands/cart/show.md +1 -1
  44. package/docs/cli-reference/commands/catalog/index.md +1 -1
  45. package/docs/cli-reference/commands/catalog/list.md +1 -1
  46. package/docs/cli-reference/commands/checkout.md +1 -1
  47. package/docs/cli-reference/commands/device.md +8 -7
  48. package/docs/cli-reference/commands/docs/index.md +1 -1
  49. package/docs/cli-reference/commands/docs/list.md +1 -1
  50. package/docs/cli-reference/commands/docs/search.md +1 -1
  51. package/docs/cli-reference/commands/docs/show.md +1 -1
  52. package/docs/cli-reference/commands/install.md +2 -2
  53. package/docs/cli-reference/commands/next.md +1 -1
  54. package/docs/cli-reference/commands/order.md +1 -1
  55. package/docs/cli-reference/commands/pay.md +1 -1
  56. package/docs/cli-reference/commands/readyz.md +1 -1
  57. package/docs/cli-reference/commands/refund/cancel.md +1 -1
  58. package/docs/cli-reference/commands/refund/create.md +1 -1
  59. package/docs/cli-reference/commands/refund/get.md +2 -2
  60. package/docs/cli-reference/commands/refund/index.md +1 -1
  61. package/docs/cli-reference/commands/refund/list.md +1 -1
  62. package/docs/cli-reference/commands/refund/watch.md +1 -1
  63. package/docs/cli-reference/commands/services/action.md +3 -3
  64. package/docs/cli-reference/commands/services/checkout.md +1 -1
  65. package/docs/cli-reference/commands/services/events.md +1 -1
  66. package/docs/cli-reference/commands/services/get.md +1 -1
  67. package/docs/cli-reference/commands/services/index.md +1 -1
  68. package/docs/cli-reference/commands/services/invoke.md +5 -13
  69. package/docs/cli-reference/commands/services/list.md +1 -1
  70. package/docs/cli-reference/commands/services/next.md +12 -10
  71. package/docs/cli-reference/commands/services/page.md +9 -7
  72. package/docs/cli-reference/commands/services/quote.md +1 -1
  73. package/docs/cli-reference/commands/services/read-result.md +16 -9
  74. package/docs/cli-reference/commands/services/run.md +4 -2
  75. package/docs/cli-reference/commands/services/start.md +1 -1
  76. package/docs/cli-reference/conventions.md +2 -2
  77. package/package.json +7 -5
  78. package/skills/itpay/SKILL.md +77 -238
  79. package/docs/reviews/agent-ux-handoff-2026-09-23/IMPLEMENTATION-EVIDENCE.md +0 -174
  80. package/docs/skill-bundle-rollout/01-mcp-authentication.md +0 -134
  81. package/docs/skill-bundle-rollout/02-platform-bundle-repositories.md +0 -210
  82. package/docs/skill-bundle-rollout/03-platform-publishing.md +0 -161
  83. package/docs/skill-bundle-rollout/04-first-wave-platforms.md +0 -70
  84. package/docs/skill-bundle-rollout/05-sync-operations.md +0 -62
  85. package/docs/skill-bundle-rollout/README.md +0 -151
  86. /package/bin/{itp → itp.mjs} +0 -0
@@ -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
  }
@@ -12,11 +12,11 @@ export async function runRefund(backend, config, options) {
12
12
  plainResult: Object.entries(envelope.result).map(([key, value]) => `${key}: ${typeof value === "string" ? value : JSON.stringify(value)}`),
13
13
  });
14
14
  }
15
- function refundStateEnvelope(refund, status) {
15
+ export function refundStateEnvelope(refund, status) {
16
16
  const terminal = ["succeeded", "failed", "cancelled", "rejected"].includes(refund.status);
17
17
  let instruction = refund.decision_mode === "manual"
18
18
  ? "先告诉用户退款已进入人工审核,原交付保持冻结;人工审核不等于拒绝,等待服务器决定,不要重复申请或承诺结果。"
19
- : "先告诉用户退款申请已经记录,原交付已冻结;自动路径表示系统会继续处理,但只有最终 succeeded 才能确认退款成功。然后只跟踪同一退款,不要重复申请、reveal、授权或读取结果。";
19
+ : "先告诉用户退款申请已经记录,原交付已冻结;自动路径表示系统会继续处理,但只有最终 succeeded 才能确认退款成功。然后只跟踪同一退款,不读取交付、不创建授权或重复申请。";
20
20
  if (!refund.access_locked)
21
21
  instruction = "先告诉用户退款当前没有锁定交付;按服务器事实解释当前状态,不要自行推断退款结果、到账时间或交付资格。";
22
22
  if (refund.status === "succeeded")
@@ -73,7 +73,7 @@ export async function runListRefunds(backend, options) {
73
73
  : "该订单没有退款记录;确认用户确实要求退款后再创建。",
74
74
  next: selected
75
75
  ? { command: `itpay refund get ${selected.refund_request_id} --json`, reason: active ? "读取活跃退款" : "读取最新退款" }
76
- : { command: `itpay refund create --order ${options.orderID} --json`, reason: "为该订单创建退款" },
76
+ : null,
77
77
  recovery: [],
78
78
  };
79
79
  writeCommandEnvelope(envelope, {
@@ -96,13 +96,13 @@ export async function runGetRefund(backend, refundID, options = {}) {
96
96
  export async function runCancelRefund(backend, refundID, reason, options = {}) {
97
97
  const refund = await backend.cancelRefund(refundID, reason?.trim() || "buyer_cancelled");
98
98
  const envelope = {
99
- status: "cancelled",
99
+ status: refund.status,
100
100
  result: {
101
101
  refund_request_id: refund.refund_request_id,
102
102
  order_id: refund.order_id,
103
103
  access_locked: refund.access_locked,
104
104
  },
105
- instruction: "退款已取消;如需交付,重新进入订单并取得新的授权。",
105
+ instruction: refund.status === "cancelled" ? "Backend确认退款已取消;如需交付,重新进入原订单核对当前权限,旧grant不自动复活;后端要求时取得新的授权。" : "Backend未确认取消;按返回的退款权威状态核对原订单,不重复提交取消。",
106
106
  next: { command: `itpay order ${refund.order_id} --json`, reason: "确认订单访问状态" },
107
107
  recovery: [],
108
108
  };