@baseworks/cli 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/dist/args.d.ts +30 -0
- package/dist/args.js +10 -0
- package/dist/chunk-5U3CFW6F.js +26 -0
- package/dist/chunk-BEHHVEGL.js +17 -0
- package/dist/chunk-C2YZKMJ7.js +185 -0
- package/dist/chunk-H4M2NPA2.js +46 -0
- package/dist/chunk-HJFKWDPX.js +40 -0
- package/dist/chunk-LQJTQ52B.js +98 -0
- package/dist/chunk-TSVJD4R6.js +51 -0
- package/dist/chunk-XQGLG3X3.js +54 -0
- package/dist/chunk-ZD7NOFZL.js +59 -0
- package/dist/client.d.ts +24 -0
- package/dist/client.js +8 -0
- package/dist/config-loader.d.ts +41 -0
- package/dist/config-loader.js +10 -0
- package/dist/context.d.ts +45 -0
- package/dist/context.js +6 -0
- package/dist/display.d.ts +82 -0
- package/dist/display.js +32 -0
- package/dist/fmt.d.ts +19 -0
- package/dist/fmt.js +17 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +78 -0
- package/dist/middleware.d.ts +37 -0
- package/dist/middleware.js +11 -0
- package/dist/plugin.d.ts +99 -0
- package/dist/plugin.js +0 -0
- package/dist/runner.d.ts +53 -0
- package/dist/runner.js +10 -0
- package/dist/tree.d.ts +31 -0
- package/dist/tree.js +7 -0
- package/package.json +34 -0
- package/src/args.ts +77 -0
- package/src/client.ts +80 -0
- package/src/config-loader.ts +68 -0
- package/src/context.ts +124 -0
- package/src/display.ts +287 -0
- package/src/fmt.ts +59 -0
- package/src/index.ts +34 -0
- package/src/middleware.ts +80 -0
- package/src/plugin.ts +120 -0
- package/src/runner.ts +164 -0
- package/src/testing.ts +125 -0
- package/src/tree.ts +55 -0
package/dist/args.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight arg/flag parser for Node CLI binaries.
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* --flag → { flag: true }
|
|
6
|
+
* --flag value → { flag: 'value' }
|
|
7
|
+
* --flag=value → { flag: 'value' }
|
|
8
|
+
* positional args → args[]
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const { args, flags } = parseFlags(process.argv.slice(2));
|
|
12
|
+
* if (flags.json) { ... }
|
|
13
|
+
* const [ws, proj] = args;
|
|
14
|
+
*/
|
|
15
|
+
interface ParsedArgs {
|
|
16
|
+
/** Positional (non-flag) arguments in order. */
|
|
17
|
+
args: string[];
|
|
18
|
+
/** Parsed flags. Values are string if the flag had a value, true if bare. */
|
|
19
|
+
flags: Record<string, string | boolean>;
|
|
20
|
+
}
|
|
21
|
+
declare function parseFlags(argv: string[]): ParsedArgs;
|
|
22
|
+
/** Returns true if --json or -j flag is present in process.argv. */
|
|
23
|
+
declare function isJsonMode(argv?: string[]): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Extract a named flag value from argv.
|
|
26
|
+
* getFlag(process.argv, '--public-key') → string | undefined
|
|
27
|
+
*/
|
|
28
|
+
declare function getFlag(argv: string[], flag: string): string | undefined;
|
|
29
|
+
|
|
30
|
+
export { type ParsedArgs, getFlag, isJsonMode, parseFlags };
|
package/dist/args.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clr
|
|
3
|
+
} from "./chunk-C2YZKMJ7.js";
|
|
4
|
+
|
|
5
|
+
// src/tree.ts
|
|
6
|
+
function tree(nodes, prefix = "") {
|
|
7
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
8
|
+
const node = nodes[i];
|
|
9
|
+
const last = i === nodes.length - 1;
|
|
10
|
+
const isRoot = prefix === "";
|
|
11
|
+
const connector = isRoot ? "" : last ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
12
|
+
const childPfx = isRoot ? last ? " " : "\u2502 " : prefix + (last ? " " : "\u2502 ");
|
|
13
|
+
const badge = node.badge ? node.badge + " " : "";
|
|
14
|
+
const meta = node.meta ? ` ${clr.dim}${node.meta}${clr.reset}` : "";
|
|
15
|
+
console.log(
|
|
16
|
+
`${clr.dim}${prefix}${connector}${clr.reset}${badge}${clr.bold}${node.label}${clr.reset}${meta}`
|
|
17
|
+
);
|
|
18
|
+
if (node.children?.length) {
|
|
19
|
+
tree(node.children, childPfx);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
tree
|
|
26
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/config-loader.ts
|
|
2
|
+
function resolveToken(cfg, opts = {}) {
|
|
3
|
+
return cfg.token ?? (opts.env ? process.env[opts.env] : void 0);
|
|
4
|
+
}
|
|
5
|
+
function resolveApiBase(cfg, opts = {}) {
|
|
6
|
+
const raw = cfg.apiBase ?? (opts.env ? process.env[opts.env] : void 0) ?? opts.default ?? "";
|
|
7
|
+
return raw.replace(/\/$/, "");
|
|
8
|
+
}
|
|
9
|
+
function resolveField(cfg, key, opts = {}) {
|
|
10
|
+
return cfg[key] ?? (opts.env ? process.env[opts.env] : void 0) ?? opts.default;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
resolveToken,
|
|
15
|
+
resolveApiBase,
|
|
16
|
+
resolveField
|
|
17
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// src/display.ts
|
|
2
|
+
import { createInterface } from "readline";
|
|
3
|
+
var isTTY = typeof process !== "undefined" && process.stdout?.isTTY;
|
|
4
|
+
var clr = {
|
|
5
|
+
reset: isTTY ? "\x1B[0m" : "",
|
|
6
|
+
bold: isTTY ? "\x1B[1m" : "",
|
|
7
|
+
dim: isTTY ? "\x1B[2m" : "",
|
|
8
|
+
green: isTTY ? "\x1B[32m" : "",
|
|
9
|
+
yellow: isTTY ? "\x1B[33m" : "",
|
|
10
|
+
cyan: isTTY ? "\x1B[36m" : "",
|
|
11
|
+
red: isTTY ? "\x1B[31m" : ""
|
|
12
|
+
};
|
|
13
|
+
function truncateId(id, n = 8) {
|
|
14
|
+
if (!id || id.length <= n + 1) return id;
|
|
15
|
+
const isUuid = /^[0-9a-f]{8}-/i.test(id);
|
|
16
|
+
return isUuid ? id.slice(0, n) + "\u2026" : id;
|
|
17
|
+
}
|
|
18
|
+
function statusColor(status) {
|
|
19
|
+
const s = status.toLowerCase();
|
|
20
|
+
if (["active", "succeeded", "paid", "completed"].includes(s))
|
|
21
|
+
return `${clr.green}${status}${clr.reset}`;
|
|
22
|
+
if (["canceled", "cancelled", "failed", "expired", "revoked"].includes(s))
|
|
23
|
+
return `${clr.dim}${status}${clr.reset}`;
|
|
24
|
+
if (["trialing", "past_due", "pending", "draft"].includes(s))
|
|
25
|
+
return `${clr.yellow}${status}${clr.reset}`;
|
|
26
|
+
return status;
|
|
27
|
+
}
|
|
28
|
+
function summary(text) {
|
|
29
|
+
console.log(` ${clr.dim}${text}${clr.reset}`);
|
|
30
|
+
}
|
|
31
|
+
function visibleLength(str) {
|
|
32
|
+
return str.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
33
|
+
}
|
|
34
|
+
function padEnd(str, width) {
|
|
35
|
+
return str + " ".repeat(Math.max(0, width - visibleLength(str)));
|
|
36
|
+
}
|
|
37
|
+
function table(rows, cols, opts = {}) {
|
|
38
|
+
const isWide = opts.wide ?? (process.argv.includes("--detail") || process.argv.includes("-d") || process.argv.includes("-o") && process.argv[process.argv.indexOf("-o") + 1] === "wide" || process.argv.includes("--output") && process.argv[process.argv.indexOf("--output") + 1] === "wide");
|
|
39
|
+
const visibleCols = cols.filter((c) => !c.wide && !c.detail || isWide);
|
|
40
|
+
if (!rows.length) {
|
|
41
|
+
const msg = opts.emptyHint ?? "(none)";
|
|
42
|
+
console.log(` ${clr.dim}${msg}${clr.reset}`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const widths = visibleCols.map((col, i) => {
|
|
46
|
+
const labelLen = col.label.length;
|
|
47
|
+
const maxVal = Math.max(...rows.map((r) => visibleLength(String(col.fmt ? col.fmt(r[col.key], r) : r[col.key] ?? "\u2014"))));
|
|
48
|
+
return Math.max(labelLen, maxVal, i);
|
|
49
|
+
});
|
|
50
|
+
console.log("");
|
|
51
|
+
const header = visibleCols.map(
|
|
52
|
+
(col, i) => padEnd(`${clr.bold}${clr.dim}${col.label}${clr.reset}`, widths[i] ?? col.label.length)
|
|
53
|
+
).join(" ");
|
|
54
|
+
console.log(" " + header);
|
|
55
|
+
console.log(" " + widths.map((w) => "\u2500".repeat(w)).join(" "));
|
|
56
|
+
for (const row of rows) {
|
|
57
|
+
const line = visibleCols.map((col, i) => {
|
|
58
|
+
const val = col.fmt ? col.fmt(row[col.key], row) : row[col.key] ?? "\u2014";
|
|
59
|
+
return padEnd(String(val), widths[i] ?? 0);
|
|
60
|
+
}).join(" ");
|
|
61
|
+
console.log(" " + line);
|
|
62
|
+
}
|
|
63
|
+
console.log("");
|
|
64
|
+
}
|
|
65
|
+
function kv(pairs) {
|
|
66
|
+
const w = Math.max(...pairs.map(([k]) => k.length));
|
|
67
|
+
for (const [k, v] of pairs) {
|
|
68
|
+
console.log(` ${clr.dim}${k.padEnd(w)}${clr.reset} ${v}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function success(msg) {
|
|
72
|
+
console.log(`${clr.green}\u2713${clr.reset} ${msg}`);
|
|
73
|
+
}
|
|
74
|
+
function warn(msg) {
|
|
75
|
+
console.warn(`${clr.yellow}\u26A0${clr.reset} ${msg}`);
|
|
76
|
+
}
|
|
77
|
+
function fatal(msg) {
|
|
78
|
+
console.error(`${clr.red}error:${clr.reset} ${msg}`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
throw new Error(msg);
|
|
81
|
+
}
|
|
82
|
+
function prompt(question) {
|
|
83
|
+
return new Promise((resolve) => {
|
|
84
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
85
|
+
rl.question(question, (ans) => {
|
|
86
|
+
rl.close();
|
|
87
|
+
resolve(ans.trim());
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function helpFor(cmdName, args, flags, helpMap) {
|
|
92
|
+
if (!flags["help"] && !flags["h"]) return false;
|
|
93
|
+
const key = args.join(" ").trim();
|
|
94
|
+
const text = (key && helpMap[key]) ?? helpMap[""] ?? null;
|
|
95
|
+
if (text) {
|
|
96
|
+
console.log(text);
|
|
97
|
+
} else {
|
|
98
|
+
const lines = Object.entries(helpMap).filter(([k]) => k !== "").map(([k, v]) => ` ${cmdName} ${k.padEnd(28)}${v.split("\n")[0]}`);
|
|
99
|
+
console.log(`
|
|
100
|
+
${clr.bold}${cmdName}${clr.reset}
|
|
101
|
+
|
|
102
|
+
${lines.join("\n")}
|
|
103
|
+
`);
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
function printOrJson(data, textFn, argv = process.argv) {
|
|
108
|
+
if (argv.includes("--json") || argv.includes("-j")) {
|
|
109
|
+
console.log(JSON.stringify(data, null, 2));
|
|
110
|
+
} else {
|
|
111
|
+
textFn();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function printOutput(data, format, renderFn, codeKey) {
|
|
115
|
+
const fmt = format ?? "table";
|
|
116
|
+
switch (fmt) {
|
|
117
|
+
case "json":
|
|
118
|
+
console.log(JSON.stringify(data, null, 2));
|
|
119
|
+
break;
|
|
120
|
+
case "yaml": {
|
|
121
|
+
const toYaml = (v, indent = 0) => {
|
|
122
|
+
const pad = " ".repeat(indent);
|
|
123
|
+
if (v === null || v === void 0) return "null";
|
|
124
|
+
if (typeof v === "string") return /[:\n#{}[\],&*?|<>=!%@`]/.test(v) ? `"${v.replace(/"/g, '\\"')}"` : v;
|
|
125
|
+
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
|
126
|
+
if (Array.isArray(v)) {
|
|
127
|
+
if (v.length === 0) return "[]";
|
|
128
|
+
return v.map((item) => `${pad}- ${toYaml(item, indent + 2).trimStart()}`).join("\n");
|
|
129
|
+
}
|
|
130
|
+
if (typeof v === "object") {
|
|
131
|
+
return Object.entries(v).map(([k, val]) => {
|
|
132
|
+
const rendered = toYaml(val, indent + 2);
|
|
133
|
+
return typeof val === "object" && val !== null && !Array.isArray(val) ? `${pad}${k}:
|
|
134
|
+
${rendered}` : `${pad}${k}: ${rendered}`;
|
|
135
|
+
}).join("\n");
|
|
136
|
+
}
|
|
137
|
+
return String(v);
|
|
138
|
+
};
|
|
139
|
+
console.log(toYaml(data));
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
case "code": {
|
|
143
|
+
const items = Array.isArray(data) ? data : [data];
|
|
144
|
+
for (const item of items) {
|
|
145
|
+
let code;
|
|
146
|
+
if (typeof codeKey === "function") {
|
|
147
|
+
code = codeKey(item);
|
|
148
|
+
} else if (codeKey && item[codeKey] != null) {
|
|
149
|
+
code = String(item[codeKey]);
|
|
150
|
+
} else {
|
|
151
|
+
code = String(item["slug"] ?? item["code"] ?? item["short_id"] ?? item["id"] ?? "");
|
|
152
|
+
}
|
|
153
|
+
if (code) console.log(code);
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
case "wide":
|
|
158
|
+
renderFn(true);
|
|
159
|
+
break;
|
|
160
|
+
case "table":
|
|
161
|
+
default:
|
|
162
|
+
renderFn(false);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function outputOption() {
|
|
167
|
+
return ["-o, --output <format>", "Output format: table|wide|json|yaml|code", "table"];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export {
|
|
171
|
+
clr,
|
|
172
|
+
truncateId,
|
|
173
|
+
statusColor,
|
|
174
|
+
summary,
|
|
175
|
+
table,
|
|
176
|
+
kv,
|
|
177
|
+
success,
|
|
178
|
+
warn,
|
|
179
|
+
fatal,
|
|
180
|
+
prompt,
|
|
181
|
+
helpFor,
|
|
182
|
+
printOrJson,
|
|
183
|
+
printOutput,
|
|
184
|
+
outputOption
|
|
185
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/args.ts
|
|
2
|
+
function parseFlags(argv) {
|
|
3
|
+
const args = [];
|
|
4
|
+
const flags = {};
|
|
5
|
+
let i = 0;
|
|
6
|
+
while (i < argv.length) {
|
|
7
|
+
const token = argv[i];
|
|
8
|
+
if (token.startsWith("--")) {
|
|
9
|
+
const eqIdx = token.indexOf("=");
|
|
10
|
+
if (eqIdx !== -1) {
|
|
11
|
+
const key = token.slice(2, eqIdx);
|
|
12
|
+
const val = token.slice(eqIdx + 1);
|
|
13
|
+
flags[key] = val;
|
|
14
|
+
} else {
|
|
15
|
+
const key = token.slice(2);
|
|
16
|
+
const next = argv[i + 1];
|
|
17
|
+
if (next !== void 0 && !next.startsWith("--")) {
|
|
18
|
+
flags[key] = next;
|
|
19
|
+
i++;
|
|
20
|
+
} else {
|
|
21
|
+
flags[key] = true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
} else if (token.startsWith("-") && token.length === 2) {
|
|
25
|
+
flags[token.slice(1)] = true;
|
|
26
|
+
} else {
|
|
27
|
+
args.push(token);
|
|
28
|
+
}
|
|
29
|
+
i++;
|
|
30
|
+
}
|
|
31
|
+
return { args, flags };
|
|
32
|
+
}
|
|
33
|
+
function isJsonMode(argv = process.argv) {
|
|
34
|
+
return argv.includes("--json") || argv.includes("-j");
|
|
35
|
+
}
|
|
36
|
+
function getFlag(argv, flag) {
|
|
37
|
+
const i = argv.indexOf(flag);
|
|
38
|
+
if (i === -1) return void 0;
|
|
39
|
+
return argv[i + 1];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
parseFlags,
|
|
44
|
+
isJsonMode,
|
|
45
|
+
getFlag
|
|
46
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clr
|
|
3
|
+
} from "./chunk-C2YZKMJ7.js";
|
|
4
|
+
|
|
5
|
+
// src/fmt.ts
|
|
6
|
+
function fmtDate(val) {
|
|
7
|
+
if (val === null || val === void 0) return `${clr.dim}\u2014${clr.reset}`;
|
|
8
|
+
const d = typeof val === "number" ? new Date(val) : new Date(val);
|
|
9
|
+
if (isNaN(d.getTime())) return `${clr.dim}\u2014${clr.reset}`;
|
|
10
|
+
return `${clr.dim}${d.toISOString().slice(0, 10)}${clr.reset}`;
|
|
11
|
+
}
|
|
12
|
+
function fmtRole(role) {
|
|
13
|
+
if (role === "owner") return `${clr.cyan}owner${clr.reset}`;
|
|
14
|
+
if (role === "admin") return `${clr.yellow}admin${clr.reset}`;
|
|
15
|
+
if (role === "member") return "member";
|
|
16
|
+
return `${clr.dim}${role ?? "\u2014"}${clr.reset}`;
|
|
17
|
+
}
|
|
18
|
+
function fmtStatus(revokedAt) {
|
|
19
|
+
return revokedAt ? `${clr.dim}revoked${clr.reset}` : `${clr.green}active${clr.reset}`;
|
|
20
|
+
}
|
|
21
|
+
function fmtBool(val) {
|
|
22
|
+
return val ? `${clr.green}yes${clr.reset}` : `${clr.dim}no${clr.reset}`;
|
|
23
|
+
}
|
|
24
|
+
function fmtCount(n) {
|
|
25
|
+
if (!n) return `${clr.dim}0${clr.reset}`;
|
|
26
|
+
return String(n);
|
|
27
|
+
}
|
|
28
|
+
function fmtId(id) {
|
|
29
|
+
if (!id) return `${clr.dim}\u2014${clr.reset}`;
|
|
30
|
+
return `${clr.dim}${id}${clr.reset}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
fmtDate,
|
|
35
|
+
fmtRole,
|
|
36
|
+
fmtStatus,
|
|
37
|
+
fmtBool,
|
|
38
|
+
fmtCount,
|
|
39
|
+
fmtId
|
|
40
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
confirmDestructive,
|
|
3
|
+
requiresContext,
|
|
4
|
+
requiresToken
|
|
5
|
+
} from "./chunk-TSVJD4R6.js";
|
|
6
|
+
import {
|
|
7
|
+
parseFlags
|
|
8
|
+
} from "./chunk-H4M2NPA2.js";
|
|
9
|
+
import {
|
|
10
|
+
fatal
|
|
11
|
+
} from "./chunk-C2YZKMJ7.js";
|
|
12
|
+
import {
|
|
13
|
+
createApiClient
|
|
14
|
+
} from "./chunk-XQGLG3X3.js";
|
|
15
|
+
|
|
16
|
+
// src/runner.ts
|
|
17
|
+
function createCliRunner(config) {
|
|
18
|
+
const allCommands = {};
|
|
19
|
+
const allAliases = {};
|
|
20
|
+
const helpSections = [];
|
|
21
|
+
let activeHooks = {};
|
|
22
|
+
return {
|
|
23
|
+
register(plugin) {
|
|
24
|
+
for (const [name, cmd] of Object.entries(plugin.commands)) {
|
|
25
|
+
allCommands[name] = cmd;
|
|
26
|
+
}
|
|
27
|
+
for (const [alias, target] of Object.entries(plugin.aliases ?? {})) {
|
|
28
|
+
allAliases[alias] = target;
|
|
29
|
+
}
|
|
30
|
+
if (plugin.helpText) helpSections.push(plugin.helpText);
|
|
31
|
+
},
|
|
32
|
+
hooks(h) {
|
|
33
|
+
activeHooks = { ...activeHooks, ...h };
|
|
34
|
+
},
|
|
35
|
+
async run(argv) {
|
|
36
|
+
const { args: topArgs, flags: topFlags } = parseFlags(argv);
|
|
37
|
+
if (topFlags["version"] || topFlags["v"]) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const rawCmd = topArgs[0];
|
|
41
|
+
const globalHelp = !rawCmd || argv[0] === "--help" || argv[0] === "-h";
|
|
42
|
+
if (globalHelp) {
|
|
43
|
+
console.log(helpSections.join("\n"));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const cmdName = allAliases[rawCmd] ?? rawCmd;
|
|
47
|
+
const cmd = allCommands[cmdName];
|
|
48
|
+
if (!cmd) {
|
|
49
|
+
fatal(`Unknown command: ${rawCmd}. Run with --help for usage.`);
|
|
50
|
+
}
|
|
51
|
+
const rawIdx = argv.indexOf(rawCmd);
|
|
52
|
+
const restArgv = argv.slice(rawIdx + 1);
|
|
53
|
+
const { args, flags } = parseFlags(restArgv);
|
|
54
|
+
const deps = {
|
|
55
|
+
argv: restArgv,
|
|
56
|
+
args,
|
|
57
|
+
flags,
|
|
58
|
+
http: createApiClient(config.getToken, config.getApiBase),
|
|
59
|
+
ctx: config.ctx,
|
|
60
|
+
appBase: config.appBase,
|
|
61
|
+
token: config.getToken()
|
|
62
|
+
};
|
|
63
|
+
await activeHooks.beforeCommand?.(cmdName, deps);
|
|
64
|
+
const t0 = Date.now();
|
|
65
|
+
try {
|
|
66
|
+
await executeCommand(cmd, deps);
|
|
67
|
+
await activeHooks.afterCommand?.(cmdName, deps, Date.now() - t0);
|
|
68
|
+
} catch (e) {
|
|
69
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
70
|
+
if (activeHooks.onError) {
|
|
71
|
+
await activeHooks.onError(cmdName, err, deps);
|
|
72
|
+
} else {
|
|
73
|
+
fatal(err.message);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async function executeCommand(cmd, deps) {
|
|
80
|
+
const chain = [];
|
|
81
|
+
if (cmd.requiresToken !== false) chain.push(requiresToken());
|
|
82
|
+
if (cmd.requiresContext) chain.push(requiresContext());
|
|
83
|
+
if (cmd.confirm) chain.push(confirmDestructive(cmd.confirm));
|
|
84
|
+
if (cmd.middleware?.length) chain.push(...cmd.middleware);
|
|
85
|
+
let i = 0;
|
|
86
|
+
const next = async () => {
|
|
87
|
+
if (i < chain.length) {
|
|
88
|
+
await chain[i++](deps, next);
|
|
89
|
+
} else {
|
|
90
|
+
await cmd.fn(deps);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
await next();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export {
|
|
97
|
+
createCliRunner
|
|
98
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fatal,
|
|
3
|
+
prompt,
|
|
4
|
+
warn
|
|
5
|
+
} from "./chunk-C2YZKMJ7.js";
|
|
6
|
+
|
|
7
|
+
// src/middleware.ts
|
|
8
|
+
function requiresToken() {
|
|
9
|
+
return async (deps, next) => {
|
|
10
|
+
if (!deps.token) {
|
|
11
|
+
fatal("No API token found. Run: login");
|
|
12
|
+
}
|
|
13
|
+
await next();
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function requiresContext() {
|
|
17
|
+
return async (deps, next) => {
|
|
18
|
+
const ctx = deps.ctx.getContext();
|
|
19
|
+
if (!ctx) {
|
|
20
|
+
fatal("No active context. Set one with: use <org>");
|
|
21
|
+
}
|
|
22
|
+
deps.context = ctx;
|
|
23
|
+
await next();
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function confirmDestructive(message) {
|
|
27
|
+
return async (deps, next) => {
|
|
28
|
+
if (deps.flags["yes"] || deps.flags["y"]) {
|
|
29
|
+
await next();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const isTTY = Boolean(process.stdin.isTTY);
|
|
33
|
+
if (!isTTY) {
|
|
34
|
+
fatal(`Destructive operation requires confirmation. Re-run with --yes to proceed.
|
|
35
|
+
${message}`);
|
|
36
|
+
}
|
|
37
|
+
warn(message);
|
|
38
|
+
const answer = await prompt("Continue? (y/N) ");
|
|
39
|
+
if (answer.toLowerCase() !== "y") {
|
|
40
|
+
console.log("Aborted.");
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
await next();
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
requiresToken,
|
|
49
|
+
requiresContext,
|
|
50
|
+
confirmDestructive
|
|
51
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
var ApiError = class extends Error {
|
|
3
|
+
constructor(status, code, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.status = status;
|
|
6
|
+
this.code = code;
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
function createApiClient(getToken, getApiBase, getExtraHeaders) {
|
|
10
|
+
async function api(method, path, body, extra = {}) {
|
|
11
|
+
const token = getToken();
|
|
12
|
+
const base = getApiBase().replace(/\/$/, "");
|
|
13
|
+
const headers = {
|
|
14
|
+
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
15
|
+
...body !== void 0 ? { "Content-Type": "application/json" } : {},
|
|
16
|
+
...getExtraHeaders ? getExtraHeaders() : {},
|
|
17
|
+
...extra
|
|
18
|
+
// per-call headers override defaults
|
|
19
|
+
};
|
|
20
|
+
const res = await fetch(`${base}${path}`, {
|
|
21
|
+
method,
|
|
22
|
+
headers,
|
|
23
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
24
|
+
});
|
|
25
|
+
if (res.status === 204) return null;
|
|
26
|
+
const text = await res.text();
|
|
27
|
+
let data;
|
|
28
|
+
try {
|
|
29
|
+
data = JSON.parse(text);
|
|
30
|
+
} catch {
|
|
31
|
+
if (!res.ok) throw new ApiError(res.status, "parse_error", `${res.status}: ${text}`);
|
|
32
|
+
return text;
|
|
33
|
+
}
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
const err = data;
|
|
36
|
+
const code = err?.error?.code ?? "unknown";
|
|
37
|
+
const msg = err?.error?.message ?? res.statusText;
|
|
38
|
+
throw new ApiError(res.status, code, `${res.status} ${code}: ${msg}`);
|
|
39
|
+
}
|
|
40
|
+
return data;
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
api,
|
|
44
|
+
get: (path, h) => api("GET", path, void 0, h),
|
|
45
|
+
post: (path, body, h) => api("POST", path, body, h),
|
|
46
|
+
patch: (path, body, h) => api("PATCH", path, body, h),
|
|
47
|
+
del: (path, h) => api("DELETE", path, void 0, h)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export {
|
|
52
|
+
ApiError,
|
|
53
|
+
createApiClient
|
|
54
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// src/context.ts
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
function createContextManager(appName) {
|
|
6
|
+
const globalDir = join(homedir(), `.${appName}`);
|
|
7
|
+
const globalPath = join(globalDir, "config.json");
|
|
8
|
+
function resolveActivePath() {
|
|
9
|
+
const localDir = join(process.cwd(), `.${appName}`);
|
|
10
|
+
const localPath = join(localDir, "config.json");
|
|
11
|
+
const isLocal = existsSync(localPath) || existsSync(localDir);
|
|
12
|
+
return isLocal ? { cfgPath: localPath, cfgDir: localDir, isLocal: true } : { cfgPath: globalPath, cfgDir: globalDir, isLocal: false };
|
|
13
|
+
}
|
|
14
|
+
function resolveReadPath() {
|
|
15
|
+
return resolveActivePath().cfgPath;
|
|
16
|
+
}
|
|
17
|
+
function resolveWritePath() {
|
|
18
|
+
const { cfgPath, cfgDir } = resolveActivePath();
|
|
19
|
+
return { cfgPath, cfgDir };
|
|
20
|
+
}
|
|
21
|
+
function loadConfig() {
|
|
22
|
+
const cfgPath = resolveReadPath();
|
|
23
|
+
if (!existsSync(cfgPath)) return {};
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(readFileSync(cfgPath, "utf8"));
|
|
26
|
+
} catch {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function saveConfig(cfg) {
|
|
31
|
+
const { cfgPath, cfgDir } = resolveWritePath();
|
|
32
|
+
mkdirSync(cfgDir, { recursive: true });
|
|
33
|
+
writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + "\n");
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
get configPath() {
|
|
37
|
+
return resolveWritePath().cfgPath;
|
|
38
|
+
},
|
|
39
|
+
loadConfig,
|
|
40
|
+
saveConfig,
|
|
41
|
+
getContext() {
|
|
42
|
+
return loadConfig().context;
|
|
43
|
+
},
|
|
44
|
+
setContext(parts) {
|
|
45
|
+
const cfg = loadConfig();
|
|
46
|
+
cfg.context = { ...cfg.context ?? {}, ...parts };
|
|
47
|
+
saveConfig(cfg);
|
|
48
|
+
},
|
|
49
|
+
clearContext() {
|
|
50
|
+
const cfg = loadConfig();
|
|
51
|
+
delete cfg.context;
|
|
52
|
+
saveConfig(cfg);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
createContextManager
|
|
59
|
+
};
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic HTTP client factory for Node CLI binaries.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* const { get, post, patch, del } = createApiClient(
|
|
6
|
+
* () => cfg.token ?? process.env.MY_TOKEN,
|
|
7
|
+
* () => cfg.apiBase ?? 'https://api.example.com',
|
|
8
|
+
* );
|
|
9
|
+
*/
|
|
10
|
+
interface ApiClient {
|
|
11
|
+
api<T>(method: string, path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
|
|
12
|
+
get<T>(path: string, headers?: Record<string, string>): Promise<T>;
|
|
13
|
+
post<T>(path: string, body: unknown, headers?: Record<string, string>): Promise<T>;
|
|
14
|
+
patch<T>(path: string, body: unknown, headers?: Record<string, string>): Promise<T>;
|
|
15
|
+
del<T>(path: string, headers?: Record<string, string>): Promise<T>;
|
|
16
|
+
}
|
|
17
|
+
declare class ApiError extends Error {
|
|
18
|
+
readonly status: number;
|
|
19
|
+
readonly code: string;
|
|
20
|
+
constructor(status: number, code: string, message: string);
|
|
21
|
+
}
|
|
22
|
+
declare function createApiClient(getToken: () => string | undefined, getApiBase: () => string, getExtraHeaders?: () => Record<string, string>): ApiClient;
|
|
23
|
+
|
|
24
|
+
export { type ApiClient, ApiError, createApiClient };
|