@hasna/microservices 0.0.3 → 0.0.5

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 (68) hide show
  1. package/bin/index.js +63 -0
  2. package/bin/mcp.js +63 -0
  3. package/dist/index.js +63 -0
  4. package/microservices/microservice-ads/package.json +27 -0
  5. package/microservices/microservice-ads/src/cli/index.ts +605 -0
  6. package/microservices/microservice-ads/src/db/campaigns.ts +797 -0
  7. package/microservices/microservice-ads/src/db/database.ts +93 -0
  8. package/microservices/microservice-ads/src/db/migrations.ts +60 -0
  9. package/microservices/microservice-ads/src/index.ts +39 -0
  10. package/microservices/microservice-ads/src/mcp/index.ts +480 -0
  11. package/microservices/microservice-contracts/package.json +27 -0
  12. package/microservices/microservice-contracts/src/cli/index.ts +770 -0
  13. package/microservices/microservice-contracts/src/db/contracts.ts +925 -0
  14. package/microservices/microservice-contracts/src/db/database.ts +93 -0
  15. package/microservices/microservice-contracts/src/db/migrations.ts +141 -0
  16. package/microservices/microservice-contracts/src/index.ts +43 -0
  17. package/microservices/microservice-contracts/src/mcp/index.ts +617 -0
  18. package/microservices/microservice-domains/package.json +27 -0
  19. package/microservices/microservice-domains/src/cli/index.ts +691 -0
  20. package/microservices/microservice-domains/src/db/database.ts +93 -0
  21. package/microservices/microservice-domains/src/db/domains.ts +1164 -0
  22. package/microservices/microservice-domains/src/db/migrations.ts +60 -0
  23. package/microservices/microservice-domains/src/index.ts +65 -0
  24. package/microservices/microservice-domains/src/mcp/index.ts +536 -0
  25. package/microservices/microservice-hiring/package.json +27 -0
  26. package/microservices/microservice-hiring/src/cli/index.ts +741 -0
  27. package/microservices/microservice-hiring/src/db/database.ts +93 -0
  28. package/microservices/microservice-hiring/src/db/hiring.ts +1085 -0
  29. package/microservices/microservice-hiring/src/db/migrations.ts +89 -0
  30. package/microservices/microservice-hiring/src/index.ts +80 -0
  31. package/microservices/microservice-hiring/src/lib/scoring.ts +206 -0
  32. package/microservices/microservice-hiring/src/mcp/index.ts +709 -0
  33. package/microservices/microservice-payments/package.json +27 -0
  34. package/microservices/microservice-payments/src/cli/index.ts +609 -0
  35. package/microservices/microservice-payments/src/db/database.ts +93 -0
  36. package/microservices/microservice-payments/src/db/migrations.ts +81 -0
  37. package/microservices/microservice-payments/src/db/payments.ts +1204 -0
  38. package/microservices/microservice-payments/src/index.ts +51 -0
  39. package/microservices/microservice-payments/src/mcp/index.ts +683 -0
  40. package/microservices/microservice-payroll/package.json +27 -0
  41. package/microservices/microservice-payroll/src/cli/index.ts +643 -0
  42. package/microservices/microservice-payroll/src/db/database.ts +93 -0
  43. package/microservices/microservice-payroll/src/db/migrations.ts +95 -0
  44. package/microservices/microservice-payroll/src/db/payroll.ts +1377 -0
  45. package/microservices/microservice-payroll/src/index.ts +48 -0
  46. package/microservices/microservice-payroll/src/mcp/index.ts +666 -0
  47. package/microservices/microservice-shipping/package.json +27 -0
  48. package/microservices/microservice-shipping/src/cli/index.ts +606 -0
  49. package/microservices/microservice-shipping/src/db/database.ts +93 -0
  50. package/microservices/microservice-shipping/src/db/migrations.ts +69 -0
  51. package/microservices/microservice-shipping/src/db/shipping.ts +1093 -0
  52. package/microservices/microservice-shipping/src/index.ts +53 -0
  53. package/microservices/microservice-shipping/src/mcp/index.ts +533 -0
  54. package/microservices/microservice-social/package.json +27 -0
  55. package/microservices/microservice-social/src/cli/index.ts +689 -0
  56. package/microservices/microservice-social/src/db/database.ts +93 -0
  57. package/microservices/microservice-social/src/db/migrations.ts +88 -0
  58. package/microservices/microservice-social/src/db/social.ts +1046 -0
  59. package/microservices/microservice-social/src/index.ts +46 -0
  60. package/microservices/microservice-social/src/mcp/index.ts +655 -0
  61. package/microservices/microservice-subscriptions/package.json +27 -0
  62. package/microservices/microservice-subscriptions/src/cli/index.ts +715 -0
  63. package/microservices/microservice-subscriptions/src/db/database.ts +93 -0
  64. package/microservices/microservice-subscriptions/src/db/migrations.ts +125 -0
  65. package/microservices/microservice-subscriptions/src/db/subscriptions.ts +1256 -0
  66. package/microservices/microservice-subscriptions/src/index.ts +41 -0
  67. package/microservices/microservice-subscriptions/src/mcp/index.ts +631 -0
  68. package/package.json +1 -1
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@hasna/microservice-shipping",
3
+ "version": "0.0.1",
4
+ "description": "Shipping and order management microservice with SQLite — manage orders, shipments, tracking, and returns",
5
+ "type": "module",
6
+ "bin": {
7
+ "microservice-shipping": "./src/cli/index.ts",
8
+ "microservice-shipping-mcp": "./src/mcp/index.ts"
9
+ },
10
+ "exports": {
11
+ ".": "./src/index.ts"
12
+ },
13
+ "scripts": {
14
+ "dev": "bun run ./src/cli/index.ts",
15
+ "test": "bun test"
16
+ },
17
+ "dependencies": {
18
+ "@modelcontextprotocol/sdk": "^1.26.0",
19
+ "commander": "^12.1.0",
20
+ "zod": "^3.24.0"
21
+ },
22
+ "license": "Apache-2.0",
23
+ "publishConfig": {
24
+ "registry": "https://registry.npmjs.org",
25
+ "access": "public"
26
+ }
27
+ }
@@ -0,0 +1,606 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from "commander";
4
+ import { readFileSync } from "node:fs";
5
+ import {
6
+ createOrder,
7
+ getOrder,
8
+ listOrders,
9
+ updateOrder,
10
+ deleteOrder,
11
+ searchOrders,
12
+ listByStatus,
13
+ bulkImportOrders,
14
+ exportOrders,
15
+ getOrderTimeline,
16
+ } from "../db/shipping.js";
17
+ import {
18
+ createShipment,
19
+ getShipment,
20
+ getShipmentByTracking,
21
+ listShipments,
22
+ updateShipment,
23
+ } from "../db/shipping.js";
24
+ import {
25
+ createReturn,
26
+ listReturns,
27
+ updateReturn,
28
+ } from "../db/shipping.js";
29
+ import {
30
+ getShippingStats,
31
+ getCostsByCarrier,
32
+ getDeliveryStats,
33
+ listOverdueShipments,
34
+ getCustomerHistory,
35
+ getCarrierPerformance,
36
+ optimizeCost,
37
+ } from "../db/shipping.js";
38
+
39
+ const program = new Command();
40
+
41
+ program
42
+ .name("microservice-shipping")
43
+ .description("Shipping and order management microservice")
44
+ .version("0.0.1");
45
+
46
+ // --- Orders ---
47
+
48
+ const orderCmd = program.command("order").description("Order management");
49
+
50
+ orderCmd
51
+ .command("create")
52
+ .description("Create a new order")
53
+ .requiredOption("--customer-name <name>", "Customer name")
54
+ .option("--customer-email <email>", "Customer email")
55
+ .option("--address <json>", "Address as JSON {street,city,state,zip,country}")
56
+ .option("--items <json>", "Items as JSON array [{name,qty,weight,value}]")
57
+ .option("--currency <code>", "Currency code", "USD")
58
+ .option("--json", "Output as JSON", false)
59
+ .action((opts) => {
60
+ const address = opts.address ? JSON.parse(opts.address) : { street: "", city: "", state: "", zip: "", country: "" };
61
+ const items = opts.items ? JSON.parse(opts.items) : [];
62
+
63
+ const order = createOrder({
64
+ customer_name: opts.customerName,
65
+ customer_email: opts.customerEmail,
66
+ address,
67
+ items,
68
+ currency: opts.currency,
69
+ });
70
+
71
+ if (opts.json) {
72
+ console.log(JSON.stringify(order, null, 2));
73
+ } else {
74
+ console.log(`Created order: ${order.id} for ${order.customer_name} ($${order.total_value} ${order.currency})`);
75
+ }
76
+ });
77
+
78
+ orderCmd
79
+ .command("list")
80
+ .description("List orders")
81
+ .option("--status <status>", "Filter by status")
82
+ .option("--search <query>", "Search by customer name or email")
83
+ .option("--limit <n>", "Limit results")
84
+ .option("--json", "Output as JSON", false)
85
+ .action((opts) => {
86
+ const orders = listOrders({
87
+ status: opts.status,
88
+ search: opts.search,
89
+ limit: opts.limit ? parseInt(opts.limit) : undefined,
90
+ });
91
+
92
+ if (opts.json) {
93
+ console.log(JSON.stringify(orders, null, 2));
94
+ } else {
95
+ if (orders.length === 0) {
96
+ console.log("No orders found.");
97
+ return;
98
+ }
99
+ for (const o of orders) {
100
+ console.log(` [${o.status}] ${o.id.slice(0, 8)}... ${o.customer_name} — $${o.total_value} ${o.currency}`);
101
+ }
102
+ console.log(`\n${orders.length} order(s)`);
103
+ }
104
+ });
105
+
106
+ orderCmd
107
+ .command("get")
108
+ .description("Get an order by ID")
109
+ .argument("<id>", "Order ID")
110
+ .option("--json", "Output as JSON", false)
111
+ .action((id, opts) => {
112
+ const order = getOrder(id);
113
+ if (!order) {
114
+ console.error(`Order '${id}' not found.`);
115
+ process.exit(1);
116
+ }
117
+
118
+ if (opts.json) {
119
+ console.log(JSON.stringify(order, null, 2));
120
+ } else {
121
+ console.log(`Order: ${order.id}`);
122
+ console.log(` Customer: ${order.customer_name}`);
123
+ if (order.customer_email) console.log(` Email: ${order.customer_email}`);
124
+ console.log(` Status: ${order.status}`);
125
+ console.log(` Total: $${order.total_value} ${order.currency}`);
126
+ console.log(` Items: ${order.items.length}`);
127
+ console.log(` Address: ${order.address.street}, ${order.address.city}, ${order.address.state} ${order.address.zip}`);
128
+ }
129
+ });
130
+
131
+ orderCmd
132
+ .command("update")
133
+ .description("Update an order")
134
+ .argument("<id>", "Order ID")
135
+ .option("--customer-name <name>", "Customer name")
136
+ .option("--customer-email <email>", "Customer email")
137
+ .option("--status <status>", "Order status")
138
+ .option("--address <json>", "Address as JSON")
139
+ .option("--items <json>", "Items as JSON")
140
+ .option("--currency <code>", "Currency code")
141
+ .option("--json", "Output as JSON", false)
142
+ .action((id, opts) => {
143
+ const input: Record<string, unknown> = {};
144
+ if (opts.customerName !== undefined) input.customer_name = opts.customerName;
145
+ if (opts.customerEmail !== undefined) input.customer_email = opts.customerEmail;
146
+ if (opts.status !== undefined) input.status = opts.status;
147
+ if (opts.address !== undefined) input.address = JSON.parse(opts.address);
148
+ if (opts.items !== undefined) input.items = JSON.parse(opts.items);
149
+ if (opts.currency !== undefined) input.currency = opts.currency;
150
+
151
+ const order = updateOrder(id, input);
152
+ if (!order) {
153
+ console.error(`Order '${id}' not found.`);
154
+ process.exit(1);
155
+ }
156
+
157
+ if (opts.json) {
158
+ console.log(JSON.stringify(order, null, 2));
159
+ } else {
160
+ console.log(`Updated order: ${order.id} [${order.status}]`);
161
+ }
162
+ });
163
+
164
+ orderCmd
165
+ .command("import")
166
+ .description("Bulk import orders from a CSV file")
167
+ .requiredOption("--file <path>", "Path to CSV file")
168
+ .option("--json", "Output as JSON", false)
169
+ .action((opts) => {
170
+ const csvData = readFileSync(opts.file, "utf-8");
171
+ const result = bulkImportOrders(csvData);
172
+
173
+ if (opts.json) {
174
+ console.log(JSON.stringify(result, null, 2));
175
+ } else {
176
+ console.log(`Imported ${result.imported} order(s)`);
177
+ if (result.errors.length > 0) {
178
+ console.log(`Errors:`);
179
+ for (const err of result.errors) {
180
+ console.log(` Line ${err.line}: ${err.message}`);
181
+ }
182
+ }
183
+ }
184
+ });
185
+
186
+ orderCmd
187
+ .command("export")
188
+ .description("Export orders to CSV or JSON")
189
+ .option("--format <format>", "Output format (csv/json)", "csv")
190
+ .option("--from <date>", "Filter from date (YYYY-MM-DD)")
191
+ .option("--to <date>", "Filter to date (YYYY-MM-DD)")
192
+ .action((opts) => {
193
+ const output = exportOrders(opts.format as "csv" | "json", opts.from, opts.to);
194
+ console.log(output);
195
+ });
196
+
197
+ orderCmd
198
+ .command("timeline")
199
+ .description("Show full event timeline for an order")
200
+ .argument("<id>", "Order ID")
201
+ .option("--json", "Output as JSON", false)
202
+ .action((id, opts) => {
203
+ const timeline = getOrderTimeline(id);
204
+ if (timeline.length === 0) {
205
+ console.error(`No events found for order '${id}'.`);
206
+ process.exit(1);
207
+ }
208
+
209
+ if (opts.json) {
210
+ console.log(JSON.stringify(timeline, null, 2));
211
+ } else {
212
+ console.log(`Timeline for order ${id}:`);
213
+ for (const event of timeline) {
214
+ console.log(` [${event.timestamp}] ${event.type}: ${event.details}`);
215
+ }
216
+ }
217
+ });
218
+
219
+ // --- Ship (create shipment) ---
220
+
221
+ program
222
+ .command("ship")
223
+ .description("Create a shipment for an order")
224
+ .requiredOption("--order <id>", "Order ID")
225
+ .requiredOption("--carrier <carrier>", "Carrier (ups/fedex/usps/dhl)")
226
+ .option("--tracking <number>", "Tracking number")
227
+ .option("--service <service>", "Service level (ground/express/overnight)", "ground")
228
+ .option("--cost <amount>", "Shipping cost")
229
+ .option("--weight <kg>", "Package weight")
230
+ .option("--estimated-delivery <date>", "Estimated delivery date")
231
+ .option("--json", "Output as JSON", false)
232
+ .action((opts) => {
233
+ const shipment = createShipment({
234
+ order_id: opts.order,
235
+ carrier: opts.carrier,
236
+ tracking_number: opts.tracking,
237
+ service: opts.service,
238
+ cost: opts.cost ? parseFloat(opts.cost) : undefined,
239
+ weight: opts.weight ? parseFloat(opts.weight) : undefined,
240
+ estimated_delivery: opts.estimatedDelivery,
241
+ });
242
+
243
+ if (opts.json) {
244
+ console.log(JSON.stringify(shipment, null, 2));
245
+ } else {
246
+ console.log(`Created shipment: ${shipment.id} via ${shipment.carrier} (${shipment.service})`);
247
+ if (shipment.tracking_number) console.log(` Tracking: ${shipment.tracking_number}`);
248
+ }
249
+ });
250
+
251
+ // --- Track ---
252
+
253
+ program
254
+ .command("track")
255
+ .description("Track a shipment by tracking number or shipment ID")
256
+ .argument("<identifier>", "Tracking number or shipment ID")
257
+ .option("--json", "Output as JSON", false)
258
+ .action((identifier, opts) => {
259
+ let shipment = getShipmentByTracking(identifier);
260
+ if (!shipment) {
261
+ shipment = getShipment(identifier);
262
+ }
263
+ if (!shipment) {
264
+ console.error(`Shipment '${identifier}' not found.`);
265
+ process.exit(1);
266
+ }
267
+
268
+ if (opts.json) {
269
+ console.log(JSON.stringify(shipment, null, 2));
270
+ } else {
271
+ console.log(`Shipment: ${shipment.id}`);
272
+ console.log(` Carrier: ${shipment.carrier}`);
273
+ console.log(` Service: ${shipment.service}`);
274
+ console.log(` Status: ${shipment.status}`);
275
+ if (shipment.tracking_number) console.log(` Tracking: ${shipment.tracking_number}`);
276
+ if (shipment.shipped_at) console.log(` Shipped: ${shipment.shipped_at}`);
277
+ if (shipment.estimated_delivery) console.log(` ETA: ${shipment.estimated_delivery}`);
278
+ if (shipment.delivered_at) console.log(` Delivered: ${shipment.delivered_at}`);
279
+ }
280
+ });
281
+
282
+ // --- Shipments ---
283
+
284
+ const shipmentsCmd = program.command("shipments").description("Shipment management");
285
+
286
+ shipmentsCmd
287
+ .command("list")
288
+ .description("List shipments")
289
+ .option("--order <id>", "Filter by order ID")
290
+ .option("--carrier <carrier>", "Filter by carrier")
291
+ .option("--status <status>", "Filter by status")
292
+ .option("--limit <n>", "Limit results")
293
+ .option("--json", "Output as JSON", false)
294
+ .action((opts) => {
295
+ const shipments = listShipments({
296
+ order_id: opts.order,
297
+ carrier: opts.carrier,
298
+ status: opts.status,
299
+ limit: opts.limit ? parseInt(opts.limit) : undefined,
300
+ });
301
+
302
+ if (opts.json) {
303
+ console.log(JSON.stringify(shipments, null, 2));
304
+ } else {
305
+ if (shipments.length === 0) {
306
+ console.log("No shipments found.");
307
+ return;
308
+ }
309
+ for (const s of shipments) {
310
+ const tracking = s.tracking_number ? ` (${s.tracking_number})` : "";
311
+ console.log(` [${s.status}] ${s.id.slice(0, 8)}... ${s.carrier}/${s.service}${tracking}`);
312
+ }
313
+ console.log(`\n${shipments.length} shipment(s)`);
314
+ }
315
+ });
316
+
317
+ // --- Returns ---
318
+
319
+ const returnCmd = program.command("return").description("Return management");
320
+
321
+ returnCmd
322
+ .command("create")
323
+ .description("Create a return request")
324
+ .requiredOption("--order <id>", "Order ID")
325
+ .option("--reason <reason>", "Return reason")
326
+ .option("--auto-rma", "Auto-generate RMA code", false)
327
+ .option("--json", "Output as JSON", false)
328
+ .action((opts) => {
329
+ const ret = createReturn({
330
+ order_id: opts.order,
331
+ reason: opts.reason,
332
+ auto_rma: opts.autoRma,
333
+ });
334
+
335
+ if (opts.json) {
336
+ console.log(JSON.stringify(ret, null, 2));
337
+ } else {
338
+ console.log(`Created return: ${ret.id} for order ${ret.order_id} [${ret.status}]`);
339
+ if (ret.rma_code) console.log(` RMA Code: ${ret.rma_code}`);
340
+ }
341
+ });
342
+
343
+ returnCmd
344
+ .command("list")
345
+ .description("List returns")
346
+ .option("--order <id>", "Filter by order ID")
347
+ .option("--status <status>", "Filter by status")
348
+ .option("--limit <n>", "Limit results")
349
+ .option("--json", "Output as JSON", false)
350
+ .action((opts) => {
351
+ const returns = listReturns({
352
+ order_id: opts.order,
353
+ status: opts.status,
354
+ limit: opts.limit ? parseInt(opts.limit) : undefined,
355
+ });
356
+
357
+ if (opts.json) {
358
+ console.log(JSON.stringify(returns, null, 2));
359
+ } else {
360
+ if (returns.length === 0) {
361
+ console.log("No returns found.");
362
+ return;
363
+ }
364
+ for (const r of returns) {
365
+ const reason = r.reason ? ` — ${r.reason}` : "";
366
+ console.log(` [${r.status}] ${r.id.slice(0, 8)}... order ${r.order_id.slice(0, 8)}...${reason}`);
367
+ }
368
+ console.log(`\n${returns.length} return(s)`);
369
+ }
370
+ });
371
+
372
+ returnCmd
373
+ .command("process")
374
+ .description("Process a return (update status)")
375
+ .argument("<id>", "Return ID")
376
+ .requiredOption("--status <status>", "New status (approved/received/refunded)")
377
+ .option("--json", "Output as JSON", false)
378
+ .action((id, opts) => {
379
+ const ret = updateReturn(id, { status: opts.status });
380
+ if (!ret) {
381
+ console.error(`Return '${id}' not found.`);
382
+ process.exit(1);
383
+ }
384
+
385
+ if (opts.json) {
386
+ console.log(JSON.stringify(ret, null, 2));
387
+ } else {
388
+ console.log(`Updated return: ${ret.id} [${ret.status}]`);
389
+ }
390
+ });
391
+
392
+ // --- Carriers ---
393
+
394
+ program
395
+ .command("carriers")
396
+ .description("List supported carriers")
397
+ .option("--json", "Output as JSON", false)
398
+ .action((opts) => {
399
+ const carriers = [
400
+ { code: "ups", name: "UPS", services: ["ground", "express", "overnight"] },
401
+ { code: "fedex", name: "FedEx", services: ["ground", "express", "overnight"] },
402
+ { code: "usps", name: "USPS", services: ["ground", "express"] },
403
+ { code: "dhl", name: "DHL", services: ["ground", "express", "overnight"] },
404
+ ];
405
+
406
+ if (opts.json) {
407
+ console.log(JSON.stringify(carriers, null, 2));
408
+ } else {
409
+ for (const c of carriers) {
410
+ console.log(` ${c.code.toUpperCase()} (${c.name}) — ${c.services.join(", ")}`);
411
+ }
412
+ }
413
+ });
414
+
415
+ // --- Stats ---
416
+
417
+ const statsCmd = program.command("stats").description("Shipping statistics and analytics");
418
+
419
+ statsCmd
420
+ .command("overview")
421
+ .description("Show overall shipping statistics")
422
+ .option("--json", "Output as JSON", false)
423
+ .action((opts) => {
424
+ const stats = getShippingStats();
425
+
426
+ if (opts.json) {
427
+ console.log(JSON.stringify(stats, null, 2));
428
+ } else {
429
+ console.log("Shipping Statistics:");
430
+ console.log(` Orders: ${stats.total_orders}`);
431
+ for (const [status, count] of Object.entries(stats.orders_by_status)) {
432
+ console.log(` ${status}: ${count}`);
433
+ }
434
+ console.log(` Shipments: ${stats.total_shipments}`);
435
+ for (const [status, count] of Object.entries(stats.shipments_by_status)) {
436
+ console.log(` ${status}: ${count}`);
437
+ }
438
+ console.log(` Returns: ${stats.total_returns}`);
439
+ console.log(` Revenue: $${stats.total_revenue.toFixed(2)}`);
440
+ console.log(` Shipping Cost: $${stats.total_shipping_cost.toFixed(2)}`);
441
+ }
442
+ });
443
+
444
+ statsCmd
445
+ .command("delivery-times")
446
+ .description("Delivery timeline analytics per carrier/service")
447
+ .option("--carrier <carrier>", "Filter by carrier")
448
+ .option("--json", "Output as JSON", false)
449
+ .action((opts) => {
450
+ const stats = getDeliveryStats(opts.carrier);
451
+
452
+ if (opts.json) {
453
+ console.log(JSON.stringify(stats, null, 2));
454
+ } else {
455
+ if (stats.length === 0) {
456
+ console.log("No delivery data available (need shipments with shipped_at and delivered_at).");
457
+ return;
458
+ }
459
+ console.log("Delivery Timeline Analytics:");
460
+ for (const s of stats) {
461
+ console.log(` ${s.carrier.toUpperCase()}/${s.service}:`);
462
+ console.log(` Shipments: ${s.total_shipments} (${s.delivered_count} delivered)`);
463
+ console.log(` Avg delivery: ${s.avg_delivery_days.toFixed(1)} days`);
464
+ console.log(` On-time: ${s.on_time_pct.toFixed(1)}%, Late: ${s.late_pct.toFixed(1)}%`);
465
+ }
466
+ }
467
+ });
468
+
469
+ statsCmd
470
+ .command("carrier-performance")
471
+ .description("Rank carriers by on-time %, avg cost, avg delivery days")
472
+ .option("--json", "Output as JSON", false)
473
+ .action((opts) => {
474
+ const perf = getCarrierPerformance();
475
+
476
+ if (opts.json) {
477
+ console.log(JSON.stringify(perf, null, 2));
478
+ } else {
479
+ if (perf.length === 0) {
480
+ console.log("No carrier data available.");
481
+ return;
482
+ }
483
+ console.log("Carrier Performance (ranked by on-time %):");
484
+ for (const p of perf) {
485
+ console.log(` ${p.carrier.toUpperCase()}:`);
486
+ console.log(` Shipments: ${p.total_shipments} (${p.delivered_count} delivered)`);
487
+ console.log(` On-time: ${p.on_time_pct.toFixed(1)}%`);
488
+ console.log(` Avg cost: $${p.avg_cost.toFixed(2)}`);
489
+ console.log(` Avg delivery: ${p.avg_delivery_days.toFixed(1)} days`);
490
+ }
491
+ }
492
+ });
493
+
494
+ // --- Costs ---
495
+
496
+ program
497
+ .command("costs")
498
+ .description("Show shipping costs by carrier")
499
+ .option("--json", "Output as JSON", false)
500
+ .action((opts) => {
501
+ const costs = getCostsByCarrier();
502
+
503
+ if (opts.json) {
504
+ console.log(JSON.stringify(costs, null, 2));
505
+ } else {
506
+ if (costs.length === 0) {
507
+ console.log("No shipping cost data.");
508
+ return;
509
+ }
510
+ console.log("Costs by Carrier:");
511
+ for (const c of costs) {
512
+ console.log(` ${c.carrier.toUpperCase()}: $${c.total_cost.toFixed(2)} total, ${c.shipment_count} shipments, $${c.avg_cost.toFixed(2)} avg`);
513
+ }
514
+ }
515
+ });
516
+
517
+ // --- Late Delivery Alerts ---
518
+
519
+ program
520
+ .command("check-late")
521
+ .description("List overdue shipments past estimated delivery + grace days")
522
+ .option("--days <n>", "Grace days beyond estimated delivery", "0")
523
+ .option("--json", "Output as JSON", false)
524
+ .action((opts) => {
525
+ const overdue = listOverdueShipments(parseInt(opts.days));
526
+
527
+ if (opts.json) {
528
+ console.log(JSON.stringify(overdue, null, 2));
529
+ } else {
530
+ if (overdue.length === 0) {
531
+ console.log("No overdue shipments found.");
532
+ return;
533
+ }
534
+ console.log(`Overdue Shipments (grace: ${opts.days} day(s)):`);
535
+ for (const s of overdue) {
536
+ const tracking = s.tracking_number ? ` (${s.tracking_number})` : "";
537
+ console.log(` ${s.shipment_id.slice(0, 8)}... ${s.carrier}/${s.service}${tracking}`);
538
+ console.log(` ETA: ${s.estimated_delivery}, ${s.days_overdue} day(s) overdue [${s.status}]`);
539
+ }
540
+ console.log(`\n${overdue.length} overdue shipment(s)`);
541
+ }
542
+ });
543
+
544
+ // --- Customer History ---
545
+
546
+ program
547
+ .command("customer-history")
548
+ .description("Show all orders, shipments, and returns for a customer")
549
+ .argument("<email>", "Customer email")
550
+ .option("--json", "Output as JSON", false)
551
+ .action((email, opts) => {
552
+ const history = getCustomerHistory(email);
553
+
554
+ if (opts.json) {
555
+ console.log(JSON.stringify(history, null, 2));
556
+ } else {
557
+ console.log(`Customer History for ${email}:`);
558
+ console.log(` Orders: ${history.orders.length}`);
559
+ for (const o of history.orders) {
560
+ console.log(` [${o.status}] ${o.id.slice(0, 8)}... $${o.total_value} ${o.currency}`);
561
+ }
562
+ console.log(` Shipments: ${history.shipments.length}`);
563
+ for (const s of history.shipments) {
564
+ console.log(` [${s.status}] ${s.id.slice(0, 8)}... ${s.carrier}/${s.service}`);
565
+ }
566
+ console.log(` Returns: ${history.returns.length}`);
567
+ for (const r of history.returns) {
568
+ const rma = r.rma_code ? ` (${r.rma_code})` : "";
569
+ console.log(` [${r.status}] ${r.id.slice(0, 8)}...${rma}`);
570
+ }
571
+ }
572
+ });
573
+
574
+ // --- Cost Optimizer ---
575
+
576
+ program
577
+ .command("optimize-cost")
578
+ .description("Recommend cheapest carrier/service based on historical data")
579
+ .requiredOption("--weight <kg>", "Package weight in kg")
580
+ .option("--from <zip>", "Origin zip code")
581
+ .option("--to <zip>", "Destination zip code")
582
+ .option("--json", "Output as JSON", false)
583
+ .action((opts) => {
584
+ const recommendations = optimizeCost(
585
+ parseFloat(opts.weight),
586
+ opts.from,
587
+ opts.to
588
+ );
589
+
590
+ if (opts.json) {
591
+ console.log(JSON.stringify(recommendations, null, 2));
592
+ } else {
593
+ if (recommendations.length === 0) {
594
+ console.log("No historical shipping data found for the given weight range.");
595
+ return;
596
+ }
597
+ console.log(`Cost recommendations for ${opts.weight}kg package:`);
598
+ for (let i = 0; i < recommendations.length; i++) {
599
+ const r = recommendations[i];
600
+ const deliveryInfo = r.avg_delivery_days ? `, ~${r.avg_delivery_days.toFixed(1)} days` : "";
601
+ console.log(` ${i + 1}. ${r.carrier.toUpperCase()}/${r.service}: $${r.avg_cost.toFixed(2)} avg (${r.shipment_count} samples${deliveryInfo})`);
602
+ }
603
+ }
604
+ });
605
+
606
+ program.parse(process.argv);