@recur-tw/cli 0.1.4 → 0.2.0

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/AGENT.md CHANGED
@@ -15,6 +15,9 @@ capabilities:
15
15
  - checkout-sessions
16
16
  - schema-introspection
17
17
  - dry-run-validation
18
+ - command-discovery
19
+ - agent-help
20
+ - breadcrumb-hints
18
21
  ---
19
22
 
20
23
  # Recur CLI — Agent Instructions
@@ -27,16 +30,19 @@ capabilities:
27
30
  # 1. Auth (once)
28
31
  export RECUR_SECRET_KEY=sk_test_xxx
29
32
 
30
- # 2. Discover API shape
33
+ # 2. Discover all CLI commands (structured JSON)
34
+ recur commands --output json
35
+
36
+ # 3. Discover API shape
31
37
  recur schema --output json
32
38
 
33
- # 3. Drill into a specific action
39
+ # 4. Drill into a specific action
34
40
  recur schema products.create --output json
35
41
 
36
- # 4. Dry-run first (validates locally, no API call)
42
+ # 5. Dry-run first (validates locally, no API call)
37
43
  recur products create --json '{"name":"Test","price":299}' --dry-run --output json
38
44
 
39
- # 5. Execute
45
+ # 6. Execute
40
46
  recur products create --json '{"name":"Test","price":299}' --output json
41
47
  ```
42
48
 
@@ -63,6 +69,63 @@ export RECUR_SECRET_KEY=sk_test_xxx
63
69
  recur --key sk_test_xxx products list --output json
64
70
  ```
65
71
 
72
+ ## Discovery
73
+
74
+ The CLI provides three levels of programmatic discovery:
75
+
76
+ ### 1. Command Inventory (`recur commands`)
77
+
78
+ Lists all CLI commands, options, and arguments as structured JSON:
79
+
80
+ ```bash
81
+ recur commands --output json
82
+ # Returns: { name, version, global_options, commands: [{ name, description, subcommands }] }
83
+ ```
84
+
85
+ ### 2. Machine-Readable Help (`--agent`)
86
+
87
+ Get structured JSON help for any command:
88
+
89
+ ```bash
90
+ recur products --help --agent # Parent command: lists subcommands
91
+ recur products list --help --agent # Leaf command: lists options, arguments, api_schema ref
92
+ ```
93
+
94
+ Returns: `{ command, description, options, arguments, subcommands?, global_options, api_schema? }`
95
+
96
+ ### 3. Breadcrumb Hints (`_hints`)
97
+
98
+ JSON responses from get/create/update/cancel include a `_hints` array suggesting next commands:
99
+
100
+ ```bash
101
+ recur products get <id> --output json
102
+ # Response includes:
103
+ # "_hints": [
104
+ # { "description": "List subscriptions for this product", "command": "recur subscriptions list --product-id <id>" },
105
+ # { "description": "Create a checkout session", "command": "recur checkouts create --product-id <id>" }
106
+ # ]
107
+ ```
108
+
109
+ Hints only appear in `--output json`. Use `--fields` to exclude them if not needed.
110
+
111
+ ### Recommended Agent Workflow: Discovery → Schema → Action
112
+
113
+ ```bash
114
+ # Step 1: What commands exist?
115
+ recur commands --output json
116
+
117
+ # Step 2: What does this specific command need?
118
+ recur products create --help --agent
119
+
120
+ # Step 3: What does the API expect?
121
+ recur schema products.create --output json
122
+
123
+ # Step 4: Dry-run → Execute → Follow hints
124
+ recur products create --json '...' --dry-run --output json
125
+ recur products create --json '...' --output json
126
+ # → _hints will suggest next steps
127
+ ```
128
+
66
129
  ## Schema Introspection
67
130
 
68
131
  The CLI is self-describing. Use `recur schema` to discover everything at runtime.
package/dist/cli.mjs CHANGED
@@ -920,7 +920,7 @@ var RecurClient = class {
920
920
  }
921
921
  const headers = {
922
922
  Authorization: `Bearer ${this.secretKey}`,
923
- "User-Agent": `@recur-tw/cli/0.1.4`
923
+ "User-Agent": `@recur-tw/cli/0.2.0`
924
924
  };
925
925
  const hasBody = opts?.body !== void 0;
926
926
  if (hasBody) headers["Content-Type"] = "application/json";
@@ -996,7 +996,8 @@ const TABLE_HIDDEN_FIELDS = new Set([
996
996
  "product_family",
997
997
  "livemode",
998
998
  "created_at",
999
- "updated_at"
999
+ "updated_at",
1000
+ "_hints"
1000
1001
  ]);
1001
1002
  /**
1002
1003
  * Render data in the specified format.
@@ -1347,6 +1348,181 @@ function extractPaginatedList(response) {
1347
1348
  };
1348
1349
  }
1349
1350
  //#endregion
1351
+ //#region src/hints.ts
1352
+ const rules = {
1353
+ "products.get": (data) => {
1354
+ const id = data["id"];
1355
+ if (!id) return [];
1356
+ return [{
1357
+ description: "List subscriptions for this product",
1358
+ command: `recur subscriptions list --product-id ${id}`
1359
+ }, {
1360
+ description: "Create a checkout session",
1361
+ command: `recur checkouts create --product-id ${id}`
1362
+ }];
1363
+ },
1364
+ "products.create": (data) => {
1365
+ const id = data["id"];
1366
+ if (!id) return [];
1367
+ return [{
1368
+ description: "View created product",
1369
+ command: `recur products get ${id}`
1370
+ }, {
1371
+ description: "Create a checkout session",
1372
+ command: `recur checkouts create --product-id ${id}`
1373
+ }];
1374
+ },
1375
+ "products.update": (data) => {
1376
+ const id = data["id"];
1377
+ if (!id) return [];
1378
+ return [{
1379
+ description: "View updated product",
1380
+ command: `recur products get ${id}`
1381
+ }];
1382
+ },
1383
+ "products.archive": (data) => {
1384
+ const id = data["id"];
1385
+ if (!id) return [];
1386
+ return [{
1387
+ description: "Verify archived status",
1388
+ command: `recur products get ${id}`
1389
+ }, {
1390
+ description: "List archived products",
1391
+ command: "recur products list --status archived"
1392
+ }];
1393
+ },
1394
+ "customers.get": (data) => {
1395
+ const id = data["id"];
1396
+ if (!id) return [];
1397
+ return [
1398
+ {
1399
+ description: "List subscriptions",
1400
+ command: `recur subscriptions list --customer-id ${id}`
1401
+ },
1402
+ {
1403
+ description: "List orders",
1404
+ command: `recur orders list --customer-id ${id}`
1405
+ },
1406
+ {
1407
+ description: "List invoices",
1408
+ command: `recur invoices list --customer-id ${id}`
1409
+ }
1410
+ ];
1411
+ },
1412
+ "customers.update": (data) => {
1413
+ const id = data["id"];
1414
+ if (!id) return [];
1415
+ return [{
1416
+ description: "View updated customer",
1417
+ command: `recur customers get ${id}`
1418
+ }];
1419
+ },
1420
+ "subscriptions.get": (data) => {
1421
+ const id = data["id"];
1422
+ if (!id) return [];
1423
+ const hints = [{
1424
+ description: "List invoices for this subscription",
1425
+ command: `recur invoices list --subscription-id ${id}`
1426
+ }];
1427
+ if (data["status"] === "active" || data["status"] === "trialing") hints.push({
1428
+ description: "Preview cancellation",
1429
+ command: `recur subscriptions cancel ${id} --dry-run`
1430
+ });
1431
+ return hints;
1432
+ },
1433
+ "subscriptions.cancel": (data) => {
1434
+ const id = data["id"];
1435
+ if (!id) return [];
1436
+ return [{
1437
+ description: "Verify canceled status",
1438
+ command: `recur subscriptions get ${id}`
1439
+ }];
1440
+ },
1441
+ "orders.get": (data) => {
1442
+ const customerId = data["customer_id"];
1443
+ const hints = [];
1444
+ if (customerId) hints.push({
1445
+ description: "View customer",
1446
+ command: `recur customers get ${customerId}`
1447
+ });
1448
+ return hints;
1449
+ },
1450
+ "invoices.get": (data) => {
1451
+ const hints = [];
1452
+ const subscriptionId = data["subscription_id"];
1453
+ if (subscriptionId) hints.push({
1454
+ description: "View subscription",
1455
+ command: `recur subscriptions get ${subscriptionId}`
1456
+ });
1457
+ const customerId = data["customer_id"];
1458
+ if (customerId) hints.push({
1459
+ description: "View customer",
1460
+ command: `recur customers get ${customerId}`
1461
+ });
1462
+ return hints;
1463
+ },
1464
+ "checkouts.create": (data) => {
1465
+ const id = data["id"];
1466
+ if (!id) return [];
1467
+ return [{
1468
+ description: "Check session status",
1469
+ command: `recur checkouts get ${id}`
1470
+ }];
1471
+ },
1472
+ "checkouts.get": (data) => {
1473
+ const hints = [];
1474
+ const customerId = data["customer_id"];
1475
+ if (customerId) hints.push({
1476
+ description: "View customer",
1477
+ command: `recur customers get ${customerId}`
1478
+ });
1479
+ return hints;
1480
+ },
1481
+ "webhooks.create": (data) => {
1482
+ const id = data["id"];
1483
+ const url = data["url"];
1484
+ const hints = [];
1485
+ if (id) hints.push({
1486
+ description: "Send a test event",
1487
+ command: `recur webhooks test ${id}`
1488
+ });
1489
+ if (url) hints.push({
1490
+ description: "Listen for events locally",
1491
+ command: `recur webhooks listen ${url}`
1492
+ });
1493
+ return hints;
1494
+ },
1495
+ "webhooks.test": (data) => {
1496
+ if (!(data["webhook_id"] ?? data["id"])) return [];
1497
+ return [{
1498
+ description: "View webhook details",
1499
+ command: `recur webhooks list`
1500
+ }];
1501
+ }
1502
+ };
1503
+ /**
1504
+ * Generate contextual hints suggesting next commands.
1505
+ */
1506
+ function generateHints(resource, action, data) {
1507
+ const rule = rules[`${resource}.${action}`];
1508
+ return rule ? rule(data) : [];
1509
+ }
1510
+ /**
1511
+ * Attach _hints to a single-object response when format is json.
1512
+ * Returns data unchanged for non-json formats or if no hints apply.
1513
+ */
1514
+ function attachHints(data, resource, action, format) {
1515
+ if (format !== "json") return data;
1516
+ if (typeof data !== "object" || data === null || Array.isArray(data)) return data;
1517
+ const record = data;
1518
+ const hints = generateHints(resource, action, record);
1519
+ if (hints.length === 0) return data;
1520
+ return {
1521
+ ...record,
1522
+ _hints: hints
1523
+ };
1524
+ }
1525
+ //#endregion
1350
1526
  //#region src/commands/products.ts
1351
1527
  function getClient$6(opts) {
1352
1528
  return new RecurClient({
@@ -1395,7 +1571,7 @@ Examples:
1395
1571
  const client = getClient$6(opts);
1396
1572
  const validId = validateResourceId(id);
1397
1573
  const path = id.includes("-") ? `/v1/products/by-slug/${validId}` : `/v1/products/${validId}`;
1398
- const data = await client.get(path);
1574
+ const data = attachHints(await client.get(path), "products", "get", opts.output);
1399
1575
  render(opts.fields ? pickFields(data, opts.fields.split(",")) : data, getOutputOpts$6(opts));
1400
1576
  } catch (err) {
1401
1577
  handleError(err);
@@ -1437,7 +1613,7 @@ Examples:
1437
1613
  render(body, { format: "json" });
1438
1614
  return;
1439
1615
  }
1440
- render(await getClient$6(opts).post("/v1/products", body), getOutputOpts$6(opts));
1616
+ render(attachHints(await getClient$6(opts).post("/v1/products", body), "products", "create", opts.output), getOutputOpts$6(opts));
1441
1617
  } catch (err) {
1442
1618
  handleError(err);
1443
1619
  }
@@ -1470,7 +1646,7 @@ Examples:
1470
1646
  render(body, { format: "json" });
1471
1647
  return;
1472
1648
  }
1473
- render(await getClient$6(opts).patch(`/v1/products/${validId}`, body), getOutputOpts$6(opts));
1649
+ render(attachHints(await getClient$6(opts).patch(`/v1/products/${validId}`, body), "products", "update", opts.output), getOutputOpts$6(opts));
1474
1650
  } catch (err) {
1475
1651
  handleError(err);
1476
1652
  }
@@ -1483,7 +1659,7 @@ Examples:
1483
1659
  console.error(pc.yellow(`[dry-run] Would archive product ${validId}`));
1484
1660
  return;
1485
1661
  }
1486
- render(await getClient$6(opts).post(`/v1/products/${validId}/archive`), getOutputOpts$6(opts));
1662
+ render(attachHints(await getClient$6(opts).post(`/v1/products/${validId}/archive`), "products", "archive", opts.output), getOutputOpts$6(opts));
1487
1663
  } catch (err) {
1488
1664
  handleError(err);
1489
1665
  }
@@ -1575,7 +1751,7 @@ Examples:
1575
1751
  try {
1576
1752
  const opts = customers.optsWithGlobals();
1577
1753
  const validId = validateResourceId(id);
1578
- const data = await getClient$5(opts).get(`/v1/customers/${validId}`);
1754
+ const data = attachHints(await getClient$5(opts).get(`/v1/customers/${validId}`), "customers", "get", opts.output);
1579
1755
  render(opts.fields ? pickFields(data, opts.fields.split(",")) : data, getOutputOpts$5(opts));
1580
1756
  } catch (err) {
1581
1757
  handleError(err);
@@ -1600,7 +1776,7 @@ Examples:
1600
1776
  render(body, { format: "json" });
1601
1777
  return;
1602
1778
  }
1603
- render(await getClient$5(opts).patch(`/v1/customers/${validId}`, body), getOutputOpts$5(opts));
1779
+ render(attachHints(await getClient$5(opts).patch(`/v1/customers/${validId}`, body), "customers", "update", opts.output), getOutputOpts$5(opts));
1604
1780
  } catch (err) {
1605
1781
  handleError(err);
1606
1782
  }
@@ -1670,7 +1846,7 @@ Note: --immediately is a flag (no value). Do NOT use --immediately false; omit t
1670
1846
  try {
1671
1847
  const opts = subscriptions.optsWithGlobals();
1672
1848
  const validId = validateResourceId(id);
1673
- const data = await getClient$4(opts).get(`/v1/subscriptions/${validId}`);
1849
+ const data = attachHints(await getClient$4(opts).get(`/v1/subscriptions/${validId}`), "subscriptions", "get", opts.output);
1674
1850
  render(opts.fields ? pickFields(data, opts.fields.split(",")) : data, getOutputOpts$4(opts));
1675
1851
  } catch (err) {
1676
1852
  handleError(err);
@@ -1690,7 +1866,7 @@ Note: --immediately is a flag (no value). Do NOT use --immediately false; omit t
1690
1866
  render(body, { format: "json" });
1691
1867
  return;
1692
1868
  }
1693
- render(await getClient$4(opts).post(`/v1/subscriptions/${validId}/cancel`, body), getOutputOpts$4(opts));
1869
+ render(attachHints(await getClient$4(opts).post(`/v1/subscriptions/${validId}/cancel`, body), "subscriptions", "cancel", opts.output), getOutputOpts$4(opts));
1694
1870
  } catch (err) {
1695
1871
  handleError(err);
1696
1872
  }
@@ -1946,7 +2122,7 @@ Examples:
1946
2122
  render(body, { format: "json" });
1947
2123
  return;
1948
2124
  }
1949
- render(await getClient$3(opts).post("/v1/webhooks", body), getOutputOpts$3(opts));
2125
+ render(attachHints(await getClient$3(opts).post("/v1/webhooks", body), "webhooks", "create", opts.output), getOutputOpts$3(opts));
1950
2126
  } catch (err) {
1951
2127
  handleError(err);
1952
2128
  }
@@ -1959,7 +2135,7 @@ Examples:
1959
2135
  console.error(pc.yellow(`[dry-run] Would send test event "${cmdOpts.event}" to webhook ${validId}`));
1960
2136
  return;
1961
2137
  }
1962
- render(await getClient$3(opts).post(`/v1/webhooks/${validId}/test`, { eventType: cmdOpts.event }), getOutputOpts$3(opts));
2138
+ render(attachHints(await getClient$3(opts).post(`/v1/webhooks/${validId}/test`, { eventType: cmdOpts.event }), "webhooks", "test", opts.output), getOutputOpts$3(opts));
1963
2139
  } catch (err) {
1964
2140
  handleError(err);
1965
2141
  }
@@ -2037,7 +2213,7 @@ Examples:
2037
2213
  try {
2038
2214
  const opts = orders.optsWithGlobals();
2039
2215
  const validId = validateResourceId(id);
2040
- const data = await getClient$2(opts).get(`/v1/orders/${validId}`);
2216
+ const data = attachHints(await getClient$2(opts).get(`/v1/orders/${validId}`), "orders", "get", opts.output);
2041
2217
  render(opts.fields ? pickFields(data, opts.fields.split(",")) : data, getOutputOpts$2(opts));
2042
2218
  } catch (err) {
2043
2219
  handleError(err);
@@ -2103,7 +2279,7 @@ Examples:
2103
2279
  try {
2104
2280
  const opts = invoices.optsWithGlobals();
2105
2281
  const validId = validateResourceId(id);
2106
- const data = await getClient$1(opts).get(`/v1/invoices/${validId}`);
2282
+ const data = attachHints(await getClient$1(opts).get(`/v1/invoices/${validId}`), "invoices", "get", opts.output);
2107
2283
  render(opts.fields ? pickFields(data, opts.fields.split(",")) : data, getOutputOpts$1(opts));
2108
2284
  } catch (err) {
2109
2285
  handleError(err);
@@ -2166,7 +2342,7 @@ Examples:
2166
2342
  render(body, { format: "json" });
2167
2343
  return;
2168
2344
  }
2169
- render(await getClient(opts).post("/v1/checkouts", body), getOutputOpts(opts));
2345
+ render(attachHints(await getClient(opts).post("/v1/checkouts", body), "checkouts", "create", opts.output), getOutputOpts(opts));
2170
2346
  } catch (err) {
2171
2347
  handleError(err);
2172
2348
  }
@@ -2175,7 +2351,7 @@ Examples:
2175
2351
  try {
2176
2352
  const opts = checkouts.optsWithGlobals();
2177
2353
  const validId = validateResourceId(id);
2178
- const data = await getClient(opts).get(`/v1/checkouts/${validId}`);
2354
+ const data = attachHints(await getClient(opts).get(`/v1/checkouts/${validId}`), "checkouts", "get", opts.output);
2179
2355
  render(opts.fields ? pickFields(data, opts.fields.split(",")) : data, getOutputOpts(opts));
2180
2356
  } catch (err) {
2181
2357
  handleError(err);
@@ -2564,7 +2740,7 @@ Requires a Secret Key (sk_test_* or sk_live_*) via --key, RECUR_SECRET_KEY, or r
2564
2740
  });
2565
2741
  const server = new McpServer({
2566
2742
  name: "recur",
2567
- version: "0.1.4"
2743
+ version: "0.2.0"
2568
2744
  }, { capabilities: { tools: {} } });
2569
2745
  registerTools(server, client);
2570
2746
  const transport = new StdioServerTransport();
@@ -2578,9 +2754,146 @@ Requires a Secret Key (sk_test_* or sk_live_*) via --key, RECUR_SECRET_KEY, or r
2578
2754
  });
2579
2755
  }
2580
2756
  //#endregion
2757
+ //#region src/commands/commands.ts
2758
+ function extractOptions$1(cmd) {
2759
+ return cmd.options.filter((o) => !o.hidden).map((o) => {
2760
+ const def = {
2761
+ flags: o.flags,
2762
+ description: o.description
2763
+ };
2764
+ if (o.defaultValue !== void 0) def.default = o.defaultValue;
2765
+ return def;
2766
+ });
2767
+ }
2768
+ function extractArguments$1(cmd) {
2769
+ return cmd.registeredArguments.map((a) => ({
2770
+ name: a.name(),
2771
+ required: a.required,
2772
+ description: a.description
2773
+ }));
2774
+ }
2775
+ /**
2776
+ * Walk the Commander.js command tree and return a structured inventory.
2777
+ * Exported for reuse by --agent help.
2778
+ */
2779
+ function walkCommandTree(program) {
2780
+ const globalOptions = extractOptions$1(program);
2781
+ const commands = program.commands.filter((cmd) => !cmd.hidden).map((cmd) => {
2782
+ const subcommands = cmd.commands.filter((sub) => !sub.hidden).map((sub) => ({
2783
+ name: sub.name(),
2784
+ full_command: `recur ${cmd.name()} ${sub.name()}`,
2785
+ description: sub.description(),
2786
+ options: extractOptions$1(sub),
2787
+ arguments: extractArguments$1(sub)
2788
+ }));
2789
+ return {
2790
+ name: cmd.name(),
2791
+ description: cmd.description(),
2792
+ options: extractOptions$1(cmd),
2793
+ arguments: extractArguments$1(cmd),
2794
+ subcommands
2795
+ };
2796
+ });
2797
+ return {
2798
+ name: program.name(),
2799
+ version: "0.2.0",
2800
+ global_options: globalOptions,
2801
+ commands
2802
+ };
2803
+ }
2804
+ function registerCommandsCommand(program) {
2805
+ program.command("commands").description("List all CLI commands as structured JSON (for AI agents and automation)").action(() => {
2806
+ try {
2807
+ const opts = program.opts();
2808
+ render(walkCommandTree(program), { format: opts.output });
2809
+ } catch (err) {
2810
+ handleError(err);
2811
+ }
2812
+ });
2813
+ }
2814
+ //#endregion
2815
+ //#region src/agent-help.ts
2816
+ function extractOptions(cmd) {
2817
+ return cmd.options.filter((o) => !o.hidden).map((o) => {
2818
+ const def = {
2819
+ flags: o.flags,
2820
+ description: o.description
2821
+ };
2822
+ if (o.defaultValue !== void 0) def.default = o.defaultValue;
2823
+ return def;
2824
+ });
2825
+ }
2826
+ function extractArguments(cmd) {
2827
+ return cmd.registeredArguments.map((a) => ({
2828
+ name: a.name(),
2829
+ required: a.required,
2830
+ description: a.description
2831
+ }));
2832
+ }
2833
+ function getFullCommand(cmd) {
2834
+ const parts = [];
2835
+ let current = cmd;
2836
+ while (current) {
2837
+ parts.unshift(current.name());
2838
+ current = current.parent;
2839
+ }
2840
+ return parts.join(" ");
2841
+ }
2842
+ function findRoot(cmd) {
2843
+ let current = cmd;
2844
+ while (current.parent) current = current.parent;
2845
+ return current;
2846
+ }
2847
+ /**
2848
+ * Extract structured metadata from a Commander.js command.
2849
+ */
2850
+ function extractCommandMeta(cmd) {
2851
+ const fullCommand = getFullCommand(cmd);
2852
+ const root = findRoot(cmd);
2853
+ const meta = {
2854
+ command: fullCommand,
2855
+ description: cmd.description(),
2856
+ arguments: extractArguments(cmd),
2857
+ options: extractOptions(cmd),
2858
+ global_options: extractOptions(root)
2859
+ };
2860
+ const visibleSubs = cmd.commands.filter((sub) => !sub.hidden);
2861
+ if (visibleSubs.length > 0) meta.subcommands = visibleSubs.map((sub) => ({
2862
+ name: sub.name(),
2863
+ description: sub.description(),
2864
+ full_command: `${fullCommand} ${sub.name()}`
2865
+ }));
2866
+ const parts = fullCommand.split(" ");
2867
+ if (parts.length >= 3) {
2868
+ const resource = parts[1];
2869
+ const action = parts[2];
2870
+ if (getAction(`${resource}.${action}`)) meta.api_schema = `recur schema ${resource}.${action}`;
2871
+ }
2872
+ return meta;
2873
+ }
2874
+ /**
2875
+ * Configure Commander.js to output structured JSON when --agent is in argv.
2876
+ * Overrides helpInformation() on every command so that --agent suppresses
2877
+ * both the main help body and addHelpText('after', ...) output.
2878
+ */
2879
+ function configureAgentHelp(program) {
2880
+ if (!process.argv.includes("--agent")) return;
2881
+ function applyRecursive(cmd) {
2882
+ cmd.helpInformation.bind(cmd);
2883
+ cmd.helpInformation = function(contextOptions) {
2884
+ return JSON.stringify(extractCommandMeta(cmd), null, 2) + "\n";
2885
+ };
2886
+ const emitter = cmd;
2887
+ emitter.removeAllListeners("afterHelp");
2888
+ emitter.removeAllListeners("afterAllHelp");
2889
+ for (const sub of cmd.commands) applyRecursive(sub);
2890
+ }
2891
+ applyRecursive(program);
2892
+ }
2893
+ //#endregion
2581
2894
  //#region src/cli.ts
2582
2895
  const program = new Command();
2583
- program.name("recur").description("Recur CLI — Taiwan subscription payment platform.\nManage products, customers, subscriptions, webhooks, and more.\nAll commands require a Secret Key (sk_test_* or sk_live_*).").version("0.1.4").option("--key <secret-key>", "API secret key (sk_test_* or sk_live_*)").option("--profile <name>", "Use a named profile from ~/.recur/credentials.json").option("--base-url <url>", "API base URL (default: https://api.recur.tw)").option("--output <format>", "Output format: json, table, csv, ndjson (default: json when piped, table otherwise)").hook("preAction", (thisCommand) => {
2896
+ program.name("recur").description("Recur CLI — Taiwan subscription payment platform.\nManage products, customers, subscriptions, webhooks, and more.\nAll commands require a Secret Key (sk_test_* or sk_live_*).").version("0.2.0").option("--key <secret-key>", "API secret key (sk_test_* or sk_live_*)").option("--profile <name>", "Use a named profile from ~/.recur/credentials.json").option("--base-url <url>", "API base URL (default: https://api.recur.tw)").option("--output <format>", "Output format: json, table, csv, ndjson (default: json when piped, table otherwise)").hook("preAction", (thisCommand) => {
2584
2897
  const opts = thisCommand.opts();
2585
2898
  if (!opts.output) opts.output = process.stdout.isTTY ? "table" : "json";
2586
2899
  if (![
@@ -2592,7 +2905,7 @@ program.name("recur").description("Recur CLI — Taiwan subscription payment pla
2592
2905
  console.error(`Error: unsupported output format "${opts.output}". Use json, table, csv, or ndjson.`);
2593
2906
  process.exit(1);
2594
2907
  }
2595
- }).option("--fields <fields>", "Comma-separated fields to include (e.g. id,name,price)").option("--dry-run", "Validate locally without making API calls (write commands only)").option("--json <payload>", "Raw JSON request body, maps directly to API (e.g. '{\"name\":\"Pro\"}')");
2908
+ }).option("--fields <fields>", "Comma-separated fields to include (e.g. id,name,price)").option("--dry-run", "Validate locally without making API calls (write commands only)").option("--json <payload>", "Raw JSON request body, maps directly to API (e.g. '{\"name\":\"Pro\"}')").option("--agent", "Output help as structured JSON for AI agents (use with --help)");
2596
2909
  program.addHelpText("after", `
2597
2910
  Global options (--output, --fields, --json, --dry-run) work with ALL subcommands.
2598
2911
 
@@ -2620,6 +2933,8 @@ registerInvoicesCommand(program);
2620
2933
  registerCheckoutsCommand(program);
2621
2934
  registerSchemaCommand(program);
2622
2935
  registerMcpCommand(program);
2936
+ registerCommandsCommand(program);
2937
+ configureAgentHelp(program);
2623
2938
  program.parse();
2624
2939
  //#endregion
2625
2940
  export {};
package/dist/index.mjs CHANGED
@@ -195,7 +195,8 @@ const TABLE_HIDDEN_FIELDS = new Set([
195
195
  "product_family",
196
196
  "livemode",
197
197
  "created_at",
198
- "updated_at"
198
+ "updated_at",
199
+ "_hints"
199
200
  ]);
200
201
  /**
201
202
  * Render data in the specified format.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@recur-tw/cli",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "CLI for Recur - Taiwan's subscription payment platform",
5
5
  "keywords": [
6
6
  "recur",
@@ -41,14 +41,6 @@
41
41
  "AGENT.md",
42
42
  "LICENSE"
43
43
  ],
44
- "scripts": {
45
- "build": "tsdown",
46
- "dev": "tsdown --watch",
47
- "lint": "eslint src/",
48
- "typecheck": "tsc --noEmit",
49
- "test": "vitest run",
50
- "prepublishOnly": "pnpm build"
51
- },
52
44
  "dependencies": {
53
45
  "@modelcontextprotocol/sdk": "^1.27.1",
54
46
  "commander": "^12.1.0",
@@ -57,15 +49,22 @@
57
49
  },
58
50
  "devDependencies": {
59
51
  "@types/node": "^22.0.0",
60
- "@workspace/core": "workspace:*",
61
52
  "tsdown": "^0.21.1",
62
53
  "typescript": "^5.7.3",
63
- "vitest": "^4.0.18"
54
+ "vitest": "^4.0.18",
55
+ "@workspace/core": "0.0.1"
64
56
  },
65
57
  "engines": {
66
58
  "node": ">=22"
67
59
  },
68
60
  "publishConfig": {
69
61
  "access": "public"
62
+ },
63
+ "scripts": {
64
+ "build": "tsdown",
65
+ "dev": "tsdown --watch",
66
+ "lint": "eslint src/",
67
+ "typecheck": "tsc --noEmit",
68
+ "test": "vitest run"
70
69
  }
71
- }
70
+ }