@recur-tw/cli 0.1.4 → 0.2.1
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 +67 -4
- package/dist/cli.mjs +336 -20
- package/dist/index.mjs +4 -2
- package/package.json +17 -15
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
|
|
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
|
-
#
|
|
39
|
+
# 4. Drill into a specific action
|
|
34
40
|
recur schema products.create --output json
|
|
35
41
|
|
|
36
|
-
#
|
|
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
|
-
#
|
|
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
|
@@ -123,6 +123,7 @@ function resolveBaseUrl(opts) {
|
|
|
123
123
|
//#endregion
|
|
124
124
|
//#region src/errors.ts
|
|
125
125
|
var CLIError = class extends Error {
|
|
126
|
+
statusCode;
|
|
126
127
|
constructor(message, statusCode) {
|
|
127
128
|
super(message);
|
|
128
129
|
this.statusCode = statusCode;
|
|
@@ -920,7 +921,7 @@ var RecurClient = class {
|
|
|
920
921
|
}
|
|
921
922
|
const headers = {
|
|
922
923
|
Authorization: `Bearer ${this.secretKey}`,
|
|
923
|
-
"User-Agent": `@recur-tw/cli/0.1
|
|
924
|
+
"User-Agent": `@recur-tw/cli/0.2.1`
|
|
924
925
|
};
|
|
925
926
|
const hasBody = opts?.body !== void 0;
|
|
926
927
|
if (hasBody) headers["Content-Type"] = "application/json";
|
|
@@ -989,14 +990,15 @@ function sanitizeResponse(data) {
|
|
|
989
990
|
//#endregion
|
|
990
991
|
//#region src/output.ts
|
|
991
992
|
/** Fields to hide in table mode unless explicitly requested via --fields */
|
|
992
|
-
const TABLE_HIDDEN_FIELDS = new Set([
|
|
993
|
+
const TABLE_HIDDEN_FIELDS = /* @__PURE__ */ new Set([
|
|
993
994
|
"object",
|
|
994
995
|
"metadata",
|
|
995
996
|
"display_order",
|
|
996
997
|
"product_family",
|
|
997
998
|
"livemode",
|
|
998
999
|
"created_at",
|
|
999
|
-
"updated_at"
|
|
1000
|
+
"updated_at",
|
|
1001
|
+
"_hints"
|
|
1000
1002
|
]);
|
|
1001
1003
|
/**
|
|
1002
1004
|
* Render data in the specified format.
|
|
@@ -1347,6 +1349,181 @@ function extractPaginatedList(response) {
|
|
|
1347
1349
|
};
|
|
1348
1350
|
}
|
|
1349
1351
|
//#endregion
|
|
1352
|
+
//#region src/hints.ts
|
|
1353
|
+
const rules = {
|
|
1354
|
+
"products.get": (data) => {
|
|
1355
|
+
const id = data["id"];
|
|
1356
|
+
if (!id) return [];
|
|
1357
|
+
return [{
|
|
1358
|
+
description: "List subscriptions for this product",
|
|
1359
|
+
command: `recur subscriptions list --product-id ${id}`
|
|
1360
|
+
}, {
|
|
1361
|
+
description: "Create a checkout session",
|
|
1362
|
+
command: `recur checkouts create --product-id ${id}`
|
|
1363
|
+
}];
|
|
1364
|
+
},
|
|
1365
|
+
"products.create": (data) => {
|
|
1366
|
+
const id = data["id"];
|
|
1367
|
+
if (!id) return [];
|
|
1368
|
+
return [{
|
|
1369
|
+
description: "View created product",
|
|
1370
|
+
command: `recur products get ${id}`
|
|
1371
|
+
}, {
|
|
1372
|
+
description: "Create a checkout session",
|
|
1373
|
+
command: `recur checkouts create --product-id ${id}`
|
|
1374
|
+
}];
|
|
1375
|
+
},
|
|
1376
|
+
"products.update": (data) => {
|
|
1377
|
+
const id = data["id"];
|
|
1378
|
+
if (!id) return [];
|
|
1379
|
+
return [{
|
|
1380
|
+
description: "View updated product",
|
|
1381
|
+
command: `recur products get ${id}`
|
|
1382
|
+
}];
|
|
1383
|
+
},
|
|
1384
|
+
"products.archive": (data) => {
|
|
1385
|
+
const id = data["id"];
|
|
1386
|
+
if (!id) return [];
|
|
1387
|
+
return [{
|
|
1388
|
+
description: "Verify archived status",
|
|
1389
|
+
command: `recur products get ${id}`
|
|
1390
|
+
}, {
|
|
1391
|
+
description: "List archived products",
|
|
1392
|
+
command: "recur products list --status archived"
|
|
1393
|
+
}];
|
|
1394
|
+
},
|
|
1395
|
+
"customers.get": (data) => {
|
|
1396
|
+
const id = data["id"];
|
|
1397
|
+
if (!id) return [];
|
|
1398
|
+
return [
|
|
1399
|
+
{
|
|
1400
|
+
description: "List subscriptions",
|
|
1401
|
+
command: `recur subscriptions list --customer-id ${id}`
|
|
1402
|
+
},
|
|
1403
|
+
{
|
|
1404
|
+
description: "List orders",
|
|
1405
|
+
command: `recur orders list --customer-id ${id}`
|
|
1406
|
+
},
|
|
1407
|
+
{
|
|
1408
|
+
description: "List invoices",
|
|
1409
|
+
command: `recur invoices list --customer-id ${id}`
|
|
1410
|
+
}
|
|
1411
|
+
];
|
|
1412
|
+
},
|
|
1413
|
+
"customers.update": (data) => {
|
|
1414
|
+
const id = data["id"];
|
|
1415
|
+
if (!id) return [];
|
|
1416
|
+
return [{
|
|
1417
|
+
description: "View updated customer",
|
|
1418
|
+
command: `recur customers get ${id}`
|
|
1419
|
+
}];
|
|
1420
|
+
},
|
|
1421
|
+
"subscriptions.get": (data) => {
|
|
1422
|
+
const id = data["id"];
|
|
1423
|
+
if (!id) return [];
|
|
1424
|
+
const hints = [{
|
|
1425
|
+
description: "List invoices for this subscription",
|
|
1426
|
+
command: `recur invoices list --subscription-id ${id}`
|
|
1427
|
+
}];
|
|
1428
|
+
if (data["status"] === "active" || data["status"] === "trialing") hints.push({
|
|
1429
|
+
description: "Preview cancellation",
|
|
1430
|
+
command: `recur subscriptions cancel ${id} --dry-run`
|
|
1431
|
+
});
|
|
1432
|
+
return hints;
|
|
1433
|
+
},
|
|
1434
|
+
"subscriptions.cancel": (data) => {
|
|
1435
|
+
const id = data["id"];
|
|
1436
|
+
if (!id) return [];
|
|
1437
|
+
return [{
|
|
1438
|
+
description: "Verify canceled status",
|
|
1439
|
+
command: `recur subscriptions get ${id}`
|
|
1440
|
+
}];
|
|
1441
|
+
},
|
|
1442
|
+
"orders.get": (data) => {
|
|
1443
|
+
const customerId = data["customer_id"];
|
|
1444
|
+
const hints = [];
|
|
1445
|
+
if (customerId) hints.push({
|
|
1446
|
+
description: "View customer",
|
|
1447
|
+
command: `recur customers get ${customerId}`
|
|
1448
|
+
});
|
|
1449
|
+
return hints;
|
|
1450
|
+
},
|
|
1451
|
+
"invoices.get": (data) => {
|
|
1452
|
+
const hints = [];
|
|
1453
|
+
const subscriptionId = data["subscription_id"];
|
|
1454
|
+
if (subscriptionId) hints.push({
|
|
1455
|
+
description: "View subscription",
|
|
1456
|
+
command: `recur subscriptions get ${subscriptionId}`
|
|
1457
|
+
});
|
|
1458
|
+
const customerId = data["customer_id"];
|
|
1459
|
+
if (customerId) hints.push({
|
|
1460
|
+
description: "View customer",
|
|
1461
|
+
command: `recur customers get ${customerId}`
|
|
1462
|
+
});
|
|
1463
|
+
return hints;
|
|
1464
|
+
},
|
|
1465
|
+
"checkouts.create": (data) => {
|
|
1466
|
+
const id = data["id"];
|
|
1467
|
+
if (!id) return [];
|
|
1468
|
+
return [{
|
|
1469
|
+
description: "Check session status",
|
|
1470
|
+
command: `recur checkouts get ${id}`
|
|
1471
|
+
}];
|
|
1472
|
+
},
|
|
1473
|
+
"checkouts.get": (data) => {
|
|
1474
|
+
const hints = [];
|
|
1475
|
+
const customerId = data["customer_id"];
|
|
1476
|
+
if (customerId) hints.push({
|
|
1477
|
+
description: "View customer",
|
|
1478
|
+
command: `recur customers get ${customerId}`
|
|
1479
|
+
});
|
|
1480
|
+
return hints;
|
|
1481
|
+
},
|
|
1482
|
+
"webhooks.create": (data) => {
|
|
1483
|
+
const id = data["id"];
|
|
1484
|
+
const url = data["url"];
|
|
1485
|
+
const hints = [];
|
|
1486
|
+
if (id) hints.push({
|
|
1487
|
+
description: "Send a test event",
|
|
1488
|
+
command: `recur webhooks test ${id}`
|
|
1489
|
+
});
|
|
1490
|
+
if (url) hints.push({
|
|
1491
|
+
description: "Listen for events locally",
|
|
1492
|
+
command: `recur webhooks listen ${url}`
|
|
1493
|
+
});
|
|
1494
|
+
return hints;
|
|
1495
|
+
},
|
|
1496
|
+
"webhooks.test": (data) => {
|
|
1497
|
+
if (!(data["webhook_id"] ?? data["id"])) return [];
|
|
1498
|
+
return [{
|
|
1499
|
+
description: "View webhook details",
|
|
1500
|
+
command: `recur webhooks list`
|
|
1501
|
+
}];
|
|
1502
|
+
}
|
|
1503
|
+
};
|
|
1504
|
+
/**
|
|
1505
|
+
* Generate contextual hints suggesting next commands.
|
|
1506
|
+
*/
|
|
1507
|
+
function generateHints(resource, action, data) {
|
|
1508
|
+
const rule = rules[`${resource}.${action}`];
|
|
1509
|
+
return rule ? rule(data) : [];
|
|
1510
|
+
}
|
|
1511
|
+
/**
|
|
1512
|
+
* Attach _hints to a single-object response when format is json.
|
|
1513
|
+
* Returns data unchanged for non-json formats or if no hints apply.
|
|
1514
|
+
*/
|
|
1515
|
+
function attachHints(data, resource, action, format) {
|
|
1516
|
+
if (format !== "json") return data;
|
|
1517
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) return data;
|
|
1518
|
+
const record = data;
|
|
1519
|
+
const hints = generateHints(resource, action, record);
|
|
1520
|
+
if (hints.length === 0) return data;
|
|
1521
|
+
return {
|
|
1522
|
+
...record,
|
|
1523
|
+
_hints: hints
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
//#endregion
|
|
1350
1527
|
//#region src/commands/products.ts
|
|
1351
1528
|
function getClient$6(opts) {
|
|
1352
1529
|
return new RecurClient({
|
|
@@ -1395,7 +1572,7 @@ Examples:
|
|
|
1395
1572
|
const client = getClient$6(opts);
|
|
1396
1573
|
const validId = validateResourceId(id);
|
|
1397
1574
|
const path = id.includes("-") ? `/v1/products/by-slug/${validId}` : `/v1/products/${validId}`;
|
|
1398
|
-
const data = await client.get(path);
|
|
1575
|
+
const data = attachHints(await client.get(path), "products", "get", opts.output);
|
|
1399
1576
|
render(opts.fields ? pickFields(data, opts.fields.split(",")) : data, getOutputOpts$6(opts));
|
|
1400
1577
|
} catch (err) {
|
|
1401
1578
|
handleError(err);
|
|
@@ -1437,7 +1614,7 @@ Examples:
|
|
|
1437
1614
|
render(body, { format: "json" });
|
|
1438
1615
|
return;
|
|
1439
1616
|
}
|
|
1440
|
-
render(await getClient$6(opts).post("/v1/products", body), getOutputOpts$6(opts));
|
|
1617
|
+
render(attachHints(await getClient$6(opts).post("/v1/products", body), "products", "create", opts.output), getOutputOpts$6(opts));
|
|
1441
1618
|
} catch (err) {
|
|
1442
1619
|
handleError(err);
|
|
1443
1620
|
}
|
|
@@ -1470,7 +1647,7 @@ Examples:
|
|
|
1470
1647
|
render(body, { format: "json" });
|
|
1471
1648
|
return;
|
|
1472
1649
|
}
|
|
1473
|
-
render(await getClient$6(opts).patch(`/v1/products/${validId}`, body), getOutputOpts$6(opts));
|
|
1650
|
+
render(attachHints(await getClient$6(opts).patch(`/v1/products/${validId}`, body), "products", "update", opts.output), getOutputOpts$6(opts));
|
|
1474
1651
|
} catch (err) {
|
|
1475
1652
|
handleError(err);
|
|
1476
1653
|
}
|
|
@@ -1483,7 +1660,7 @@ Examples:
|
|
|
1483
1660
|
console.error(pc.yellow(`[dry-run] Would archive product ${validId}`));
|
|
1484
1661
|
return;
|
|
1485
1662
|
}
|
|
1486
|
-
render(await getClient$6(opts).post(`/v1/products/${validId}/archive`), getOutputOpts$6(opts));
|
|
1663
|
+
render(attachHints(await getClient$6(opts).post(`/v1/products/${validId}/archive`), "products", "archive", opts.output), getOutputOpts$6(opts));
|
|
1487
1664
|
} catch (err) {
|
|
1488
1665
|
handleError(err);
|
|
1489
1666
|
}
|
|
@@ -1575,7 +1752,7 @@ Examples:
|
|
|
1575
1752
|
try {
|
|
1576
1753
|
const opts = customers.optsWithGlobals();
|
|
1577
1754
|
const validId = validateResourceId(id);
|
|
1578
|
-
const data = await getClient$5(opts).get(`/v1/customers/${validId}`);
|
|
1755
|
+
const data = attachHints(await getClient$5(opts).get(`/v1/customers/${validId}`), "customers", "get", opts.output);
|
|
1579
1756
|
render(opts.fields ? pickFields(data, opts.fields.split(",")) : data, getOutputOpts$5(opts));
|
|
1580
1757
|
} catch (err) {
|
|
1581
1758
|
handleError(err);
|
|
@@ -1600,7 +1777,7 @@ Examples:
|
|
|
1600
1777
|
render(body, { format: "json" });
|
|
1601
1778
|
return;
|
|
1602
1779
|
}
|
|
1603
|
-
render(await getClient$5(opts).patch(`/v1/customers/${validId}`, body), getOutputOpts$5(opts));
|
|
1780
|
+
render(attachHints(await getClient$5(opts).patch(`/v1/customers/${validId}`, body), "customers", "update", opts.output), getOutputOpts$5(opts));
|
|
1604
1781
|
} catch (err) {
|
|
1605
1782
|
handleError(err);
|
|
1606
1783
|
}
|
|
@@ -1670,7 +1847,7 @@ Note: --immediately is a flag (no value). Do NOT use --immediately false; omit t
|
|
|
1670
1847
|
try {
|
|
1671
1848
|
const opts = subscriptions.optsWithGlobals();
|
|
1672
1849
|
const validId = validateResourceId(id);
|
|
1673
|
-
const data = await getClient$4(opts).get(`/v1/subscriptions/${validId}`);
|
|
1850
|
+
const data = attachHints(await getClient$4(opts).get(`/v1/subscriptions/${validId}`), "subscriptions", "get", opts.output);
|
|
1674
1851
|
render(opts.fields ? pickFields(data, opts.fields.split(",")) : data, getOutputOpts$4(opts));
|
|
1675
1852
|
} catch (err) {
|
|
1676
1853
|
handleError(err);
|
|
@@ -1690,7 +1867,7 @@ Note: --immediately is a flag (no value). Do NOT use --immediately false; omit t
|
|
|
1690
1867
|
render(body, { format: "json" });
|
|
1691
1868
|
return;
|
|
1692
1869
|
}
|
|
1693
|
-
render(await getClient$4(opts).post(`/v1/subscriptions/${validId}/cancel`, body), getOutputOpts$4(opts));
|
|
1870
|
+
render(attachHints(await getClient$4(opts).post(`/v1/subscriptions/${validId}/cancel`, body), "subscriptions", "cancel", opts.output), getOutputOpts$4(opts));
|
|
1694
1871
|
} catch (err) {
|
|
1695
1872
|
handleError(err);
|
|
1696
1873
|
}
|
|
@@ -1946,7 +2123,7 @@ Examples:
|
|
|
1946
2123
|
render(body, { format: "json" });
|
|
1947
2124
|
return;
|
|
1948
2125
|
}
|
|
1949
|
-
render(await getClient$3(opts).post("/v1/webhooks", body), getOutputOpts$3(opts));
|
|
2126
|
+
render(attachHints(await getClient$3(opts).post("/v1/webhooks", body), "webhooks", "create", opts.output), getOutputOpts$3(opts));
|
|
1950
2127
|
} catch (err) {
|
|
1951
2128
|
handleError(err);
|
|
1952
2129
|
}
|
|
@@ -1959,7 +2136,7 @@ Examples:
|
|
|
1959
2136
|
console.error(pc.yellow(`[dry-run] Would send test event "${cmdOpts.event}" to webhook ${validId}`));
|
|
1960
2137
|
return;
|
|
1961
2138
|
}
|
|
1962
|
-
render(await getClient$3(opts).post(`/v1/webhooks/${validId}/test`, { eventType: cmdOpts.event }), getOutputOpts$3(opts));
|
|
2139
|
+
render(attachHints(await getClient$3(opts).post(`/v1/webhooks/${validId}/test`, { eventType: cmdOpts.event }), "webhooks", "test", opts.output), getOutputOpts$3(opts));
|
|
1963
2140
|
} catch (err) {
|
|
1964
2141
|
handleError(err);
|
|
1965
2142
|
}
|
|
@@ -2037,7 +2214,7 @@ Examples:
|
|
|
2037
2214
|
try {
|
|
2038
2215
|
const opts = orders.optsWithGlobals();
|
|
2039
2216
|
const validId = validateResourceId(id);
|
|
2040
|
-
const data = await getClient$2(opts).get(`/v1/orders/${validId}`);
|
|
2217
|
+
const data = attachHints(await getClient$2(opts).get(`/v1/orders/${validId}`), "orders", "get", opts.output);
|
|
2041
2218
|
render(opts.fields ? pickFields(data, opts.fields.split(",")) : data, getOutputOpts$2(opts));
|
|
2042
2219
|
} catch (err) {
|
|
2043
2220
|
handleError(err);
|
|
@@ -2103,7 +2280,7 @@ Examples:
|
|
|
2103
2280
|
try {
|
|
2104
2281
|
const opts = invoices.optsWithGlobals();
|
|
2105
2282
|
const validId = validateResourceId(id);
|
|
2106
|
-
const data = await getClient$1(opts).get(`/v1/invoices/${validId}`);
|
|
2283
|
+
const data = attachHints(await getClient$1(opts).get(`/v1/invoices/${validId}`), "invoices", "get", opts.output);
|
|
2107
2284
|
render(opts.fields ? pickFields(data, opts.fields.split(",")) : data, getOutputOpts$1(opts));
|
|
2108
2285
|
} catch (err) {
|
|
2109
2286
|
handleError(err);
|
|
@@ -2166,7 +2343,7 @@ Examples:
|
|
|
2166
2343
|
render(body, { format: "json" });
|
|
2167
2344
|
return;
|
|
2168
2345
|
}
|
|
2169
|
-
render(await getClient(opts).post("/v1/checkouts", body), getOutputOpts(opts));
|
|
2346
|
+
render(attachHints(await getClient(opts).post("/v1/checkouts", body), "checkouts", "create", opts.output), getOutputOpts(opts));
|
|
2170
2347
|
} catch (err) {
|
|
2171
2348
|
handleError(err);
|
|
2172
2349
|
}
|
|
@@ -2175,7 +2352,7 @@ Examples:
|
|
|
2175
2352
|
try {
|
|
2176
2353
|
const opts = checkouts.optsWithGlobals();
|
|
2177
2354
|
const validId = validateResourceId(id);
|
|
2178
|
-
const data = await getClient(opts).get(`/v1/checkouts/${validId}`);
|
|
2355
|
+
const data = attachHints(await getClient(opts).get(`/v1/checkouts/${validId}`), "checkouts", "get", opts.output);
|
|
2179
2356
|
render(opts.fields ? pickFields(data, opts.fields.split(",")) : data, getOutputOpts(opts));
|
|
2180
2357
|
} catch (err) {
|
|
2181
2358
|
handleError(err);
|
|
@@ -2564,7 +2741,7 @@ Requires a Secret Key (sk_test_* or sk_live_*) via --key, RECUR_SECRET_KEY, or r
|
|
|
2564
2741
|
});
|
|
2565
2742
|
const server = new McpServer({
|
|
2566
2743
|
name: "recur",
|
|
2567
|
-
version: "0.1
|
|
2744
|
+
version: "0.2.1"
|
|
2568
2745
|
}, { capabilities: { tools: {} } });
|
|
2569
2746
|
registerTools(server, client);
|
|
2570
2747
|
const transport = new StdioServerTransport();
|
|
@@ -2578,9 +2755,146 @@ Requires a Secret Key (sk_test_* or sk_live_*) via --key, RECUR_SECRET_KEY, or r
|
|
|
2578
2755
|
});
|
|
2579
2756
|
}
|
|
2580
2757
|
//#endregion
|
|
2758
|
+
//#region src/commands/commands.ts
|
|
2759
|
+
function extractOptions$1(cmd) {
|
|
2760
|
+
return cmd.options.filter((o) => !o.hidden).map((o) => {
|
|
2761
|
+
const def = {
|
|
2762
|
+
flags: o.flags,
|
|
2763
|
+
description: o.description
|
|
2764
|
+
};
|
|
2765
|
+
if (o.defaultValue !== void 0) def.default = o.defaultValue;
|
|
2766
|
+
return def;
|
|
2767
|
+
});
|
|
2768
|
+
}
|
|
2769
|
+
function extractArguments$1(cmd) {
|
|
2770
|
+
return cmd.registeredArguments.map((a) => ({
|
|
2771
|
+
name: a.name(),
|
|
2772
|
+
required: a.required,
|
|
2773
|
+
description: a.description
|
|
2774
|
+
}));
|
|
2775
|
+
}
|
|
2776
|
+
/**
|
|
2777
|
+
* Walk the Commander.js command tree and return a structured inventory.
|
|
2778
|
+
* Exported for reuse by --agent help.
|
|
2779
|
+
*/
|
|
2780
|
+
function walkCommandTree(program) {
|
|
2781
|
+
const globalOptions = extractOptions$1(program);
|
|
2782
|
+
const commands = program.commands.filter((cmd) => !cmd.hidden).map((cmd) => {
|
|
2783
|
+
const subcommands = cmd.commands.filter((sub) => !sub.hidden).map((sub) => ({
|
|
2784
|
+
name: sub.name(),
|
|
2785
|
+
full_command: `recur ${cmd.name()} ${sub.name()}`,
|
|
2786
|
+
description: sub.description(),
|
|
2787
|
+
options: extractOptions$1(sub),
|
|
2788
|
+
arguments: extractArguments$1(sub)
|
|
2789
|
+
}));
|
|
2790
|
+
return {
|
|
2791
|
+
name: cmd.name(),
|
|
2792
|
+
description: cmd.description(),
|
|
2793
|
+
options: extractOptions$1(cmd),
|
|
2794
|
+
arguments: extractArguments$1(cmd),
|
|
2795
|
+
subcommands
|
|
2796
|
+
};
|
|
2797
|
+
});
|
|
2798
|
+
return {
|
|
2799
|
+
name: program.name(),
|
|
2800
|
+
version: "0.2.1",
|
|
2801
|
+
global_options: globalOptions,
|
|
2802
|
+
commands
|
|
2803
|
+
};
|
|
2804
|
+
}
|
|
2805
|
+
function registerCommandsCommand(program) {
|
|
2806
|
+
program.command("commands").description("List all CLI commands as structured JSON (for AI agents and automation)").action(() => {
|
|
2807
|
+
try {
|
|
2808
|
+
const opts = program.opts();
|
|
2809
|
+
render(walkCommandTree(program), { format: opts.output });
|
|
2810
|
+
} catch (err) {
|
|
2811
|
+
handleError(err);
|
|
2812
|
+
}
|
|
2813
|
+
});
|
|
2814
|
+
}
|
|
2815
|
+
//#endregion
|
|
2816
|
+
//#region src/agent-help.ts
|
|
2817
|
+
function extractOptions(cmd) {
|
|
2818
|
+
return cmd.options.filter((o) => !o.hidden).map((o) => {
|
|
2819
|
+
const def = {
|
|
2820
|
+
flags: o.flags,
|
|
2821
|
+
description: o.description
|
|
2822
|
+
};
|
|
2823
|
+
if (o.defaultValue !== void 0) def.default = o.defaultValue;
|
|
2824
|
+
return def;
|
|
2825
|
+
});
|
|
2826
|
+
}
|
|
2827
|
+
function extractArguments(cmd) {
|
|
2828
|
+
return cmd.registeredArguments.map((a) => ({
|
|
2829
|
+
name: a.name(),
|
|
2830
|
+
required: a.required,
|
|
2831
|
+
description: a.description
|
|
2832
|
+
}));
|
|
2833
|
+
}
|
|
2834
|
+
function getFullCommand(cmd) {
|
|
2835
|
+
const parts = [];
|
|
2836
|
+
let current = cmd;
|
|
2837
|
+
while (current) {
|
|
2838
|
+
parts.unshift(current.name());
|
|
2839
|
+
current = current.parent;
|
|
2840
|
+
}
|
|
2841
|
+
return parts.join(" ");
|
|
2842
|
+
}
|
|
2843
|
+
function findRoot(cmd) {
|
|
2844
|
+
let current = cmd;
|
|
2845
|
+
while (current.parent) current = current.parent;
|
|
2846
|
+
return current;
|
|
2847
|
+
}
|
|
2848
|
+
/**
|
|
2849
|
+
* Extract structured metadata from a Commander.js command.
|
|
2850
|
+
*/
|
|
2851
|
+
function extractCommandMeta(cmd) {
|
|
2852
|
+
const fullCommand = getFullCommand(cmd);
|
|
2853
|
+
const root = findRoot(cmd);
|
|
2854
|
+
const meta = {
|
|
2855
|
+
command: fullCommand,
|
|
2856
|
+
description: cmd.description(),
|
|
2857
|
+
arguments: extractArguments(cmd),
|
|
2858
|
+
options: extractOptions(cmd),
|
|
2859
|
+
global_options: extractOptions(root)
|
|
2860
|
+
};
|
|
2861
|
+
const visibleSubs = cmd.commands.filter((sub) => !sub.hidden);
|
|
2862
|
+
if (visibleSubs.length > 0) meta.subcommands = visibleSubs.map((sub) => ({
|
|
2863
|
+
name: sub.name(),
|
|
2864
|
+
description: sub.description(),
|
|
2865
|
+
full_command: `${fullCommand} ${sub.name()}`
|
|
2866
|
+
}));
|
|
2867
|
+
const parts = fullCommand.split(" ");
|
|
2868
|
+
if (parts.length >= 3) {
|
|
2869
|
+
const resource = parts[1];
|
|
2870
|
+
const action = parts[2];
|
|
2871
|
+
if (getAction(`${resource}.${action}`)) meta.api_schema = `recur schema ${resource}.${action}`;
|
|
2872
|
+
}
|
|
2873
|
+
return meta;
|
|
2874
|
+
}
|
|
2875
|
+
/**
|
|
2876
|
+
* Configure Commander.js to output structured JSON when --agent is in argv.
|
|
2877
|
+
* Overrides helpInformation() on every command so that --agent suppresses
|
|
2878
|
+
* both the main help body and addHelpText('after', ...) output.
|
|
2879
|
+
*/
|
|
2880
|
+
function configureAgentHelp(program) {
|
|
2881
|
+
if (!process.argv.includes("--agent")) return;
|
|
2882
|
+
function applyRecursive(cmd) {
|
|
2883
|
+
cmd.helpInformation.bind(cmd);
|
|
2884
|
+
cmd.helpInformation = function(contextOptions) {
|
|
2885
|
+
return JSON.stringify(extractCommandMeta(cmd), null, 2) + "\n";
|
|
2886
|
+
};
|
|
2887
|
+
const emitter = cmd;
|
|
2888
|
+
emitter.removeAllListeners("afterHelp");
|
|
2889
|
+
emitter.removeAllListeners("afterAllHelp");
|
|
2890
|
+
for (const sub of cmd.commands) applyRecursive(sub);
|
|
2891
|
+
}
|
|
2892
|
+
applyRecursive(program);
|
|
2893
|
+
}
|
|
2894
|
+
//#endregion
|
|
2581
2895
|
//#region src/cli.ts
|
|
2582
2896
|
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
|
|
2897
|
+
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.1").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
2898
|
const opts = thisCommand.opts();
|
|
2585
2899
|
if (!opts.output) opts.output = process.stdout.isTTY ? "table" : "json";
|
|
2586
2900
|
if (![
|
|
@@ -2592,7 +2906,7 @@ program.name("recur").description("Recur CLI — Taiwan subscription payment pla
|
|
|
2592
2906
|
console.error(`Error: unsupported output format "${opts.output}". Use json, table, csv, or ndjson.`);
|
|
2593
2907
|
process.exit(1);
|
|
2594
2908
|
}
|
|
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\"}')");
|
|
2909
|
+
}).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
2910
|
program.addHelpText("after", `
|
|
2597
2911
|
Global options (--output, --fields, --json, --dry-run) work with ALL subcommands.
|
|
2598
2912
|
|
|
@@ -2620,6 +2934,8 @@ registerInvoicesCommand(program);
|
|
|
2620
2934
|
registerCheckoutsCommand(program);
|
|
2621
2935
|
registerSchemaCommand(program);
|
|
2622
2936
|
registerMcpCommand(program);
|
|
2937
|
+
registerCommandsCommand(program);
|
|
2938
|
+
configureAgentHelp(program);
|
|
2623
2939
|
program.parse();
|
|
2624
2940
|
//#endregion
|
|
2625
2941
|
export {};
|
package/dist/index.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
//#region src/errors.ts
|
|
6
6
|
var CLIError = class extends Error {
|
|
7
|
+
statusCode;
|
|
7
8
|
constructor(message, statusCode) {
|
|
8
9
|
super(message);
|
|
9
10
|
this.statusCode = statusCode;
|
|
@@ -188,14 +189,15 @@ function resolveBaseUrl(opts) {
|
|
|
188
189
|
//#endregion
|
|
189
190
|
//#region src/output.ts
|
|
190
191
|
/** Fields to hide in table mode unless explicitly requested via --fields */
|
|
191
|
-
const TABLE_HIDDEN_FIELDS = new Set([
|
|
192
|
+
const TABLE_HIDDEN_FIELDS = /* @__PURE__ */ new Set([
|
|
192
193
|
"object",
|
|
193
194
|
"metadata",
|
|
194
195
|
"display_order",
|
|
195
196
|
"product_family",
|
|
196
197
|
"livemode",
|
|
197
198
|
"created_at",
|
|
198
|
-
"updated_at"
|
|
199
|
+
"updated_at",
|
|
200
|
+
"_hints"
|
|
199
201
|
]);
|
|
200
202
|
/**
|
|
201
203
|
* 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
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "CLI for Recur - Taiwan's subscription payment platform",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"recur",
|
|
@@ -13,9 +13,12 @@
|
|
|
13
13
|
"homepage": "https://recur.tw/docs/cli",
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
16
|
-
"url": "https://github.com/
|
|
16
|
+
"url": "https://github.com/kaikhq/recur.tw.git",
|
|
17
17
|
"directory": "packages/cli"
|
|
18
18
|
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/kaikhq/recur.tw/issues"
|
|
21
|
+
},
|
|
19
22
|
"license": "MIT",
|
|
20
23
|
"author": {
|
|
21
24
|
"name": "Recur",
|
|
@@ -41,14 +44,6 @@
|
|
|
41
44
|
"AGENT.md",
|
|
42
45
|
"LICENSE"
|
|
43
46
|
],
|
|
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
47
|
"dependencies": {
|
|
53
48
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
54
49
|
"commander": "^12.1.0",
|
|
@@ -57,15 +52,22 @@
|
|
|
57
52
|
},
|
|
58
53
|
"devDependencies": {
|
|
59
54
|
"@types/node": "^22.0.0",
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
55
|
+
"tsdown": "^0.22.12",
|
|
56
|
+
"typescript": "^6.0.3",
|
|
57
|
+
"vitest": "^4.1.10",
|
|
58
|
+
"@workspace/core": "0.0.1"
|
|
64
59
|
},
|
|
65
60
|
"engines": {
|
|
66
61
|
"node": ">=22"
|
|
67
62
|
},
|
|
68
63
|
"publishConfig": {
|
|
69
64
|
"access": "public"
|
|
65
|
+
},
|
|
66
|
+
"scripts": {
|
|
67
|
+
"build": "tsdown",
|
|
68
|
+
"dev": "tsdown --watch",
|
|
69
|
+
"lint": "eslint src/",
|
|
70
|
+
"typecheck": "tsc --noEmit",
|
|
71
|
+
"test": "vitest run"
|
|
70
72
|
}
|
|
71
|
-
}
|
|
73
|
+
}
|