@recur-tw/cli 0.1.3 → 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 +67 -4
- package/dist/cli.mjs +353 -31
- package/dist/index.mjs +2 -1
- package/package.json +11 -12
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
|
@@ -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.
|
|
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.
|
|
@@ -1226,8 +1227,8 @@ function registerLoginCommand(program) {
|
|
|
1226
1227
|
secretKey: key
|
|
1227
1228
|
});
|
|
1228
1229
|
try {
|
|
1229
|
-
await client.get("/v1/
|
|
1230
|
-
console.error(pc.green(
|
|
1230
|
+
const me = await client.get("/v1/me");
|
|
1231
|
+
console.error(pc.green(`✓ API key verified (${me.organization.name})`));
|
|
1231
1232
|
} catch {
|
|
1232
1233
|
console.error(pc.yellow("⚠ Could not verify API key (saved anyway)"));
|
|
1233
1234
|
}
|
|
@@ -1253,26 +1254,33 @@ function registerLoginCommand(program) {
|
|
|
1253
1254
|
handleError(err);
|
|
1254
1255
|
}
|
|
1255
1256
|
});
|
|
1256
|
-
program.command("whoami").description("Show current profile and
|
|
1257
|
+
program.command("whoami").description("Show current profile, API key, and organization info").action(async () => {
|
|
1257
1258
|
try {
|
|
1258
|
-
const
|
|
1259
|
+
const globalOpts = program.opts();
|
|
1259
1260
|
const active = listProfiles().find((p) => p.active);
|
|
1260
1261
|
if (!active) {
|
|
1261
1262
|
console.error(pc.yellow("No profiles configured. Run: recur login"));
|
|
1262
1263
|
process.exit(1);
|
|
1263
1264
|
}
|
|
1264
|
-
const
|
|
1265
|
-
|
|
1265
|
+
const info = {
|
|
1266
|
+
profile: active.name,
|
|
1267
|
+
environment: active.environment
|
|
1268
|
+
};
|
|
1266
1269
|
try {
|
|
1267
1270
|
const key = resolveSecretKey(globalOpts);
|
|
1268
1271
|
const prefixEnd = key.lastIndexOf("_") + 1;
|
|
1269
|
-
|
|
1272
|
+
info["key"] = key.slice(0, prefixEnd) + key.slice(prefixEnd, prefixEnd + 4) + "...";
|
|
1273
|
+
try {
|
|
1274
|
+
const data = await new RecurClient({
|
|
1275
|
+
baseUrl: resolveBaseUrl(globalOpts),
|
|
1276
|
+
secretKey: key
|
|
1277
|
+
}).get("/v1/me");
|
|
1278
|
+
info["org"] = data.organization.name;
|
|
1279
|
+
info["org_id"] = data.organization.id;
|
|
1280
|
+
info["org_slug"] = data.organization.slug;
|
|
1281
|
+
} catch {}
|
|
1270
1282
|
} catch {}
|
|
1271
|
-
render({
|
|
1272
|
-
profile: active.name,
|
|
1273
|
-
environment: active.environment,
|
|
1274
|
-
...maskedKey && { key: maskedKey }
|
|
1275
|
-
}, { format: opts.output });
|
|
1283
|
+
render(info, { format: globalOpts.output });
|
|
1276
1284
|
} catch (err) {
|
|
1277
1285
|
handleError(err);
|
|
1278
1286
|
}
|
|
@@ -1340,6 +1348,181 @@ function extractPaginatedList(response) {
|
|
|
1340
1348
|
};
|
|
1341
1349
|
}
|
|
1342
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
|
|
1343
1526
|
//#region src/commands/products.ts
|
|
1344
1527
|
function getClient$6(opts) {
|
|
1345
1528
|
return new RecurClient({
|
|
@@ -1388,7 +1571,7 @@ Examples:
|
|
|
1388
1571
|
const client = getClient$6(opts);
|
|
1389
1572
|
const validId = validateResourceId(id);
|
|
1390
1573
|
const path = id.includes("-") ? `/v1/products/by-slug/${validId}` : `/v1/products/${validId}`;
|
|
1391
|
-
const data = await client.get(path);
|
|
1574
|
+
const data = attachHints(await client.get(path), "products", "get", opts.output);
|
|
1392
1575
|
render(opts.fields ? pickFields(data, opts.fields.split(",")) : data, getOutputOpts$6(opts));
|
|
1393
1576
|
} catch (err) {
|
|
1394
1577
|
handleError(err);
|
|
@@ -1430,7 +1613,7 @@ Examples:
|
|
|
1430
1613
|
render(body, { format: "json" });
|
|
1431
1614
|
return;
|
|
1432
1615
|
}
|
|
1433
|
-
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));
|
|
1434
1617
|
} catch (err) {
|
|
1435
1618
|
handleError(err);
|
|
1436
1619
|
}
|
|
@@ -1463,7 +1646,7 @@ Examples:
|
|
|
1463
1646
|
render(body, { format: "json" });
|
|
1464
1647
|
return;
|
|
1465
1648
|
}
|
|
1466
|
-
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));
|
|
1467
1650
|
} catch (err) {
|
|
1468
1651
|
handleError(err);
|
|
1469
1652
|
}
|
|
@@ -1476,7 +1659,7 @@ Examples:
|
|
|
1476
1659
|
console.error(pc.yellow(`[dry-run] Would archive product ${validId}`));
|
|
1477
1660
|
return;
|
|
1478
1661
|
}
|
|
1479
|
-
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));
|
|
1480
1663
|
} catch (err) {
|
|
1481
1664
|
handleError(err);
|
|
1482
1665
|
}
|
|
@@ -1568,7 +1751,7 @@ Examples:
|
|
|
1568
1751
|
try {
|
|
1569
1752
|
const opts = customers.optsWithGlobals();
|
|
1570
1753
|
const validId = validateResourceId(id);
|
|
1571
|
-
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);
|
|
1572
1755
|
render(opts.fields ? pickFields(data, opts.fields.split(",")) : data, getOutputOpts$5(opts));
|
|
1573
1756
|
} catch (err) {
|
|
1574
1757
|
handleError(err);
|
|
@@ -1593,7 +1776,7 @@ Examples:
|
|
|
1593
1776
|
render(body, { format: "json" });
|
|
1594
1777
|
return;
|
|
1595
1778
|
}
|
|
1596
|
-
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));
|
|
1597
1780
|
} catch (err) {
|
|
1598
1781
|
handleError(err);
|
|
1599
1782
|
}
|
|
@@ -1663,7 +1846,7 @@ Note: --immediately is a flag (no value). Do NOT use --immediately false; omit t
|
|
|
1663
1846
|
try {
|
|
1664
1847
|
const opts = subscriptions.optsWithGlobals();
|
|
1665
1848
|
const validId = validateResourceId(id);
|
|
1666
|
-
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);
|
|
1667
1850
|
render(opts.fields ? pickFields(data, opts.fields.split(",")) : data, getOutputOpts$4(opts));
|
|
1668
1851
|
} catch (err) {
|
|
1669
1852
|
handleError(err);
|
|
@@ -1683,7 +1866,7 @@ Note: --immediately is a flag (no value). Do NOT use --immediately false; omit t
|
|
|
1683
1866
|
render(body, { format: "json" });
|
|
1684
1867
|
return;
|
|
1685
1868
|
}
|
|
1686
|
-
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));
|
|
1687
1870
|
} catch (err) {
|
|
1688
1871
|
handleError(err);
|
|
1689
1872
|
}
|
|
@@ -1939,7 +2122,7 @@ Examples:
|
|
|
1939
2122
|
render(body, { format: "json" });
|
|
1940
2123
|
return;
|
|
1941
2124
|
}
|
|
1942
|
-
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));
|
|
1943
2126
|
} catch (err) {
|
|
1944
2127
|
handleError(err);
|
|
1945
2128
|
}
|
|
@@ -1952,7 +2135,7 @@ Examples:
|
|
|
1952
2135
|
console.error(pc.yellow(`[dry-run] Would send test event "${cmdOpts.event}" to webhook ${validId}`));
|
|
1953
2136
|
return;
|
|
1954
2137
|
}
|
|
1955
|
-
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));
|
|
1956
2139
|
} catch (err) {
|
|
1957
2140
|
handleError(err);
|
|
1958
2141
|
}
|
|
@@ -2030,7 +2213,7 @@ Examples:
|
|
|
2030
2213
|
try {
|
|
2031
2214
|
const opts = orders.optsWithGlobals();
|
|
2032
2215
|
const validId = validateResourceId(id);
|
|
2033
|
-
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);
|
|
2034
2217
|
render(opts.fields ? pickFields(data, opts.fields.split(",")) : data, getOutputOpts$2(opts));
|
|
2035
2218
|
} catch (err) {
|
|
2036
2219
|
handleError(err);
|
|
@@ -2096,7 +2279,7 @@ Examples:
|
|
|
2096
2279
|
try {
|
|
2097
2280
|
const opts = invoices.optsWithGlobals();
|
|
2098
2281
|
const validId = validateResourceId(id);
|
|
2099
|
-
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);
|
|
2100
2283
|
render(opts.fields ? pickFields(data, opts.fields.split(",")) : data, getOutputOpts$1(opts));
|
|
2101
2284
|
} catch (err) {
|
|
2102
2285
|
handleError(err);
|
|
@@ -2159,7 +2342,7 @@ Examples:
|
|
|
2159
2342
|
render(body, { format: "json" });
|
|
2160
2343
|
return;
|
|
2161
2344
|
}
|
|
2162
|
-
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));
|
|
2163
2346
|
} catch (err) {
|
|
2164
2347
|
handleError(err);
|
|
2165
2348
|
}
|
|
@@ -2168,7 +2351,7 @@ Examples:
|
|
|
2168
2351
|
try {
|
|
2169
2352
|
const opts = checkouts.optsWithGlobals();
|
|
2170
2353
|
const validId = validateResourceId(id);
|
|
2171
|
-
const data = await getClient(opts).get(`/v1/checkouts/${validId}`);
|
|
2354
|
+
const data = attachHints(await getClient(opts).get(`/v1/checkouts/${validId}`), "checkouts", "get", opts.output);
|
|
2172
2355
|
render(opts.fields ? pickFields(data, opts.fields.split(",")) : data, getOutputOpts(opts));
|
|
2173
2356
|
} catch (err) {
|
|
2174
2357
|
handleError(err);
|
|
@@ -2557,7 +2740,7 @@ Requires a Secret Key (sk_test_* or sk_live_*) via --key, RECUR_SECRET_KEY, or r
|
|
|
2557
2740
|
});
|
|
2558
2741
|
const server = new McpServer({
|
|
2559
2742
|
name: "recur",
|
|
2560
|
-
version: "0.
|
|
2743
|
+
version: "0.2.0"
|
|
2561
2744
|
}, { capabilities: { tools: {} } });
|
|
2562
2745
|
registerTools(server, client);
|
|
2563
2746
|
const transport = new StdioServerTransport();
|
|
@@ -2571,9 +2754,146 @@ Requires a Secret Key (sk_test_* or sk_live_*) via --key, RECUR_SECRET_KEY, or r
|
|
|
2571
2754
|
});
|
|
2572
2755
|
}
|
|
2573
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
|
|
2574
2894
|
//#region src/cli.ts
|
|
2575
2895
|
const program = new Command();
|
|
2576
|
-
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.
|
|
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) => {
|
|
2577
2897
|
const opts = thisCommand.opts();
|
|
2578
2898
|
if (!opts.output) opts.output = process.stdout.isTTY ? "table" : "json";
|
|
2579
2899
|
if (![
|
|
@@ -2585,7 +2905,7 @@ program.name("recur").description("Recur CLI — Taiwan subscription payment pla
|
|
|
2585
2905
|
console.error(`Error: unsupported output format "${opts.output}". Use json, table, csv, or ndjson.`);
|
|
2586
2906
|
process.exit(1);
|
|
2587
2907
|
}
|
|
2588
|
-
}).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)");
|
|
2589
2909
|
program.addHelpText("after", `
|
|
2590
2910
|
Global options (--output, --fields, --json, --dry-run) work with ALL subcommands.
|
|
2591
2911
|
|
|
@@ -2613,6 +2933,8 @@ registerInvoicesCommand(program);
|
|
|
2613
2933
|
registerCheckoutsCommand(program);
|
|
2614
2934
|
registerSchemaCommand(program);
|
|
2615
2935
|
registerMcpCommand(program);
|
|
2936
|
+
registerCommandsCommand(program);
|
|
2937
|
+
configureAgentHelp(program);
|
|
2616
2938
|
program.parse();
|
|
2617
2939
|
//#endregion
|
|
2618
2940
|
export {};
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@recur-tw/cli",
|
|
3
|
-
"version": "0.
|
|
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
|
+
}
|