@bpmnkit/cli 0.0.9
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/README.md +113 -0
- package/dist/args.js +101 -0
- package/dist/client.js +19 -0
- package/dist/color.js +50 -0
- package/dist/commands/admin-shared.js +180 -0
- package/dist/commands/bpmn.js +143 -0
- package/dist/commands/completion.js +52 -0
- package/dist/commands/connector.js +172 -0
- package/dist/commands/index.js +47 -0
- package/dist/commands/profile.js +325 -0
- package/dist/commands/relations.js +28 -0
- package/dist/commands/settings.js +138 -0
- package/dist/commands/shared.js +196 -0
- package/dist/commands/worker.js +135 -0
- package/dist/completion.js +89 -0
- package/dist/generated/admin-commands.js +407 -0
- package/dist/generated/commands.js +2101 -0
- package/dist/help.js +117 -0
- package/dist/index.js +4 -0
- package/dist/output.js +262 -0
- package/dist/profile-tui.js +229 -0
- package/dist/profile.js +103 -0
- package/dist/run.js +210 -0
- package/dist/settings-tui.js +195 -0
- package/dist/tui.js +2544 -0
- package/dist/types.js +2 -0
- package/package.json +32 -0
package/dist/help.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { bold, cyan, dim, green } from "./color.js";
|
|
3
|
+
const GLOBAL_FLAGS = [
|
|
4
|
+
{
|
|
5
|
+
name: "profile",
|
|
6
|
+
short: "p",
|
|
7
|
+
description: "Profile to use",
|
|
8
|
+
type: "string",
|
|
9
|
+
placeholder: "NAME",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: "output",
|
|
13
|
+
short: "o",
|
|
14
|
+
description: "Output format: table|json|yaml",
|
|
15
|
+
type: "string",
|
|
16
|
+
default: "table",
|
|
17
|
+
placeholder: "FORMAT",
|
|
18
|
+
},
|
|
19
|
+
{ name: "no-color", description: "Disable colored output", type: "boolean" },
|
|
20
|
+
{ name: "debug", description: "Print debug information", type: "boolean" },
|
|
21
|
+
{ name: "help", short: "h", description: "Show help for this command", type: "boolean" },
|
|
22
|
+
];
|
|
23
|
+
const VERSION = (() => {
|
|
24
|
+
try {
|
|
25
|
+
const url = new URL("../package.json", import.meta.url);
|
|
26
|
+
const pkg = JSON.parse(readFileSync(url, "utf8"));
|
|
27
|
+
return pkg.version ?? "unknown";
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return "unknown";
|
|
31
|
+
}
|
|
32
|
+
})();
|
|
33
|
+
const BINARY = "casen";
|
|
34
|
+
export function printVersion() {
|
|
35
|
+
process.stdout.write(`${BINARY} ${VERSION}\n`);
|
|
36
|
+
}
|
|
37
|
+
export function printGlobalHelp(groups, colors) {
|
|
38
|
+
process.stdout.write(`${bold(`${BINARY} — Camunda v2 REST API CLI`, colors)}\n\n`);
|
|
39
|
+
process.stdout.write(`${bold("USAGE", colors)}\n`);
|
|
40
|
+
process.stdout.write(` ${BINARY} <resource> <command> [args] [flags]\n\n`);
|
|
41
|
+
process.stdout.write(`${bold("RESOURCES", colors)}\n`);
|
|
42
|
+
const maxName = groups.reduce((m, g) => Math.max(m, g.name.length), 0);
|
|
43
|
+
for (const g of groups) {
|
|
44
|
+
const aliases = g.aliases?.length ? dim(` (${g.aliases.join(", ")})`, colors) : "";
|
|
45
|
+
process.stdout.write(` ${cyan(g.name.padEnd(maxName), colors)}${aliases} ${g.description}\n`);
|
|
46
|
+
}
|
|
47
|
+
process.stdout.write(`\n${bold("FLAGS", colors)}\n`);
|
|
48
|
+
printFlags(GLOBAL_FLAGS, colors);
|
|
49
|
+
process.stdout.write(`\n${bold("EXAMPLES", colors)}\n`);
|
|
50
|
+
process.stdout.write(` ${dim("# List active process instances", colors)}\n`);
|
|
51
|
+
process.stdout.write(` ${BINARY} process-instance list --filter '{"state":"ACTIVE"}'\n\n`);
|
|
52
|
+
process.stdout.write(` ${dim("# Switch profile", colors)}\n`);
|
|
53
|
+
process.stdout.write(` ${BINARY} profile use production\n\n`);
|
|
54
|
+
process.stdout.write(` ${dim("# Enable shell completions (zsh)", colors)}\n`);
|
|
55
|
+
process.stdout.write(` ${BINARY} completion zsh > ~/.zfunc/_casen\n\n`);
|
|
56
|
+
process.stdout.write(`${dim(`Run \`${BINARY} <resource> --help\` for resource-level help.`, colors)}\n`);
|
|
57
|
+
}
|
|
58
|
+
export function printGroupHelp(group, colors) {
|
|
59
|
+
const aliases = group.aliases?.length ? ` (${group.aliases.join(", ")})` : "";
|
|
60
|
+
process.stdout.write(`${bold(`${BINARY} ${group.name}`, colors)}${aliases} — ${group.description}\n\n`);
|
|
61
|
+
process.stdout.write(`${bold("USAGE", colors)}\n`);
|
|
62
|
+
process.stdout.write(` ${BINARY} ${group.name} <command> [args] [flags]\n\n`);
|
|
63
|
+
process.stdout.write(`${bold("COMMANDS", colors)}\n`);
|
|
64
|
+
const maxName = group.commands.reduce((m, c) => Math.max(m, c.name.length), 0);
|
|
65
|
+
for (const cmd of group.commands) {
|
|
66
|
+
const aliases = cmd.aliases?.length ? dim(` (${cmd.aliases.join(", ")})`, colors) : "";
|
|
67
|
+
process.stdout.write(` ${cyan(cmd.name.padEnd(maxName), colors)}${aliases} ${cmd.description}\n`);
|
|
68
|
+
}
|
|
69
|
+
process.stdout.write(`\n${bold("FLAGS", colors)}\n`);
|
|
70
|
+
printFlags(GLOBAL_FLAGS, colors);
|
|
71
|
+
process.stdout.write(`\n${dim(`Run \`${BINARY} ${group.name} <command> --help\` for command details.`, colors)}\n`);
|
|
72
|
+
}
|
|
73
|
+
export function printCommandHelp(group, cmd, colors) {
|
|
74
|
+
process.stdout.write(`${bold(`${BINARY} ${group.name} ${cmd.name}`, colors)} — ${cmd.description}\n\n`);
|
|
75
|
+
// Usage
|
|
76
|
+
const argPart = cmd.args
|
|
77
|
+
? cmd.args.map((a) => (a.required ? `<${a.name}>` : `[${a.name}]`)).join(" ")
|
|
78
|
+
: "";
|
|
79
|
+
process.stdout.write(`${bold("USAGE", colors)}\n`);
|
|
80
|
+
process.stdout.write(` ${BINARY} ${group.name} ${cmd.name}${argPart ? ` ${argPart}` : ""} [flags]\n\n`);
|
|
81
|
+
// Args
|
|
82
|
+
if (cmd.args?.length) {
|
|
83
|
+
process.stdout.write(`${bold("ARGUMENTS", colors)}\n`);
|
|
84
|
+
const maxName = cmd.args.reduce((m, a) => Math.max(m, a.name.length), 0);
|
|
85
|
+
for (const arg of cmd.args) {
|
|
86
|
+
const req = arg.required ? "" : dim(" (optional)", colors);
|
|
87
|
+
process.stdout.write(` ${cyan(`<${arg.name}>`.padEnd(maxName + 2), colors)}${req} ${arg.description}\n`);
|
|
88
|
+
}
|
|
89
|
+
process.stdout.write("\n");
|
|
90
|
+
}
|
|
91
|
+
// Flags
|
|
92
|
+
const allFlags = [...(cmd.flags ?? []), ...GLOBAL_FLAGS];
|
|
93
|
+
process.stdout.write(`${bold("FLAGS", colors)}\n`);
|
|
94
|
+
printFlags(allFlags, colors);
|
|
95
|
+
// Examples
|
|
96
|
+
if (cmd.examples?.length) {
|
|
97
|
+
process.stdout.write(`\n${bold("EXAMPLES", colors)}\n`);
|
|
98
|
+
for (const ex of cmd.examples) {
|
|
99
|
+
process.stdout.write(` ${dim(`# ${ex.description}`, colors)}\n`);
|
|
100
|
+
process.stdout.write(` ${green(ex.command, colors)}\n\n`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function printFlags(flags, colors) {
|
|
105
|
+
const entries = flags.map((f) => {
|
|
106
|
+
const shortPart = f.short ? `-${f.short}, ` : " ";
|
|
107
|
+
const placeholder = f.placeholder ? ` <${f.placeholder}>` : "";
|
|
108
|
+
const longPart = `--${f.name}${placeholder}`;
|
|
109
|
+
return { shortPart, longPart, description: f.description, def: f.default };
|
|
110
|
+
});
|
|
111
|
+
const maxLong = entries.reduce((m, e) => Math.max(m, e.longPart.length), 0);
|
|
112
|
+
for (const { shortPart, longPart, description, def } of entries) {
|
|
113
|
+
const defPart = def !== undefined ? dim(` (default: ${def})`, colors) : "";
|
|
114
|
+
process.stdout.write(` ${dim(shortPart, colors)}${cyan(longPart.padEnd(maxLong), colors)} ${description}${defPart}\n`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=help.js.map
|
package/dist/index.js
ADDED
package/dist/output.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { bold, cyan, dim, green, red, shouldUseColor, stateColor } from "./color.js";
|
|
2
|
+
// ─── Formatting helpers ────────────────────────────────────────────────────────
|
|
3
|
+
/** Format an ISO date string to a readable local time. */
|
|
4
|
+
function fmtDate(v) {
|
|
5
|
+
if (!v)
|
|
6
|
+
return "—";
|
|
7
|
+
const d = new Date(String(v));
|
|
8
|
+
if (Number.isNaN(d.getTime()))
|
|
9
|
+
return String(v);
|
|
10
|
+
return d.toLocaleString("en-US", {
|
|
11
|
+
year: "numeric",
|
|
12
|
+
month: "2-digit",
|
|
13
|
+
day: "2-digit",
|
|
14
|
+
hour: "2-digit",
|
|
15
|
+
minute: "2-digit",
|
|
16
|
+
second: "2-digit",
|
|
17
|
+
hour12: false,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/** Format a value for table display. */
|
|
21
|
+
function formatCell(value, transform) {
|
|
22
|
+
if (transform)
|
|
23
|
+
return transform(value);
|
|
24
|
+
if (value === null || value === undefined)
|
|
25
|
+
return "—";
|
|
26
|
+
if (typeof value === "object")
|
|
27
|
+
return JSON.stringify(value);
|
|
28
|
+
return String(value);
|
|
29
|
+
}
|
|
30
|
+
/** Truncate a string to maxLen, appending "…" if truncated. */
|
|
31
|
+
function truncate(s, maxLen) {
|
|
32
|
+
if (maxLen > 0 && s.length > maxLen)
|
|
33
|
+
return `${s.slice(0, maxLen - 1)}…`;
|
|
34
|
+
return s;
|
|
35
|
+
}
|
|
36
|
+
/** Strip ANSI escape codes to get the visual width of a string. */
|
|
37
|
+
function visibleLength(s) {
|
|
38
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: needed for ANSI stripping
|
|
39
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
40
|
+
}
|
|
41
|
+
/** Pad a string to visually fill `width` characters (accounting for ANSI codes). */
|
|
42
|
+
function padEnd(s, width) {
|
|
43
|
+
const vis = visibleLength(s);
|
|
44
|
+
return vis < width ? s + " ".repeat(width - vis) : s;
|
|
45
|
+
}
|
|
46
|
+
// ─── YAML serializer ──────────────────────────────────────────────────────────
|
|
47
|
+
function toYaml(value, indent = 0) {
|
|
48
|
+
const pad = " ".repeat(indent);
|
|
49
|
+
if (value === null || value === undefined)
|
|
50
|
+
return "null\n";
|
|
51
|
+
if (typeof value === "boolean")
|
|
52
|
+
return `${value}\n`;
|
|
53
|
+
if (typeof value === "number")
|
|
54
|
+
return `${value}\n`;
|
|
55
|
+
if (typeof value === "string") {
|
|
56
|
+
if (/[\n:#{}"',]/.test(value))
|
|
57
|
+
return `"${value.replace(/"/g, '\\"')}"\n`;
|
|
58
|
+
return `${value}\n`;
|
|
59
|
+
}
|
|
60
|
+
if (Array.isArray(value)) {
|
|
61
|
+
if (value.length === 0)
|
|
62
|
+
return "[]\n";
|
|
63
|
+
return value.map((item) => `${pad}- ${toYaml(item, indent + 1).trimStart()}`).join("");
|
|
64
|
+
}
|
|
65
|
+
if (typeof value === "object") {
|
|
66
|
+
const obj = value;
|
|
67
|
+
const keys = Object.keys(obj);
|
|
68
|
+
if (keys.length === 0)
|
|
69
|
+
return "{}\n";
|
|
70
|
+
return keys.map((k) => `${pad}${k}: ${toYaml(obj[k], indent + 1).trimStart()}`).join("");
|
|
71
|
+
}
|
|
72
|
+
return `${String(value)}\n`;
|
|
73
|
+
}
|
|
74
|
+
// ─── Table renderer ───────────────────────────────────────────────────────────
|
|
75
|
+
function printTable(rows, columns, colors) {
|
|
76
|
+
if (rows.length === 0) {
|
|
77
|
+
process.stdout.write(dim("No results.\n", colors));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Calculate column widths
|
|
81
|
+
const widths = columns.map((col) => {
|
|
82
|
+
const dataMax = rows.reduce((max, row) => {
|
|
83
|
+
const raw = formatCell(row[col.key], col.transform);
|
|
84
|
+
const cell = col.maxWidth ? truncate(raw, col.maxWidth) : raw;
|
|
85
|
+
return Math.max(max, visibleLength(cell));
|
|
86
|
+
}, 0);
|
|
87
|
+
return Math.max(col.header.length, dataMax);
|
|
88
|
+
});
|
|
89
|
+
// Header
|
|
90
|
+
const header = columns
|
|
91
|
+
.map((col, i) => padEnd(bold(col.header, colors), widths[i] ?? col.header.length))
|
|
92
|
+
.join(" ");
|
|
93
|
+
process.stdout.write(`${header}\n`);
|
|
94
|
+
process.stdout.write(`${dim("─".repeat(visibleLength(header)), colors)}\n`);
|
|
95
|
+
// Rows
|
|
96
|
+
for (const row of rows) {
|
|
97
|
+
const line = columns
|
|
98
|
+
.map((col, i) => {
|
|
99
|
+
const width = widths[i] ?? col.maxWidth ?? 20;
|
|
100
|
+
const raw = formatCell(row[col.key], col.transform);
|
|
101
|
+
const cell = col.maxWidth ? truncate(raw, col.maxWidth) : raw;
|
|
102
|
+
// Apply state colors automatically
|
|
103
|
+
const colored = col.key === "state" || col.key === "errorState" ? stateColor(cell, colors) : cell;
|
|
104
|
+
return padEnd(colored, width);
|
|
105
|
+
})
|
|
106
|
+
.join(" ");
|
|
107
|
+
process.stdout.write(`${line}\n`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Print a single object as aligned key-value pairs. */
|
|
111
|
+
function printKV(obj, colors) {
|
|
112
|
+
const entries = Object.entries(obj).filter(([, v]) => v !== undefined);
|
|
113
|
+
const keyWidth = entries.reduce((max, [k]) => Math.max(max, k.length), 0);
|
|
114
|
+
for (const [k, v] of entries) {
|
|
115
|
+
const label = padEnd(cyan(k, colors), keyWidth);
|
|
116
|
+
const value = v === null || v === undefined ? dim("—", colors) : String(v);
|
|
117
|
+
process.stdout.write(`${label} ${value}\n`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** Recursively flatten an object for KV display. Arrays of objects are expanded with [i] keys. */
|
|
121
|
+
function flattenForDisplay(obj, prefix = "") {
|
|
122
|
+
if (typeof obj !== "object" || obj === null)
|
|
123
|
+
return { value: obj };
|
|
124
|
+
const result = {};
|
|
125
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
126
|
+
const key = prefix ? `${prefix}.${k}` : k;
|
|
127
|
+
if (Array.isArray(v)) {
|
|
128
|
+
if (v.length === 0) {
|
|
129
|
+
result[key] = "[]";
|
|
130
|
+
}
|
|
131
|
+
else if (v.every((item) => typeof item !== "object" || item === null)) {
|
|
132
|
+
result[key] = v.join(", ");
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
for (let i = 0; i < v.length; i++) {
|
|
136
|
+
const elem = v[i];
|
|
137
|
+
if (typeof elem === "object" && elem !== null) {
|
|
138
|
+
Object.assign(result, flattenForDisplay(elem, `${key}[${i}]`));
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
result[`${key}[${i}]`] = elem;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else if (typeof v === "object" && v !== null) {
|
|
147
|
+
Object.assign(result, flattenForDisplay(v, key));
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
result[key] = v;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
// ─── OutputWriter factory ────────────────────────────────────────────────────
|
|
156
|
+
export function createOutputWriter(format, noColor) {
|
|
157
|
+
const colors = shouldUseColor(noColor);
|
|
158
|
+
return {
|
|
159
|
+
format,
|
|
160
|
+
isInteractive: process.stdout.isTTY === true,
|
|
161
|
+
printList(data, columns) {
|
|
162
|
+
// Unwrap { items: [...], page: {...} } envelope
|
|
163
|
+
let items;
|
|
164
|
+
let total;
|
|
165
|
+
if (typeof data === "object" &&
|
|
166
|
+
data !== null &&
|
|
167
|
+
"items" in data &&
|
|
168
|
+
Array.isArray(data.items)) {
|
|
169
|
+
items = data.items;
|
|
170
|
+
const page = data.page;
|
|
171
|
+
if (typeof page === "object" && page !== null && "totalItems" in page) {
|
|
172
|
+
total = page.totalItems;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else if (Array.isArray(data)) {
|
|
176
|
+
items = data;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
items = [data];
|
|
180
|
+
}
|
|
181
|
+
if (format === "json") {
|
|
182
|
+
process.stdout.write(`${JSON.stringify(items, null, 2)}\n`);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (format === "yaml") {
|
|
186
|
+
process.stdout.write(toYaml(items));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
printTable(items, columns, colors);
|
|
190
|
+
const count = total !== undefined ? total : items.length;
|
|
191
|
+
process.stdout.write(`\n${dim(`${count} ${count === 1 ? "item" : "items"}`, colors)}\n`);
|
|
192
|
+
},
|
|
193
|
+
printItem(data) {
|
|
194
|
+
if (format === "json") {
|
|
195
|
+
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (format === "yaml") {
|
|
199
|
+
process.stdout.write(toYaml(data));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const obj = typeof data === "object" && data !== null
|
|
203
|
+
? flattenForDisplay(data)
|
|
204
|
+
: { value: String(data) };
|
|
205
|
+
printKV(obj, colors);
|
|
206
|
+
},
|
|
207
|
+
print(data) {
|
|
208
|
+
if (format === "json") {
|
|
209
|
+
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (format === "yaml") {
|
|
213
|
+
process.stdout.write(toYaml(data));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (typeof data === "string") {
|
|
217
|
+
process.stdout.write(`${data}\n`);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
|
|
221
|
+
},
|
|
222
|
+
ok(msg) {
|
|
223
|
+
process.stdout.write(`${green("✓", colors)} ${msg}\n`);
|
|
224
|
+
},
|
|
225
|
+
info(msg) {
|
|
226
|
+
process.stdout.write(`${dim("→", colors)} ${msg}\n`);
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/** Date transformer for use in ColumnDef. */
|
|
231
|
+
export const dateTransform = fmtDate;
|
|
232
|
+
/** An OutputWriter that discards all output (used when --raw suppresses formatted output). */
|
|
233
|
+
export function createNullWriter() {
|
|
234
|
+
return {
|
|
235
|
+
format: "table",
|
|
236
|
+
isInteractive: false,
|
|
237
|
+
printList() { },
|
|
238
|
+
printItem() { },
|
|
239
|
+
print() { },
|
|
240
|
+
ok() { },
|
|
241
|
+
info() { },
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/** Print a raw HTTP response (status, headers, body) to stdout. */
|
|
245
|
+
export function printRawResponse(raw, noColor) {
|
|
246
|
+
const colors = shouldUseColor(noColor);
|
|
247
|
+
const statusFn = raw.status >= 200 && raw.status < 300 ? green : red;
|
|
248
|
+
process.stdout.write(`${statusFn(`HTTP ${raw.status}`, colors)}\n`);
|
|
249
|
+
for (const [k, v] of Object.entries(raw.headers)) {
|
|
250
|
+
process.stdout.write(`${cyan(k, colors)}: ${v}\n`);
|
|
251
|
+
}
|
|
252
|
+
process.stdout.write("\n");
|
|
253
|
+
// Pretty-print JSON body if possible, otherwise raw text
|
|
254
|
+
try {
|
|
255
|
+
const parsed = JSON.parse(raw.body);
|
|
256
|
+
process.stdout.write(`${JSON.stringify(parsed, null, 2)}\n`);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
process.stdout.write(`${raw.body}\n`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=output.js.map
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { deleteProfile, getActiveName, listProfiles, useProfile } from "@bpmnkit/profiles";
|
|
2
|
+
// ─── ANSI helpers ─────────────────────────────────────────────────────────────
|
|
3
|
+
const CSI = "\x1b[";
|
|
4
|
+
const HIDE_CURSOR = `${CSI}?25l`;
|
|
5
|
+
const SHOW_CURSOR = `${CSI}?25h`;
|
|
6
|
+
const ALT_ON = `${CSI}?1049h`;
|
|
7
|
+
const ALT_OFF = `${CSI}?1049l`;
|
|
8
|
+
const CLEAR = `${CSI}2J${CSI}H`;
|
|
9
|
+
function inv(s) {
|
|
10
|
+
return `${CSI}7m${s}${CSI}m`;
|
|
11
|
+
}
|
|
12
|
+
function bold(s) {
|
|
13
|
+
return `${CSI}1m${s}${CSI}m`;
|
|
14
|
+
}
|
|
15
|
+
function dim(s) {
|
|
16
|
+
return `${CSI}2m${s}${CSI}m`;
|
|
17
|
+
}
|
|
18
|
+
function green(s) {
|
|
19
|
+
return `${CSI}32m${s}${CSI}m`;
|
|
20
|
+
}
|
|
21
|
+
function red(s) {
|
|
22
|
+
return `${CSI}31m${s}${CSI}m`;
|
|
23
|
+
}
|
|
24
|
+
function cyan(s) {
|
|
25
|
+
return `${CSI}36m${s}${CSI}m`;
|
|
26
|
+
}
|
|
27
|
+
/** Strip ANSI codes to get visible length. */
|
|
28
|
+
function vlen(s) {
|
|
29
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: needed for ANSI stripping
|
|
30
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
31
|
+
}
|
|
32
|
+
/** Truncate and pad to exactly n visible characters. */
|
|
33
|
+
function fit(s, n) {
|
|
34
|
+
if (s.length > n)
|
|
35
|
+
return `${s.slice(0, n - 1)}…`;
|
|
36
|
+
return s.padEnd(n);
|
|
37
|
+
}
|
|
38
|
+
function loadRows() {
|
|
39
|
+
const profiles = listProfiles();
|
|
40
|
+
const activeName = getActiveName();
|
|
41
|
+
const rows = profiles.map((p) => ({
|
|
42
|
+
name: p.name,
|
|
43
|
+
apiType: p.apiType,
|
|
44
|
+
url: (p.config.baseUrl ?? ""),
|
|
45
|
+
authType: p.config.auth?.type ?? "—",
|
|
46
|
+
createdAt: p.createdAt ? p.createdAt.slice(0, 10) : "—",
|
|
47
|
+
}));
|
|
48
|
+
return { rows, activeName };
|
|
49
|
+
}
|
|
50
|
+
// ─── Render ───────────────────────────────────────────────────────────────────
|
|
51
|
+
function render(state) {
|
|
52
|
+
const { rows, activeName, cursor, selected, message, confirmDelete } = state;
|
|
53
|
+
const termCols = process.stdout.columns ?? 80;
|
|
54
|
+
// Fixed cols: 2 indent + 3 chk + 1 sp + 1 active + 1 sp + 16 name + 1 sp + 8 auth + 1 sp + 10 date = 44
|
|
55
|
+
const urlWidth = Math.max(16, termCols - 53);
|
|
56
|
+
const out = [];
|
|
57
|
+
out.push("");
|
|
58
|
+
out.push(` ${bold("casen — Profile Manager")} ${dim(`${rows.length} profile${rows.length !== 1 ? "s" : ""}`)}`);
|
|
59
|
+
out.push("");
|
|
60
|
+
if (rows.length === 0) {
|
|
61
|
+
out.push(dim(" No profiles yet."));
|
|
62
|
+
out.push(dim(" Create one with: casen profile create <name> ..."));
|
|
63
|
+
out.push("");
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// Header
|
|
67
|
+
const hdr = ` ${dim(" ")} ${dim(" ")} ${dim(fit("NAME", 16))} ` +
|
|
68
|
+
`${dim(fit("API", 6))} ${dim(fit("BASE URL", urlWidth))} ${dim(fit("AUTH", 8))} ${dim("CREATED")}`;
|
|
69
|
+
out.push(hdr);
|
|
70
|
+
out.push(dim(` ${"─".repeat(termCols - 4)}`));
|
|
71
|
+
for (let i = 0; i < rows.length; i++) {
|
|
72
|
+
const row = rows[i];
|
|
73
|
+
if (!row)
|
|
74
|
+
continue;
|
|
75
|
+
const isCursor = i === cursor;
|
|
76
|
+
const isSel = selected.has(i);
|
|
77
|
+
const isActive = row.name === activeName;
|
|
78
|
+
const chk = isSel ? green("[✓]") : dim("[ ]");
|
|
79
|
+
const act = isActive ? green("●") : " ";
|
|
80
|
+
const name = fit(row.name, 16);
|
|
81
|
+
const api = fit(row.apiType, 6);
|
|
82
|
+
const url = fit(row.url, urlWidth);
|
|
83
|
+
const auth = fit(row.authType, 8);
|
|
84
|
+
const date = row.createdAt;
|
|
85
|
+
// Build the line without leading spaces so inverse spans full width
|
|
86
|
+
const content = ` ${chk} ${act} ${name} ${api} ${url} ${auth} ${date}`;
|
|
87
|
+
// Pad to terminal width so inverse video fills the row
|
|
88
|
+
const visible = vlen(content);
|
|
89
|
+
const padded = content + " ".repeat(Math.max(0, termCols - visible - 1));
|
|
90
|
+
out.push(isCursor ? inv(padded) : padded);
|
|
91
|
+
}
|
|
92
|
+
out.push("");
|
|
93
|
+
}
|
|
94
|
+
// Status / help bar
|
|
95
|
+
if (confirmDelete) {
|
|
96
|
+
const n = selected.size;
|
|
97
|
+
out.push(red(` Delete ${n} profile${n !== 1 ? "s" : ""}? [y/N] `));
|
|
98
|
+
}
|
|
99
|
+
else if (message) {
|
|
100
|
+
out.push(` ${message}`);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
out.push(`${dim(" ")}${dim("↑↓")} navigate ${dim("space")} select ${cyan("d")} delete ${cyan("u")} activate ${cyan("q")} quit`);
|
|
104
|
+
}
|
|
105
|
+
process.stdout.write(`${CLEAR}${out.join("\n")}\n`);
|
|
106
|
+
}
|
|
107
|
+
// ─── Key handling ─────────────────────────────────────────────────────────────
|
|
108
|
+
function handleKey(key, state, done) {
|
|
109
|
+
// Confirmation prompt
|
|
110
|
+
if (state.confirmDelete) {
|
|
111
|
+
if (key === "y" || key === "Y") {
|
|
112
|
+
const names = [...state.selected]
|
|
113
|
+
.map((i) => state.rows[i]?.name)
|
|
114
|
+
.filter((n) => n !== undefined);
|
|
115
|
+
for (const name of names)
|
|
116
|
+
deleteProfile(name);
|
|
117
|
+
const { rows, activeName } = loadRows();
|
|
118
|
+
state.rows = rows;
|
|
119
|
+
state.activeName = activeName;
|
|
120
|
+
state.selected.clear();
|
|
121
|
+
state.cursor = Math.min(state.cursor, Math.max(0, rows.length - 1));
|
|
122
|
+
state.message = green(`✓ Deleted: ${names.join(", ")}`);
|
|
123
|
+
state.confirmDelete = false;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
state.confirmDelete = false;
|
|
127
|
+
state.message = "";
|
|
128
|
+
}
|
|
129
|
+
render(state);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
state.message = "";
|
|
133
|
+
switch (key) {
|
|
134
|
+
case "\x1b[A": // up arrow
|
|
135
|
+
if (state.rows.length > 0)
|
|
136
|
+
state.cursor = (state.cursor - 1 + state.rows.length) % state.rows.length;
|
|
137
|
+
break;
|
|
138
|
+
case "\x1b[B": // down arrow
|
|
139
|
+
if (state.rows.length > 0)
|
|
140
|
+
state.cursor = (state.cursor + 1) % state.rows.length;
|
|
141
|
+
break;
|
|
142
|
+
case " ": // toggle selection
|
|
143
|
+
if (state.rows.length > 0) {
|
|
144
|
+
if (state.selected.has(state.cursor))
|
|
145
|
+
state.selected.delete(state.cursor);
|
|
146
|
+
else
|
|
147
|
+
state.selected.add(state.cursor);
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
case "d":
|
|
151
|
+
case "D": {
|
|
152
|
+
// If nothing selected, select current row
|
|
153
|
+
if (state.selected.size === 0 && state.rows.length > 0) {
|
|
154
|
+
state.selected.add(state.cursor);
|
|
155
|
+
}
|
|
156
|
+
if (state.selected.size > 0)
|
|
157
|
+
state.confirmDelete = true;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
case "u":
|
|
161
|
+
case "U":
|
|
162
|
+
case "\r":
|
|
163
|
+
case "\n": {
|
|
164
|
+
const row = state.rows[state.cursor];
|
|
165
|
+
if (row) {
|
|
166
|
+
useProfile(row.name);
|
|
167
|
+
state.activeName = row.name;
|
|
168
|
+
state.message = green(`✓ Now using "${row.name}"`);
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
case "q":
|
|
173
|
+
case "Q":
|
|
174
|
+
case "\x03": // Ctrl+C
|
|
175
|
+
case "\x1b": // ESC
|
|
176
|
+
done();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
render(state);
|
|
180
|
+
}
|
|
181
|
+
// ─── Entry point ──────────────────────────────────────────────────────────────
|
|
182
|
+
export async function runProfileManager() {
|
|
183
|
+
// Non-interactive fallback (piped / no TTY)
|
|
184
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
185
|
+
const profiles = listProfiles();
|
|
186
|
+
const activeName = getActiveName();
|
|
187
|
+
if (profiles.length === 0) {
|
|
188
|
+
process.stdout.write("No profiles. Create one with: casen profile create <name> ...\n");
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
for (const p of profiles) {
|
|
192
|
+
const active = p.name === activeName ? " (active)" : "";
|
|
193
|
+
const date = p.createdAt ? ` ${p.createdAt.slice(0, 10)}` : "";
|
|
194
|
+
process.stdout.write(`${p.name}${active}${date}\n`);
|
|
195
|
+
}
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
process.stdout.write(ALT_ON + HIDE_CURSOR);
|
|
199
|
+
let cleaned = false;
|
|
200
|
+
const cleanup = () => {
|
|
201
|
+
if (cleaned)
|
|
202
|
+
return;
|
|
203
|
+
cleaned = true;
|
|
204
|
+
process.stdout.write(ALT_OFF + SHOW_CURSOR);
|
|
205
|
+
if (process.stdin.isTTY)
|
|
206
|
+
process.stdin.setRawMode(false);
|
|
207
|
+
process.stdin.pause();
|
|
208
|
+
};
|
|
209
|
+
process.on("exit", cleanup);
|
|
210
|
+
const { rows, activeName } = loadRows();
|
|
211
|
+
const state = {
|
|
212
|
+
rows,
|
|
213
|
+
activeName,
|
|
214
|
+
cursor: 0,
|
|
215
|
+
selected: new Set(),
|
|
216
|
+
message: "",
|
|
217
|
+
confirmDelete: false,
|
|
218
|
+
};
|
|
219
|
+
render(state);
|
|
220
|
+
await new Promise((resolve) => {
|
|
221
|
+
process.stdin.setRawMode(true);
|
|
222
|
+
process.stdin.resume();
|
|
223
|
+
process.stdin.setEncoding("utf8");
|
|
224
|
+
process.stdin.on("data", (key) => handleKey(key, state, resolve));
|
|
225
|
+
});
|
|
226
|
+
cleanup();
|
|
227
|
+
process.removeListener("exit", cleanup);
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=profile-tui.js.map
|