@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/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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { run } from "./run.js";
3
+ await run(process.argv.slice(2));
4
+ //# sourceMappingURL=index.js.map
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