@recur-tw/cli 0.1.1 → 0.1.2
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/dist/cli.mjs +79 -12
- package/dist/index.mjs +76 -9
- package/package.json +1 -1
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.
|
|
923
|
+
"User-Agent": `@recur-tw/cli/0.1.2`
|
|
924
924
|
};
|
|
925
925
|
const hasBody = opts?.body !== void 0;
|
|
926
926
|
if (hasBody) headers["Content-Type"] = "application/json";
|
|
@@ -988,6 +988,16 @@ function sanitizeResponse(data) {
|
|
|
988
988
|
}
|
|
989
989
|
//#endregion
|
|
990
990
|
//#region src/output.ts
|
|
991
|
+
/** Fields to hide in table mode unless explicitly requested via --fields */
|
|
992
|
+
const TABLE_HIDDEN_FIELDS = new Set([
|
|
993
|
+
"object",
|
|
994
|
+
"metadata",
|
|
995
|
+
"display_order",
|
|
996
|
+
"product_family",
|
|
997
|
+
"livemode",
|
|
998
|
+
"created_at",
|
|
999
|
+
"updated_at"
|
|
1000
|
+
]);
|
|
991
1001
|
/**
|
|
992
1002
|
* Render data in the specified format.
|
|
993
1003
|
* Agent-optimized: --output json produces clean, parseable JSON to stdout.
|
|
@@ -1037,32 +1047,88 @@ function renderCsv(data, opts) {
|
|
|
1037
1047
|
console.log(values.join(","));
|
|
1038
1048
|
}
|
|
1039
1049
|
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Calculate display width of a string in terminal columns.
|
|
1052
|
+
* CJK characters (Chinese/Japanese/Korean) take 2 columns.
|
|
1053
|
+
* ANSI escape sequences take 0 columns.
|
|
1054
|
+
*/
|
|
1055
|
+
function displayWidth(str) {
|
|
1056
|
+
const stripped = str.replace(/\x1B\[[0-9;]*[A-Za-z]/g, "");
|
|
1057
|
+
let width = 0;
|
|
1058
|
+
for (const char of stripped) if (isFullWidth(char.codePointAt(0))) width += 2;
|
|
1059
|
+
else width += 1;
|
|
1060
|
+
return width;
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Check if a Unicode code point is full-width (2 terminal columns).
|
|
1064
|
+
* Covers CJK Unified Ideographs, CJK Compatibility, Hangul, Fullwidth Forms, etc.
|
|
1065
|
+
*/
|
|
1066
|
+
function isFullWidth(code) {
|
|
1067
|
+
return code >= 4352 && code <= 4447 || code >= 11904 && code <= 12350 || code >= 12352 && code <= 13247 || code >= 13312 && code <= 19903 || code >= 19968 && code <= 42191 || code >= 43360 && code <= 43388 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65072 && code <= 65131 || code >= 65281 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 131072 && code <= 196605 || code >= 196608 && code <= 262141;
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Pad a string to target display width (CJK-aware).
|
|
1071
|
+
*/
|
|
1072
|
+
function padEndDisplay(str, targetWidth) {
|
|
1073
|
+
const currentWidth = displayWidth(str);
|
|
1074
|
+
if (currentWidth >= targetWidth) return str;
|
|
1075
|
+
return str + " ".repeat(targetWidth - currentWidth);
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Slice a string to fit within maxWidth display columns (CJK-aware).
|
|
1079
|
+
* Adds '…' if truncated.
|
|
1080
|
+
*/
|
|
1081
|
+
function sliceDisplay(str, maxWidth) {
|
|
1082
|
+
if (maxWidth <= 1) return "…";
|
|
1083
|
+
const stripped = str.replace(/\x1B\[[0-9;]*[A-Za-z]/g, "");
|
|
1084
|
+
let width = 0;
|
|
1085
|
+
let result = "";
|
|
1086
|
+
for (const char of stripped) {
|
|
1087
|
+
const charWidth = isFullWidth(char.codePointAt(0)) ? 2 : 1;
|
|
1088
|
+
if (width + charWidth > maxWidth - 1) return result + "…";
|
|
1089
|
+
result += char;
|
|
1090
|
+
width += charWidth;
|
|
1091
|
+
}
|
|
1092
|
+
return str;
|
|
1093
|
+
}
|
|
1040
1094
|
function renderTable(data, opts) {
|
|
1041
1095
|
const rows = Array.isArray(data) ? data : [data];
|
|
1042
1096
|
if (rows.length === 0) {
|
|
1043
1097
|
console.log(pc.dim("No results"));
|
|
1044
1098
|
return;
|
|
1045
1099
|
}
|
|
1046
|
-
const
|
|
1100
|
+
const allFields = Object.keys(rows[0]);
|
|
1101
|
+
const fields = opts.fields ?? allFields.filter((f) => !TABLE_HIDDEN_FIELDS.has(f));
|
|
1102
|
+
const termWidth = process.stdout.columns || 120;
|
|
1103
|
+
const maxColWidth = 40;
|
|
1047
1104
|
const widths = {};
|
|
1048
|
-
for (const field of fields) widths[field] = field
|
|
1105
|
+
for (const field of fields) widths[field] = displayWidth(field);
|
|
1049
1106
|
for (const row of rows) {
|
|
1050
1107
|
const record = row;
|
|
1051
1108
|
for (const field of fields) {
|
|
1052
|
-
const
|
|
1053
|
-
widths[field] = Math.min(Math.max(widths[field] ?? 0,
|
|
1109
|
+
const w = displayWidth(formatValue(record[field]));
|
|
1110
|
+
widths[field] = Math.min(Math.max(widths[field] ?? 0, w), maxColWidth);
|
|
1054
1111
|
}
|
|
1055
1112
|
}
|
|
1056
|
-
const
|
|
1113
|
+
const totalGap = 2 * (fields.length - 1);
|
|
1114
|
+
let totalWidth = fields.reduce((sum, f) => sum + (widths[f] ?? 0), 0) + totalGap;
|
|
1115
|
+
while (totalWidth > termWidth && fields.length > 1) {
|
|
1116
|
+
let widest = fields[0];
|
|
1117
|
+
for (const f of fields) if ((widths[f] ?? 0) > (widths[widest] ?? 0)) widest = f;
|
|
1118
|
+
if ((widths[widest] ?? 0) <= 6) break;
|
|
1119
|
+
widths[widest] = (widths[widest] ?? 0) - 1;
|
|
1120
|
+
totalWidth--;
|
|
1121
|
+
}
|
|
1122
|
+
const header = fields.map((f) => pc.bold(padEndDisplay(f, widths[f] ?? 0))).join(" ");
|
|
1057
1123
|
console.log(header);
|
|
1058
1124
|
console.log(fields.map((f) => "─".repeat(widths[f] ?? 0)).join(" "));
|
|
1059
1125
|
for (const row of rows) {
|
|
1060
1126
|
const record = row;
|
|
1061
1127
|
const line = fields.map((f) => {
|
|
1062
1128
|
const val = formatValue(record[f]);
|
|
1063
|
-
const
|
|
1064
|
-
|
|
1065
|
-
return
|
|
1129
|
+
const maxW = widths[f] ?? maxColWidth;
|
|
1130
|
+
if (displayWidth(val) > maxW) return sliceDisplay(val, maxW);
|
|
1131
|
+
return padEndDisplay(val, maxW);
|
|
1066
1132
|
}).join(" ");
|
|
1067
1133
|
console.log(line);
|
|
1068
1134
|
}
|
|
@@ -1071,7 +1137,8 @@ function renderTable(data, opts) {
|
|
|
1071
1137
|
function formatValue(val) {
|
|
1072
1138
|
if (val === null || val === void 0) return pc.dim("—");
|
|
1073
1139
|
if (typeof val === "boolean") return val ? pc.green("true") : pc.dim("false");
|
|
1074
|
-
if (
|
|
1140
|
+
if (Array.isArray(val)) return pc.dim(`[${val.length} items]`);
|
|
1141
|
+
if (typeof val === "object") return pc.dim("{…}");
|
|
1075
1142
|
return sanitizeString(String(val));
|
|
1076
1143
|
}
|
|
1077
1144
|
/**
|
|
@@ -2489,7 +2556,7 @@ Requires a Secret Key (sk_test_* or sk_live_*) via --key, RECUR_SECRET_KEY, or r
|
|
|
2489
2556
|
});
|
|
2490
2557
|
const server = new McpServer({
|
|
2491
2558
|
name: "recur",
|
|
2492
|
-
version: "0.1.
|
|
2559
|
+
version: "0.1.2"
|
|
2493
2560
|
}, { capabilities: { tools: {} } });
|
|
2494
2561
|
registerTools(server, client);
|
|
2495
2562
|
const transport = new StdioServerTransport();
|
|
@@ -2505,7 +2572,7 @@ Requires a Secret Key (sk_test_* or sk_live_*) via --key, RECUR_SECRET_KEY, or r
|
|
|
2505
2572
|
//#endregion
|
|
2506
2573
|
//#region src/cli.ts
|
|
2507
2574
|
const program = new Command();
|
|
2508
|
-
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.
|
|
2575
|
+
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.2").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) => {
|
|
2509
2576
|
const opts = thisCommand.opts();
|
|
2510
2577
|
if (!opts.output) opts.output = process.stdout.isTTY ? "table" : "json";
|
|
2511
2578
|
if (![
|
package/dist/index.mjs
CHANGED
|
@@ -187,6 +187,16 @@ function resolveBaseUrl(opts) {
|
|
|
187
187
|
}
|
|
188
188
|
//#endregion
|
|
189
189
|
//#region src/output.ts
|
|
190
|
+
/** Fields to hide in table mode unless explicitly requested via --fields */
|
|
191
|
+
const TABLE_HIDDEN_FIELDS = new Set([
|
|
192
|
+
"object",
|
|
193
|
+
"metadata",
|
|
194
|
+
"display_order",
|
|
195
|
+
"product_family",
|
|
196
|
+
"livemode",
|
|
197
|
+
"created_at",
|
|
198
|
+
"updated_at"
|
|
199
|
+
]);
|
|
190
200
|
/**
|
|
191
201
|
* Render data in the specified format.
|
|
192
202
|
* Agent-optimized: --output json produces clean, parseable JSON to stdout.
|
|
@@ -236,32 +246,88 @@ function renderCsv(data, opts) {
|
|
|
236
246
|
console.log(values.join(","));
|
|
237
247
|
}
|
|
238
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* Calculate display width of a string in terminal columns.
|
|
251
|
+
* CJK characters (Chinese/Japanese/Korean) take 2 columns.
|
|
252
|
+
* ANSI escape sequences take 0 columns.
|
|
253
|
+
*/
|
|
254
|
+
function displayWidth(str) {
|
|
255
|
+
const stripped = str.replace(/\x1B\[[0-9;]*[A-Za-z]/g, "");
|
|
256
|
+
let width = 0;
|
|
257
|
+
for (const char of stripped) if (isFullWidth(char.codePointAt(0))) width += 2;
|
|
258
|
+
else width += 1;
|
|
259
|
+
return width;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Check if a Unicode code point is full-width (2 terminal columns).
|
|
263
|
+
* Covers CJK Unified Ideographs, CJK Compatibility, Hangul, Fullwidth Forms, etc.
|
|
264
|
+
*/
|
|
265
|
+
function isFullWidth(code) {
|
|
266
|
+
return code >= 4352 && code <= 4447 || code >= 11904 && code <= 12350 || code >= 12352 && code <= 13247 || code >= 13312 && code <= 19903 || code >= 19968 && code <= 42191 || code >= 43360 && code <= 43388 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65072 && code <= 65131 || code >= 65281 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 131072 && code <= 196605 || code >= 196608 && code <= 262141;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Pad a string to target display width (CJK-aware).
|
|
270
|
+
*/
|
|
271
|
+
function padEndDisplay(str, targetWidth) {
|
|
272
|
+
const currentWidth = displayWidth(str);
|
|
273
|
+
if (currentWidth >= targetWidth) return str;
|
|
274
|
+
return str + " ".repeat(targetWidth - currentWidth);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Slice a string to fit within maxWidth display columns (CJK-aware).
|
|
278
|
+
* Adds '…' if truncated.
|
|
279
|
+
*/
|
|
280
|
+
function sliceDisplay(str, maxWidth) {
|
|
281
|
+
if (maxWidth <= 1) return "…";
|
|
282
|
+
const stripped = str.replace(/\x1B\[[0-9;]*[A-Za-z]/g, "");
|
|
283
|
+
let width = 0;
|
|
284
|
+
let result = "";
|
|
285
|
+
for (const char of stripped) {
|
|
286
|
+
const charWidth = isFullWidth(char.codePointAt(0)) ? 2 : 1;
|
|
287
|
+
if (width + charWidth > maxWidth - 1) return result + "…";
|
|
288
|
+
result += char;
|
|
289
|
+
width += charWidth;
|
|
290
|
+
}
|
|
291
|
+
return str;
|
|
292
|
+
}
|
|
239
293
|
function renderTable(data, opts) {
|
|
240
294
|
const rows = Array.isArray(data) ? data : [data];
|
|
241
295
|
if (rows.length === 0) {
|
|
242
296
|
console.log(pc.dim("No results"));
|
|
243
297
|
return;
|
|
244
298
|
}
|
|
245
|
-
const
|
|
299
|
+
const allFields = Object.keys(rows[0]);
|
|
300
|
+
const fields = opts.fields ?? allFields.filter((f) => !TABLE_HIDDEN_FIELDS.has(f));
|
|
301
|
+
const termWidth = process.stdout.columns || 120;
|
|
302
|
+
const maxColWidth = 40;
|
|
246
303
|
const widths = {};
|
|
247
|
-
for (const field of fields) widths[field] = field
|
|
304
|
+
for (const field of fields) widths[field] = displayWidth(field);
|
|
248
305
|
for (const row of rows) {
|
|
249
306
|
const record = row;
|
|
250
307
|
for (const field of fields) {
|
|
251
|
-
const
|
|
252
|
-
widths[field] = Math.min(Math.max(widths[field] ?? 0,
|
|
308
|
+
const w = displayWidth(formatValue(record[field]));
|
|
309
|
+
widths[field] = Math.min(Math.max(widths[field] ?? 0, w), maxColWidth);
|
|
253
310
|
}
|
|
254
311
|
}
|
|
255
|
-
const
|
|
312
|
+
const totalGap = 2 * (fields.length - 1);
|
|
313
|
+
let totalWidth = fields.reduce((sum, f) => sum + (widths[f] ?? 0), 0) + totalGap;
|
|
314
|
+
while (totalWidth > termWidth && fields.length > 1) {
|
|
315
|
+
let widest = fields[0];
|
|
316
|
+
for (const f of fields) if ((widths[f] ?? 0) > (widths[widest] ?? 0)) widest = f;
|
|
317
|
+
if ((widths[widest] ?? 0) <= 6) break;
|
|
318
|
+
widths[widest] = (widths[widest] ?? 0) - 1;
|
|
319
|
+
totalWidth--;
|
|
320
|
+
}
|
|
321
|
+
const header = fields.map((f) => pc.bold(padEndDisplay(f, widths[f] ?? 0))).join(" ");
|
|
256
322
|
console.log(header);
|
|
257
323
|
console.log(fields.map((f) => "─".repeat(widths[f] ?? 0)).join(" "));
|
|
258
324
|
for (const row of rows) {
|
|
259
325
|
const record = row;
|
|
260
326
|
const line = fields.map((f) => {
|
|
261
327
|
const val = formatValue(record[f]);
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
return
|
|
328
|
+
const maxW = widths[f] ?? maxColWidth;
|
|
329
|
+
if (displayWidth(val) > maxW) return sliceDisplay(val, maxW);
|
|
330
|
+
return padEndDisplay(val, maxW);
|
|
265
331
|
}).join(" ");
|
|
266
332
|
console.log(line);
|
|
267
333
|
}
|
|
@@ -270,7 +336,8 @@ function renderTable(data, opts) {
|
|
|
270
336
|
function formatValue(val) {
|
|
271
337
|
if (val === null || val === void 0) return pc.dim("—");
|
|
272
338
|
if (typeof val === "boolean") return val ? pc.green("true") : pc.dim("false");
|
|
273
|
-
if (
|
|
339
|
+
if (Array.isArray(val)) return pc.dim(`[${val.length} items]`);
|
|
340
|
+
if (typeof val === "object") return pc.dim("{…}");
|
|
274
341
|
return sanitizeString(String(val));
|
|
275
342
|
}
|
|
276
343
|
/**
|