@sushant-kum/ccstatus 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +16 -2
- package/dist/app-3OCESK5H.js +1393 -0
- package/dist/chunk-XYVU45PI.js +5035 -0
- package/dist/index.js +13 -0
- package/dist/statusline-cmd-AVLQRDFI.js +48 -0
- package/package.json +54 -4
|
@@ -0,0 +1,1393 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
COLORS,
|
|
4
|
+
configPath,
|
|
5
|
+
importCcstatusline,
|
|
6
|
+
isValidWhen,
|
|
7
|
+
loadConfigFile,
|
|
8
|
+
readSnapshot,
|
|
9
|
+
registry,
|
|
10
|
+
render,
|
|
11
|
+
rendererBg,
|
|
12
|
+
rendererColor,
|
|
13
|
+
saveConfigFile,
|
|
14
|
+
snapshotPath
|
|
15
|
+
} from "./chunk-XYVU45PI.js";
|
|
16
|
+
|
|
17
|
+
// src/app.tsx
|
|
18
|
+
import { Box as Box12, Text as Text12, useApp, useInput as useInput10 } from "ink";
|
|
19
|
+
import { useEffect, useMemo as useMemo2, useRef, useState as useState10 } from "react";
|
|
20
|
+
|
|
21
|
+
// src/mod-status.ts
|
|
22
|
+
import { execFile } from "child_process";
|
|
23
|
+
var MARKETPLACE_SLUG = "sushant-kum/ccstatus";
|
|
24
|
+
var PLUGIN_ID = "ccstatus@ccstatus";
|
|
25
|
+
var runCmd = (argv) => new Promise((resolve) => {
|
|
26
|
+
const [command, ...args] = argv;
|
|
27
|
+
if (command === void 0) {
|
|
28
|
+
resolve({ code: -1, stdout: "", stderr: "" });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
execFile(command, args, { timeout: 1e4, maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => {
|
|
32
|
+
const errCode = err?.code;
|
|
33
|
+
const code = typeof errCode === "number" ? errCode : err ? -1 : 0;
|
|
34
|
+
resolve({ code, stdout: stdout ?? "", stderr: stderr ?? "" });
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
async function detectModStatus(run = runCmd) {
|
|
38
|
+
let res;
|
|
39
|
+
try {
|
|
40
|
+
res = await run(["claude", "plugin", "list", "--json"]);
|
|
41
|
+
} catch {
|
|
42
|
+
return "unknown";
|
|
43
|
+
}
|
|
44
|
+
if (res.code !== 0) {
|
|
45
|
+
return "unknown";
|
|
46
|
+
}
|
|
47
|
+
let parsed;
|
|
48
|
+
try {
|
|
49
|
+
parsed = JSON.parse(res.stdout);
|
|
50
|
+
} catch {
|
|
51
|
+
return "unknown";
|
|
52
|
+
}
|
|
53
|
+
if (!Array.isArray(parsed)) {
|
|
54
|
+
return "unknown";
|
|
55
|
+
}
|
|
56
|
+
const entry = parsed.find(
|
|
57
|
+
(p) => !!p && typeof p === "object" && typeof p.id === "string" && p.id.startsWith("ccstatus@")
|
|
58
|
+
);
|
|
59
|
+
if (!entry) {
|
|
60
|
+
return "absent";
|
|
61
|
+
}
|
|
62
|
+
return entry.enabled === false ? "disabled" : "installed";
|
|
63
|
+
}
|
|
64
|
+
var firstLine = (s) => s.split("\n").map((l) => l.trim()).find(Boolean) ?? "";
|
|
65
|
+
async function installMod(run = runCmd) {
|
|
66
|
+
const add = await run(["claude", "plugin", "marketplace", "add", MARKETPLACE_SLUG]);
|
|
67
|
+
if (add.code !== 0) {
|
|
68
|
+
return {
|
|
69
|
+
ok: false,
|
|
70
|
+
message: `marketplace add failed: ${firstLine(add.stderr || add.stdout) || `exit ${add.code}`}`
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const inst = await run(["claude", "plugin", "install", PLUGIN_ID, "-y"]);
|
|
74
|
+
if (inst.code !== 0) {
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
message: `install failed: ${firstLine(inst.stderr || inst.stdout) || `exit ${inst.code}`}`
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
ok: true,
|
|
82
|
+
message: "mod installed \u2014 restart Claude Code (or run /reload-plugins) to see the bar"
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/preview.tsx
|
|
87
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
88
|
+
|
|
89
|
+
// src/paint.tsx
|
|
90
|
+
import { Box, Text } from "ink";
|
|
91
|
+
import { jsx } from "react/jsx-runtime";
|
|
92
|
+
var inkColor = (fg) => rendererColor(fg);
|
|
93
|
+
var inkBg = (bg) => rendererBg(bg);
|
|
94
|
+
function paintModel(model) {
|
|
95
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: model.lines.map((line, i) => /* @__PURE__ */ jsx(Box, { children: line.segments.map((s, j) => /* @__PURE__ */ jsx(Text, { color: inkColor(s.fg), backgroundColor: inkBg(s.bg), children: s.text }, j)) }, i)) });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/preview.tsx
|
|
99
|
+
import { jsxs } from "react/jsx-runtime";
|
|
100
|
+
function Preview({
|
|
101
|
+
config,
|
|
102
|
+
snapshot,
|
|
103
|
+
source,
|
|
104
|
+
width,
|
|
105
|
+
surface = "band"
|
|
106
|
+
}) {
|
|
107
|
+
const model = render(config, snapshot, { surface, width });
|
|
108
|
+
return /* @__PURE__ */ jsxs(Box2, { flexDirection: "column", marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: [
|
|
109
|
+
/* @__PURE__ */ jsxs(Text2, { color: "gray", children: [
|
|
110
|
+
"live preview \xB7 ",
|
|
111
|
+
surface,
|
|
112
|
+
" \xB7 ",
|
|
113
|
+
width,
|
|
114
|
+
" cols \xB7 ",
|
|
115
|
+
source,
|
|
116
|
+
" data"
|
|
117
|
+
] }),
|
|
118
|
+
paintModel(model)
|
|
119
|
+
] });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/screens/defaults.tsx
|
|
123
|
+
import { Box as Box3, Text as Text3, useInput } from "ink";
|
|
124
|
+
import { useState } from "react";
|
|
125
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
126
|
+
var SEPARATORS = ["powerline", "space", "none"];
|
|
127
|
+
var FIELDS = ["separator", "padding"];
|
|
128
|
+
function Defaults({ config, setConfig, goHome }) {
|
|
129
|
+
const [focus, setFocus] = useState(0);
|
|
130
|
+
const d = config.defaults;
|
|
131
|
+
const cycle = (dir) => {
|
|
132
|
+
const field = FIELDS[focus];
|
|
133
|
+
if (field === "separator") {
|
|
134
|
+
const i = SEPARATORS.indexOf(d.separator);
|
|
135
|
+
const next = SEPARATORS[(i + dir + SEPARATORS.length) % SEPARATORS.length] ?? d.separator;
|
|
136
|
+
setConfig({ ...config, defaults: { ...d, separator: next } });
|
|
137
|
+
} else {
|
|
138
|
+
const next = Math.min(4, Math.max(0, d.padding + dir));
|
|
139
|
+
if (next !== d.padding) {
|
|
140
|
+
setConfig({ ...config, defaults: { ...d, padding: next } });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
useInput((_i, key) => {
|
|
145
|
+
if (key.escape) {
|
|
146
|
+
goHome();
|
|
147
|
+
} else if (key.upArrow) {
|
|
148
|
+
setFocus((f) => Math.max(0, f - 1));
|
|
149
|
+
} else if (key.downArrow) {
|
|
150
|
+
setFocus((f) => Math.min(FIELDS.length - 1, f + 1));
|
|
151
|
+
} else if (key.leftArrow) {
|
|
152
|
+
cycle(-1);
|
|
153
|
+
} else if (key.rightArrow) {
|
|
154
|
+
cycle(1);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
const row = (idx, name, value) => /* @__PURE__ */ jsxs2(Text3, { color: focus === idx ? "cyan" : void 0, children: [
|
|
158
|
+
focus === idx ? "\u25B8 " : " ",
|
|
159
|
+
name,
|
|
160
|
+
": ",
|
|
161
|
+
value
|
|
162
|
+
] });
|
|
163
|
+
return /* @__PURE__ */ jsxs2(Box3, { flexDirection: "column", children: [
|
|
164
|
+
/* @__PURE__ */ jsx2(Text3, { bold: true, children: "Global defaults" }),
|
|
165
|
+
row(0, "separator", d.separator),
|
|
166
|
+
row(1, "padding", String(d.padding)),
|
|
167
|
+
/* @__PURE__ */ jsxs2(Text3, { color: "gray", children: [
|
|
168
|
+
" align: ",
|
|
169
|
+
d.align,
|
|
170
|
+
" (stored, not yet applied)"
|
|
171
|
+
] }),
|
|
172
|
+
/* @__PURE__ */ jsx2(Text3, { dimColor: true, children: "\u2191/\u2193 field \xB7 \u2190/\u2192 change \xB7 esc back" })
|
|
173
|
+
] });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/screens/import.tsx
|
|
177
|
+
import { readFileSync } from "fs";
|
|
178
|
+
import { homedir } from "os";
|
|
179
|
+
import { join } from "path";
|
|
180
|
+
import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
|
|
181
|
+
import { useMemo, useState as useState2 } from "react";
|
|
182
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
183
|
+
function defaultReadFile(path) {
|
|
184
|
+
try {
|
|
185
|
+
return readFileSync(path, "utf8");
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function detectCcstatuslinePath() {
|
|
191
|
+
const base = process.env["XDG_CONFIG_HOME"] || join(homedir(), ".config");
|
|
192
|
+
return join(base, "ccstatusline", "settings.json");
|
|
193
|
+
}
|
|
194
|
+
function load(path, readFile) {
|
|
195
|
+
if (!path.trim()) {
|
|
196
|
+
return { ok: false, message: "enter a path" };
|
|
197
|
+
}
|
|
198
|
+
let text;
|
|
199
|
+
try {
|
|
200
|
+
text = readFile(path);
|
|
201
|
+
} catch {
|
|
202
|
+
text = null;
|
|
203
|
+
}
|
|
204
|
+
if (text === null) {
|
|
205
|
+
return { ok: false, message: `cannot read ${path}` };
|
|
206
|
+
}
|
|
207
|
+
let parsed;
|
|
208
|
+
try {
|
|
209
|
+
parsed = JSON.parse(text);
|
|
210
|
+
} catch {
|
|
211
|
+
return { ok: false, message: `invalid JSON in ${path}` };
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const r = importCcstatusline(parsed);
|
|
215
|
+
if (r.warnings.some((w) => w.startsWith("input is not a ccstatusline config"))) {
|
|
216
|
+
return { ok: false, message: "not a ccstatusline config" };
|
|
217
|
+
}
|
|
218
|
+
return { ok: true, imported: r.config, warnings: r.warnings };
|
|
219
|
+
} catch {
|
|
220
|
+
return { ok: false, message: "import failed" };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function Import({
|
|
224
|
+
config,
|
|
225
|
+
setConfig,
|
|
226
|
+
goHome,
|
|
227
|
+
readFile = defaultReadFile
|
|
228
|
+
}) {
|
|
229
|
+
const detected = useMemo(detectCcstatuslinePath, []);
|
|
230
|
+
const [focus, setFocus] = useState2(0);
|
|
231
|
+
const [custom, setCustom] = useState2("");
|
|
232
|
+
const path = focus === 0 ? detected : custom;
|
|
233
|
+
const result = useMemo(() => load(path, readFile), [path, readFile]);
|
|
234
|
+
useInput2((input, key) => {
|
|
235
|
+
if (key.escape) {
|
|
236
|
+
goHome();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (key.upArrow) {
|
|
240
|
+
setFocus(0);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (key.downArrow) {
|
|
244
|
+
setFocus(1);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (key.return) {
|
|
248
|
+
if (result.ok) {
|
|
249
|
+
setConfig({
|
|
250
|
+
...config,
|
|
251
|
+
surfaces: { ...config.surfaces, band: result.imported.surfaces.band }
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (focus === 1) {
|
|
257
|
+
if (key.backspace || key.delete) {
|
|
258
|
+
setCustom((c) => c.slice(0, -1));
|
|
259
|
+
} else if (input && !key.ctrl && !key.meta) {
|
|
260
|
+
setCustom((c) => c + input);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
const dropped = result.ok ? result.warnings.length : 0;
|
|
265
|
+
const items = result.ok ? result.imported.surfaces.band.lines.map((l) => l.map((i) => i.type).join(", ")) : [];
|
|
266
|
+
const mapped = items.reduce((n, l) => n + (l ? l.split(", ").length : 0), 0);
|
|
267
|
+
return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
|
|
268
|
+
/* @__PURE__ */ jsx3(Text4, { bold: true, children: "Import from ccstatusline" }),
|
|
269
|
+
/* @__PURE__ */ jsxs3(Text4, { color: focus === 0 ? "cyan" : void 0, children: [
|
|
270
|
+
focus === 0 ? "\u25B8 " : " ",
|
|
271
|
+
"detected: ",
|
|
272
|
+
detected
|
|
273
|
+
] }),
|
|
274
|
+
/* @__PURE__ */ jsxs3(Text4, { color: focus === 1 ? "cyan" : void 0, children: [
|
|
275
|
+
focus === 1 ? "\u25B8 " : " ",
|
|
276
|
+
"custom path: ",
|
|
277
|
+
custom,
|
|
278
|
+
focus === 1 ? "\u258F" : ""
|
|
279
|
+
] }),
|
|
280
|
+
result.ok ? /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
|
|
281
|
+
/* @__PURE__ */ jsxs3(Text4, { children: [
|
|
282
|
+
"mapped ",
|
|
283
|
+
mapped,
|
|
284
|
+
" item(s), dropped ",
|
|
285
|
+
dropped
|
|
286
|
+
] }),
|
|
287
|
+
items.map((l, i) => /* @__PURE__ */ jsxs3(Text4, { children: [
|
|
288
|
+
" ",
|
|
289
|
+
"line ",
|
|
290
|
+
i + 1,
|
|
291
|
+
": ",
|
|
292
|
+
l
|
|
293
|
+
] }, i)),
|
|
294
|
+
result.warnings.map((w, i) => /* @__PURE__ */ jsxs3(Text4, { color: "yellow", children: [
|
|
295
|
+
" ",
|
|
296
|
+
"! ",
|
|
297
|
+
w
|
|
298
|
+
] }, i))
|
|
299
|
+
] }) : /* @__PURE__ */ jsxs3(Text4, { color: "yellow", children: [
|
|
300
|
+
"warning: ",
|
|
301
|
+
result.message
|
|
302
|
+
] }),
|
|
303
|
+
/* @__PURE__ */ jsx3(Text4, { dimColor: true, children: "\u2191/\u2193 source \xB7 \u23CE import (replaces band) \xB7 esc back" })
|
|
304
|
+
] });
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/screens/items.tsx
|
|
308
|
+
import { Box as Box5, Text as Text5, useInput as useInput3 } from "ink";
|
|
309
|
+
import { useState as useState3 } from "react";
|
|
310
|
+
|
|
311
|
+
// src/named-colors.ts
|
|
312
|
+
var all = [...COLORS];
|
|
313
|
+
var NAMED_COLORS = [
|
|
314
|
+
"(none)",
|
|
315
|
+
...all.filter((c) => !c.startsWith("bg")),
|
|
316
|
+
...all.filter((c) => c.startsWith("bg"))
|
|
317
|
+
];
|
|
318
|
+
|
|
319
|
+
// src/screens/items.tsx
|
|
320
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
321
|
+
var SURFACES = ["band", "statusline", "pane"];
|
|
322
|
+
var FIELDS2 = ["fg", "bg", "raw", "merge", "align"];
|
|
323
|
+
var ALIGNS = ["left", "center", "right"];
|
|
324
|
+
var FG = NAMED_COLORS.filter((c) => !c.startsWith("bg"));
|
|
325
|
+
var BG = ["(none)", ...NAMED_COLORS.filter((c) => c.startsWith("bg"))];
|
|
326
|
+
var WIDGETS = Object.values(registry);
|
|
327
|
+
var wrap = (i, n) => (i + n) % n;
|
|
328
|
+
var isStylable = (item) => item.type !== "flex-separator";
|
|
329
|
+
function Chip({ item, selected }) {
|
|
330
|
+
const fg = isStylable(item) ? item.fg : void 0;
|
|
331
|
+
const bg = isStylable(item) ? item.bg : void 0;
|
|
332
|
+
return /* @__PURE__ */ jsxs4(Text5, { inverse: selected, color: inkColor(fg), backgroundColor: inkBg(bg), children: [
|
|
333
|
+
"[ ",
|
|
334
|
+
item.type,
|
|
335
|
+
" ]"
|
|
336
|
+
] });
|
|
337
|
+
}
|
|
338
|
+
function Swatch({ name, bg }) {
|
|
339
|
+
if (!name) {
|
|
340
|
+
return /* @__PURE__ */ jsx4(Text5, { dimColor: true, children: "(none)" });
|
|
341
|
+
}
|
|
342
|
+
return /* @__PURE__ */ jsxs4(Text5, { color: bg ? void 0 : inkColor(name), backgroundColor: bg ? inkBg(name) : void 0, children: [
|
|
343
|
+
" ",
|
|
344
|
+
name,
|
|
345
|
+
" "
|
|
346
|
+
] });
|
|
347
|
+
}
|
|
348
|
+
function Items({ config, setConfig, goHome }) {
|
|
349
|
+
const [surface, setSurface] = useState3("band");
|
|
350
|
+
const [sel, setSel] = useState3(0);
|
|
351
|
+
const [mode, setMode] = useState3("strip");
|
|
352
|
+
const [field, setField] = useState3(0);
|
|
353
|
+
const [pick, setPick] = useState3(0);
|
|
354
|
+
const items = config.surfaces[surface].lines[0] ?? [];
|
|
355
|
+
const cur = items[Math.min(sel, items.length - 1)];
|
|
356
|
+
const idx = cur ? items.indexOf(cur) : -1;
|
|
357
|
+
const commit = (next) => {
|
|
358
|
+
const s = config.surfaces[surface];
|
|
359
|
+
const lines = [next, ...s.lines.slice(1)];
|
|
360
|
+
const c = { ...config, surfaces: { ...config.surfaces, [surface]: { ...s, lines } } };
|
|
361
|
+
setConfig(c);
|
|
362
|
+
};
|
|
363
|
+
const patch = (p) => {
|
|
364
|
+
const base = items[idx];
|
|
365
|
+
if (idx < 0 || !base || !isStylable(base)) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const item = { ...base, ...p };
|
|
369
|
+
for (const k of Object.keys(p)) {
|
|
370
|
+
if (item[k] === void 0) {
|
|
371
|
+
delete item[k];
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
commit(items.map((it, i) => i === idx ? item : it));
|
|
375
|
+
};
|
|
376
|
+
const cycleColor = (list, cur2, dir) => {
|
|
377
|
+
const n = list[wrap(Math.max(0, list.indexOf(cur2 ?? "(none)")) + dir + list.length, list.length)] ?? "(none)";
|
|
378
|
+
return n === "(none)" ? void 0 : n;
|
|
379
|
+
};
|
|
380
|
+
const change = (dir) => {
|
|
381
|
+
if (!cur || !isStylable(cur)) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const f = FIELDS2[field];
|
|
385
|
+
if (f === "fg") {
|
|
386
|
+
patch({ fg: cycleColor(FG, cur.fg, dir) });
|
|
387
|
+
} else if (f === "bg") {
|
|
388
|
+
patch({ bg: cycleColor(BG, cur.bg, dir) });
|
|
389
|
+
} else if (f === "raw") {
|
|
390
|
+
patch({ rawValue: !cur.rawValue });
|
|
391
|
+
} else if (f === "merge") {
|
|
392
|
+
patch({ merge: !cur.merge });
|
|
393
|
+
} else {
|
|
394
|
+
patch({ align: ALIGNS[wrap(ALIGNS.indexOf(cur.align ?? "left") + dir + 3, 3)] ?? "left" });
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
const move = (dir) => {
|
|
398
|
+
const j = idx + dir;
|
|
399
|
+
if (idx < 0 || j < 0 || j >= items.length) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
const next = [...items];
|
|
403
|
+
const a = next[idx];
|
|
404
|
+
const b = next[j];
|
|
405
|
+
if (a === void 0 || b === void 0) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
next[idx] = b;
|
|
409
|
+
next[j] = a;
|
|
410
|
+
commit(next);
|
|
411
|
+
setSel(j);
|
|
412
|
+
};
|
|
413
|
+
useInput3((input, key) => {
|
|
414
|
+
if (mode === "picker") {
|
|
415
|
+
if (key.escape) {
|
|
416
|
+
setMode("strip");
|
|
417
|
+
} else if (key.upArrow) {
|
|
418
|
+
setPick((p) => wrap(p - 1 + WIDGETS.length, WIDGETS.length));
|
|
419
|
+
} else if (key.downArrow) {
|
|
420
|
+
setPick((p) => wrap(p + 1, WIDGETS.length));
|
|
421
|
+
} else if (key.return) {
|
|
422
|
+
const picked = WIDGETS[pick];
|
|
423
|
+
if (picked) {
|
|
424
|
+
const type = picked.type;
|
|
425
|
+
const id = `${type}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
426
|
+
const newItem = type === "custom-text" ? { id, type, text: "" } : type === "custom-command" ? { id, type, command: "" } : type === "flex-separator" ? { id, type } : { id, type };
|
|
427
|
+
commit([...items, newItem]);
|
|
428
|
+
setSel(items.length);
|
|
429
|
+
setMode("strip");
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
} else if (mode === "editor") {
|
|
433
|
+
if (key.escape) {
|
|
434
|
+
setMode("strip");
|
|
435
|
+
} else if (key.upArrow) {
|
|
436
|
+
setField((f) => Math.max(0, f - 1));
|
|
437
|
+
} else if (key.downArrow) {
|
|
438
|
+
setField((f) => Math.min(FIELDS2.length - 1, f + 1));
|
|
439
|
+
} else if (key.leftArrow) {
|
|
440
|
+
change(-1);
|
|
441
|
+
} else if (key.rightArrow) {
|
|
442
|
+
change(1);
|
|
443
|
+
}
|
|
444
|
+
} else {
|
|
445
|
+
if (key.escape) {
|
|
446
|
+
goHome();
|
|
447
|
+
} else if (key.tab) {
|
|
448
|
+
setSurface((s) => SURFACES[wrap(SURFACES.indexOf(s) + 1, SURFACES.length)] ?? s);
|
|
449
|
+
setSel(0);
|
|
450
|
+
} else if (key.leftArrow) {
|
|
451
|
+
setSel(Math.max(0, idx - 1));
|
|
452
|
+
} else if (key.rightArrow) {
|
|
453
|
+
setSel(Math.min(items.length - 1, idx + 1));
|
|
454
|
+
} else if (input === "[") {
|
|
455
|
+
move(-1);
|
|
456
|
+
} else if (input === "]") {
|
|
457
|
+
move(1);
|
|
458
|
+
} else if (input === "a") {
|
|
459
|
+
setPick(0);
|
|
460
|
+
setMode("picker");
|
|
461
|
+
} else if (input === "x" && idx >= 0) {
|
|
462
|
+
commit(items.filter((_, i) => i !== idx));
|
|
463
|
+
setSel(Math.max(0, idx - 1));
|
|
464
|
+
} else if (input === "e" && idx >= 0) {
|
|
465
|
+
setField(0);
|
|
466
|
+
setMode("editor");
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
const row = (i, name, value) => /* @__PURE__ */ jsxs4(Box5, { children: [
|
|
471
|
+
/* @__PURE__ */ jsxs4(Text5, { color: mode === "editor" && field === i ? "cyan" : void 0, children: [
|
|
472
|
+
mode === "editor" && field === i ? "\u25B8 " : " ",
|
|
473
|
+
name,
|
|
474
|
+
":",
|
|
475
|
+
" "
|
|
476
|
+
] }),
|
|
477
|
+
value
|
|
478
|
+
] }, name);
|
|
479
|
+
return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
|
|
480
|
+
/* @__PURE__ */ jsx4(Box5, { children: SURFACES.map((s) => /* @__PURE__ */ jsxs4(Text5, { bold: s === surface, color: s === surface ? "cyan" : void 0, children: [
|
|
481
|
+
" ",
|
|
482
|
+
s === surface ? `[${s}]` : s,
|
|
483
|
+
" "
|
|
484
|
+
] }, s)) }),
|
|
485
|
+
/* @__PURE__ */ jsx4(Box5, { children: items.length === 0 ? /* @__PURE__ */ jsx4(Text5, { dimColor: true, children: "(no items \u2014 press a)" }) : items.map((it, i) => /* @__PURE__ */ jsx4(Chip, { item: it, selected: i === idx }, it.id)) }),
|
|
486
|
+
mode === "picker" ? /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", marginTop: 1, children: [
|
|
487
|
+
/* @__PURE__ */ jsx4(Text5, { bold: true, children: "Add widget" }),
|
|
488
|
+
WIDGETS.map((w, i) => /* @__PURE__ */ jsxs4(Text5, { color: i === pick ? "cyan" : void 0, children: [
|
|
489
|
+
i === pick ? "\u25B8 " : " ",
|
|
490
|
+
w.type,
|
|
491
|
+
" ",
|
|
492
|
+
/* @__PURE__ */ jsx4(Text5, { dimColor: true, children: w.label })
|
|
493
|
+
] }, w.type))
|
|
494
|
+
] }) : cur && (isStylable(cur) ? /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", marginTop: 1, borderStyle: "round", paddingX: 1, children: [
|
|
495
|
+
/* @__PURE__ */ jsxs4(Text5, { children: [
|
|
496
|
+
"type: ",
|
|
497
|
+
cur.type
|
|
498
|
+
] }),
|
|
499
|
+
row(0, "fg", /* @__PURE__ */ jsx4(Swatch, { name: cur.fg })),
|
|
500
|
+
row(1, "bg", /* @__PURE__ */ jsx4(Swatch, { name: cur.bg, bg: true })),
|
|
501
|
+
row(2, "raw", /* @__PURE__ */ jsx4(Text5, { children: cur.rawValue ? "on" : "off" })),
|
|
502
|
+
row(3, "merge", /* @__PURE__ */ jsx4(Text5, { children: cur.merge ? "on" : "off" })),
|
|
503
|
+
row(4, "align", /* @__PURE__ */ jsx4(Text5, { children: cur.align ?? "left" }))
|
|
504
|
+
] }) : /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", marginTop: 1, borderStyle: "round", paddingX: 1, children: [
|
|
505
|
+
/* @__PURE__ */ jsxs4(Text5, { children: [
|
|
506
|
+
"type: ",
|
|
507
|
+
cur.type
|
|
508
|
+
] }),
|
|
509
|
+
/* @__PURE__ */ jsx4(Text5, { dimColor: true, children: "(flex separator \u2014 no style options)" })
|
|
510
|
+
] })),
|
|
511
|
+
/* @__PURE__ */ jsx4(Text5, { dimColor: true, children: mode === "strip" ? "\u2190/\u2192 select \xB7 [/] move \xB7 a add \xB7 x remove \xB7 e edit \xB7 tab surface \xB7 esc back" : mode === "editor" ? "\u2191/\u2193 field \xB7 \u2190/\u2192 change \xB7 esc done" : "\u2191/\u2193 choose \xB7 enter add \xB7 esc cancel" })
|
|
512
|
+
] });
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// src/screens/menu.tsx
|
|
516
|
+
import { Box as Box6, Text as Text6, useInput as useInput4 } from "ink";
|
|
517
|
+
import { useState as useState4 } from "react";
|
|
518
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
519
|
+
var ITEMS = [
|
|
520
|
+
["items", "Edit items"],
|
|
521
|
+
["themes", "Themes"],
|
|
522
|
+
["powerline", "Powerline & separators"],
|
|
523
|
+
["surfaces", "Surfaces"],
|
|
524
|
+
["statusline", "Native status line"],
|
|
525
|
+
["defaults", "Global defaults"],
|
|
526
|
+
["preview", "Preview"],
|
|
527
|
+
["import", "Import from ccstatusline"],
|
|
528
|
+
["save", "Save & quit"],
|
|
529
|
+
["quit", "Quit without saving"]
|
|
530
|
+
];
|
|
531
|
+
function Menu({ onSelect }) {
|
|
532
|
+
const [i, setI] = useState4(0);
|
|
533
|
+
useInput4((input, key) => {
|
|
534
|
+
if (key.upArrow) {
|
|
535
|
+
setI((v) => Math.max(0, v - 1));
|
|
536
|
+
} else if (key.downArrow) {
|
|
537
|
+
setI((v) => Math.min(ITEMS.length - 1, v + 1));
|
|
538
|
+
} else if (key.return) {
|
|
539
|
+
const entry = ITEMS[i];
|
|
540
|
+
if (entry) {
|
|
541
|
+
onSelect(entry[0]);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
|
|
546
|
+
/* @__PURE__ */ jsx5(Text6, { bold: true, children: "ccstatus \xB7 configurator" }),
|
|
547
|
+
ITEMS.map(([id, label], idx) => /* @__PURE__ */ jsxs5(Text6, { color: idx === i ? "cyan" : void 0, children: [
|
|
548
|
+
idx === i ? "\u25B8 " : " ",
|
|
549
|
+
label
|
|
550
|
+
] }, id))
|
|
551
|
+
] });
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// src/screens/powerline.tsx
|
|
555
|
+
import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
|
|
556
|
+
import { useState as useState5 } from "react";
|
|
557
|
+
|
|
558
|
+
// src/separators.ts
|
|
559
|
+
var SEPARATOR_PRESETS = [
|
|
560
|
+
{ char: "\uE0B0", name: "Triangle Right", code: "U+E0B0" },
|
|
561
|
+
{ char: "\uE0B2", name: "Triangle Left", code: "U+E0B2" },
|
|
562
|
+
{ char: "\uE0B4", name: "Round Right", code: "U+E0B4" },
|
|
563
|
+
{ char: "\uE0B6", name: "Round Left", code: "U+E0B6" },
|
|
564
|
+
{ char: "\uE0BA", name: "Lower Triangle", code: "U+E0BA" },
|
|
565
|
+
{ char: "\uE0BE", name: "Diagonal", code: "U+E0BE" }
|
|
566
|
+
];
|
|
567
|
+
|
|
568
|
+
// src/screens/powerline.tsx
|
|
569
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
570
|
+
var MODES = ["powerline", "space", "none"];
|
|
571
|
+
var CUSTOM = SEPARATOR_PRESETS.length;
|
|
572
|
+
var SEP_ROW = 0;
|
|
573
|
+
var GLYPH_ROW = (i) => 1 + i;
|
|
574
|
+
var INVERT_ROW = 1 + SEPARATOR_PRESETS.length + 1;
|
|
575
|
+
var LAST_ROW = INVERT_ROW;
|
|
576
|
+
var codeOf = (g) => {
|
|
577
|
+
const cp = g.codePointAt(0);
|
|
578
|
+
return cp === void 0 ? "" : "U+" + cp.toString(16).toUpperCase().padStart(4, "0");
|
|
579
|
+
};
|
|
580
|
+
function Powerline({ config, setConfig, goHome }) {
|
|
581
|
+
const [focus, setFocus] = useState5(0);
|
|
582
|
+
const [editing, setEditing] = useState5(false);
|
|
583
|
+
const [draft, setDraft] = useState5("");
|
|
584
|
+
const d = config.defaults;
|
|
585
|
+
const update = (patch) => setConfig({ ...config, defaults: { ...d, ...patch } });
|
|
586
|
+
const isPreset = SEPARATOR_PRESETS.some((p) => p.char === d.glyph);
|
|
587
|
+
const hasCustom = d.glyph !== "" && !isPreset;
|
|
588
|
+
const activate = () => {
|
|
589
|
+
if (focus === INVERT_ROW) {
|
|
590
|
+
update({ invert: !d.invert });
|
|
591
|
+
} else if (focus > SEP_ROW) {
|
|
592
|
+
const i = focus - 1;
|
|
593
|
+
if (i === CUSTOM) {
|
|
594
|
+
setDraft("");
|
|
595
|
+
setEditing(true);
|
|
596
|
+
} else {
|
|
597
|
+
const preset = SEPARATOR_PRESETS[i];
|
|
598
|
+
if (preset) {
|
|
599
|
+
update({ glyph: preset.char });
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
};
|
|
604
|
+
useInput5((input, key) => {
|
|
605
|
+
if (editing) {
|
|
606
|
+
if (key.escape) {
|
|
607
|
+
setEditing(false);
|
|
608
|
+
} else if (key.return) {
|
|
609
|
+
if (draft) {
|
|
610
|
+
update({ glyph: draft });
|
|
611
|
+
}
|
|
612
|
+
setEditing(false);
|
|
613
|
+
} else if (key.backspace || key.delete) {
|
|
614
|
+
setDraft((t) => Array.from(t).slice(0, -1).join(""));
|
|
615
|
+
} else if (input && !key.ctrl && !key.meta) {
|
|
616
|
+
setDraft((t) => t + input);
|
|
617
|
+
}
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
if (key.escape) {
|
|
621
|
+
goHome();
|
|
622
|
+
} else if (key.upArrow) {
|
|
623
|
+
setFocus((f) => Math.max(0, f - 1));
|
|
624
|
+
} else if (key.downArrow) {
|
|
625
|
+
setFocus((f) => Math.min(LAST_ROW, f + 1));
|
|
626
|
+
} else if ((key.leftArrow || key.rightArrow) && focus === SEP_ROW) {
|
|
627
|
+
const dir = key.rightArrow ? 1 : -1;
|
|
628
|
+
update({
|
|
629
|
+
separator: MODES[(MODES.indexOf(d.separator) + dir + MODES.length) % MODES.length] ?? d.separator
|
|
630
|
+
});
|
|
631
|
+
} else if (key.return || input === " ") {
|
|
632
|
+
activate();
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
const cursor = (idx) => focus === idx ? "\u25B8 " : " ";
|
|
636
|
+
const color = (idx) => focus === idx ? "cyan" : void 0;
|
|
637
|
+
return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
|
|
638
|
+
/* @__PURE__ */ jsx6(Text7, { bold: true, children: "Powerline" }),
|
|
639
|
+
/* @__PURE__ */ jsxs6(Text7, { color: color(SEP_ROW), children: [
|
|
640
|
+
cursor(SEP_ROW),
|
|
641
|
+
"separator: ",
|
|
642
|
+
d.separator
|
|
643
|
+
] }),
|
|
644
|
+
/* @__PURE__ */ jsx6(Text7, { dimColor: true, children: " glyph" }),
|
|
645
|
+
SEPARATOR_PRESETS.map((p, i) => /* @__PURE__ */ jsxs6(Text7, { color: color(GLYPH_ROW(i)), children: [
|
|
646
|
+
cursor(GLYPH_ROW(i)),
|
|
647
|
+
d.glyph === p.char ? "(\u2022)" : "( )",
|
|
648
|
+
" ",
|
|
649
|
+
p.name,
|
|
650
|
+
" ",
|
|
651
|
+
p.code,
|
|
652
|
+
" ",
|
|
653
|
+
p.char
|
|
654
|
+
] }, p.code)),
|
|
655
|
+
/* @__PURE__ */ jsxs6(Text7, { color: color(GLYPH_ROW(CUSTOM)), children: [
|
|
656
|
+
cursor(GLYPH_ROW(CUSTOM)),
|
|
657
|
+
hasCustom ? "(\u2022)" : "( )",
|
|
658
|
+
" ",
|
|
659
|
+
editing ? `Custom: ${draft}_ (enter to set, esc to cancel)` : hasCustom ? `Custom (${codeOf(d.glyph)}) ${d.glyph}` : "Custom\u2026"
|
|
660
|
+
] }),
|
|
661
|
+
/* @__PURE__ */ jsxs6(Text7, { color: color(INVERT_ROW), children: [
|
|
662
|
+
cursor(INVERT_ROW),
|
|
663
|
+
"invert background: ",
|
|
664
|
+
d.invert ? "on" : "off",
|
|
665
|
+
/* @__PURE__ */ jsx6(Text7, { color: "gray", children: " (stored, not applied)" })
|
|
666
|
+
] }),
|
|
667
|
+
/* @__PURE__ */ jsx6(Text7, { dimColor: true, children: "\u2191/\u2193 move \xB7 \u2190/\u2192 separator \xB7 enter select \xB7 esc back" })
|
|
668
|
+
] });
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// src/screens/preview-screen.tsx
|
|
672
|
+
import { Box as Box8, Text as Text8, useInput as useInput6 } from "ink";
|
|
673
|
+
import { useState as useState6 } from "react";
|
|
674
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
675
|
+
var SURFACES2 = ["band", "statusline", "pane"];
|
|
676
|
+
function PreviewScreen({
|
|
677
|
+
config,
|
|
678
|
+
snapshot,
|
|
679
|
+
source,
|
|
680
|
+
goHome
|
|
681
|
+
}) {
|
|
682
|
+
const termWidth = process.stdout.columns || 120;
|
|
683
|
+
const widths = [80, 120, 160, termWidth];
|
|
684
|
+
const [wi, setWi] = useState6(0);
|
|
685
|
+
const [si, setSi] = useState6(0);
|
|
686
|
+
useInput6((input, key) => {
|
|
687
|
+
if (key.escape) {
|
|
688
|
+
goHome();
|
|
689
|
+
} else if (input === "w") {
|
|
690
|
+
setWi((v) => (v + 1) % widths.length);
|
|
691
|
+
} else if (input === "s") {
|
|
692
|
+
setSi((v) => (v + 1) % SURFACES2.length);
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
const width = widths[wi] ?? termWidth;
|
|
696
|
+
const surface = SURFACES2[si] ?? "band";
|
|
697
|
+
return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
|
|
698
|
+
/* @__PURE__ */ jsx7(Text8, { bold: true, children: "Preview" }),
|
|
699
|
+
/* @__PURE__ */ jsxs7(Text8, { color: "gray", children: [
|
|
700
|
+
"w width (",
|
|
701
|
+
width,
|
|
702
|
+
wi === widths.length - 1 ? " = terminal" : "",
|
|
703
|
+
") \xB7 s surface (",
|
|
704
|
+
surface,
|
|
705
|
+
") \xB7 esc back"
|
|
706
|
+
] }),
|
|
707
|
+
/* @__PURE__ */ jsx7(
|
|
708
|
+
Preview,
|
|
709
|
+
{
|
|
710
|
+
config,
|
|
711
|
+
snapshot,
|
|
712
|
+
source,
|
|
713
|
+
width,
|
|
714
|
+
surface
|
|
715
|
+
}
|
|
716
|
+
)
|
|
717
|
+
] });
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// src/screens/statusline.tsx
|
|
721
|
+
import { Box as Box9, Text as Text9, useInput as useInput7 } from "ink";
|
|
722
|
+
import { useState as useState7 } from "react";
|
|
723
|
+
|
|
724
|
+
// src/statusline-setup.ts
|
|
725
|
+
import {
|
|
726
|
+
copyFileSync,
|
|
727
|
+
existsSync,
|
|
728
|
+
mkdirSync,
|
|
729
|
+
readFileSync as readFileSync2,
|
|
730
|
+
renameSync,
|
|
731
|
+
writeFileSync
|
|
732
|
+
} from "fs";
|
|
733
|
+
import { dirname } from "path";
|
|
734
|
+
var STATUSLINE_COMMAND = "npx @sushant-kum/ccstatus statusline";
|
|
735
|
+
function settingsPath(scope, env = process.env) {
|
|
736
|
+
if (scope === "project") {
|
|
737
|
+
return `${process.cwd()}/.claude/settings.json`;
|
|
738
|
+
}
|
|
739
|
+
return `${env["HOME"] ?? ""}/.claude/settings.json`;
|
|
740
|
+
}
|
|
741
|
+
var nodeIo = {
|
|
742
|
+
// `read` returns null only for an absent file; a real read error (EACCES, a
|
|
743
|
+
// directory, transient I/O) propagates so callers refuse rather than treat an
|
|
744
|
+
// unreadable settings.json as empty and overwrite it (cf. config-store.ts).
|
|
745
|
+
read: (path) => existsSync(path) ? readFileSync2(path, "utf8") : null,
|
|
746
|
+
write: (path, data) => {
|
|
747
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
748
|
+
if (existsSync(path)) {
|
|
749
|
+
try {
|
|
750
|
+
copyFileSync(path, path + ".bak");
|
|
751
|
+
} catch {
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
const tmp = path + ".tmp";
|
|
755
|
+
writeFileSync(tmp, data);
|
|
756
|
+
renameSync(tmp, path);
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
var isOurs = (sl) => {
|
|
760
|
+
if (!sl || typeof sl !== "object") {
|
|
761
|
+
return false;
|
|
762
|
+
}
|
|
763
|
+
const c = sl.command;
|
|
764
|
+
return typeof c === "string" && (c === STATUSLINE_COMMAND || /(^|\/)ccstatus statusline$/.test(c));
|
|
765
|
+
};
|
|
766
|
+
function detectStatusline(scope, io = nodeIo, env = process.env) {
|
|
767
|
+
let text;
|
|
768
|
+
try {
|
|
769
|
+
text = io.read(settingsPath(scope, env));
|
|
770
|
+
} catch {
|
|
771
|
+
return { state: "unknown" };
|
|
772
|
+
}
|
|
773
|
+
if (text === null) {
|
|
774
|
+
return { state: "absent" };
|
|
775
|
+
}
|
|
776
|
+
let parsed;
|
|
777
|
+
try {
|
|
778
|
+
parsed = JSON.parse(text);
|
|
779
|
+
} catch {
|
|
780
|
+
return { state: "unknown" };
|
|
781
|
+
}
|
|
782
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
783
|
+
return { state: "unknown" };
|
|
784
|
+
}
|
|
785
|
+
const sl = parsed.statusLine;
|
|
786
|
+
if (sl === void 0) {
|
|
787
|
+
return { state: "absent" };
|
|
788
|
+
}
|
|
789
|
+
if (isOurs(sl)) {
|
|
790
|
+
return { state: "ours" };
|
|
791
|
+
}
|
|
792
|
+
const command = sl?.command;
|
|
793
|
+
if (typeof command !== "string") {
|
|
794
|
+
return { state: "unknown" };
|
|
795
|
+
}
|
|
796
|
+
return { state: "other", command };
|
|
797
|
+
}
|
|
798
|
+
function enableStatusline(scope, opts = {}, io = nodeIo, env = process.env) {
|
|
799
|
+
const path = settingsPath(scope, env);
|
|
800
|
+
let text;
|
|
801
|
+
try {
|
|
802
|
+
text = io.read(path);
|
|
803
|
+
} catch {
|
|
804
|
+
return { ok: false, message: "settings.json exists but could not be read; left unchanged" };
|
|
805
|
+
}
|
|
806
|
+
let settings = {};
|
|
807
|
+
if (text !== null) {
|
|
808
|
+
try {
|
|
809
|
+
const parsed = JSON.parse(text);
|
|
810
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
811
|
+
return { ok: false, message: "settings.json is not a JSON object; left unchanged" };
|
|
812
|
+
}
|
|
813
|
+
settings = parsed;
|
|
814
|
+
} catch {
|
|
815
|
+
return { ok: false, message: "settings.json is not valid JSON; left unchanged" };
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
const existing = settings["statusLine"];
|
|
819
|
+
if (existing !== void 0 && !isOurs(existing) && !opts.replaceExisting) {
|
|
820
|
+
return { ok: false, message: "a different statusLine is configured" };
|
|
821
|
+
}
|
|
822
|
+
settings["statusLine"] = { type: "command", command: STATUSLINE_COMMAND, padding: 0 };
|
|
823
|
+
io.write(path, JSON.stringify(settings, null, 2) + "\n");
|
|
824
|
+
return { ok: true, message: `status line enabled in ${path}` };
|
|
825
|
+
}
|
|
826
|
+
function disableStatusline(scope, io = nodeIo, env = process.env) {
|
|
827
|
+
const path = settingsPath(scope, env);
|
|
828
|
+
let text;
|
|
829
|
+
try {
|
|
830
|
+
text = io.read(path);
|
|
831
|
+
} catch {
|
|
832
|
+
return { ok: false, message: "settings.json exists but could not be read; left unchanged" };
|
|
833
|
+
}
|
|
834
|
+
if (text === null) {
|
|
835
|
+
return { ok: true, message: "no settings.json; nothing to disable" };
|
|
836
|
+
}
|
|
837
|
+
let settings;
|
|
838
|
+
try {
|
|
839
|
+
const parsed = JSON.parse(text);
|
|
840
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
841
|
+
return { ok: false, message: "settings.json is not a JSON object; left unchanged" };
|
|
842
|
+
}
|
|
843
|
+
settings = parsed;
|
|
844
|
+
} catch {
|
|
845
|
+
return { ok: false, message: "settings.json is not valid JSON; left unchanged" };
|
|
846
|
+
}
|
|
847
|
+
if (!isOurs(settings["statusLine"])) {
|
|
848
|
+
return { ok: false, message: "the configured statusLine is not ccstatus; left unchanged" };
|
|
849
|
+
}
|
|
850
|
+
delete settings["statusLine"];
|
|
851
|
+
io.write(path, JSON.stringify(settings, null, 2) + "\n");
|
|
852
|
+
return { ok: true, message: `status line disabled in ${path}` };
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// src/screens/statusline.tsx
|
|
856
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
857
|
+
function Statusline({
|
|
858
|
+
goHome,
|
|
859
|
+
detect = (s) => detectStatusline(s),
|
|
860
|
+
enable = (s, o) => enableStatusline(s, o),
|
|
861
|
+
disable = (s) => disableStatusline(s)
|
|
862
|
+
}) {
|
|
863
|
+
const [scope, setScope] = useState7("user");
|
|
864
|
+
const [info, setInfo] = useState7(() => detect("user"));
|
|
865
|
+
const [msg, setMsg] = useState7(null);
|
|
866
|
+
const [pendingScope, setPendingScope] = useState7(null);
|
|
867
|
+
const doEnable = (s, replaceExisting) => {
|
|
868
|
+
setScope(s);
|
|
869
|
+
const current = detect(s);
|
|
870
|
+
if (current.state === "other" && !replaceExisting) {
|
|
871
|
+
setPendingScope(s);
|
|
872
|
+
setMsg(`A different statusLine is set (${current.command}). Press r to replace it.`);
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
const r = enable(s, { replaceExisting });
|
|
876
|
+
setMsg(r.message);
|
|
877
|
+
setPendingScope(null);
|
|
878
|
+
setInfo(detect(s));
|
|
879
|
+
};
|
|
880
|
+
useInput7((input, key) => {
|
|
881
|
+
if (key.escape) {
|
|
882
|
+
goHome();
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
if (input === "u") {
|
|
886
|
+
doEnable("user", false);
|
|
887
|
+
} else if (input === "p") {
|
|
888
|
+
doEnable("project", false);
|
|
889
|
+
} else if (input === "r" && pendingScope) {
|
|
890
|
+
doEnable(pendingScope, true);
|
|
891
|
+
} else if (input === "d") {
|
|
892
|
+
setPendingScope(null);
|
|
893
|
+
const r = disable(scope);
|
|
894
|
+
setMsg(r.message);
|
|
895
|
+
setInfo(detect(scope));
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
const state = info.state === "ours" ? "configured (ccstatus)" : info.state === "other" ? `another command: ${info.command}` : info.state === "unknown" ? "settings.json unreadable" : "not configured";
|
|
899
|
+
return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", children: [
|
|
900
|
+
/* @__PURE__ */ jsx8(Text9, { bold: true, children: "Native status line" }),
|
|
901
|
+
/* @__PURE__ */ jsxs8(Text9, { children: [
|
|
902
|
+
"Current: ",
|
|
903
|
+
state
|
|
904
|
+
] }),
|
|
905
|
+
/* @__PURE__ */ jsxs8(Text9, { dimColor: true, children: [
|
|
906
|
+
"u: enable (user) \xB7 p: enable (project) \xB7 d: disable (",
|
|
907
|
+
scope,
|
|
908
|
+
") \xB7 esc: back"
|
|
909
|
+
] }),
|
|
910
|
+
pendingScope && /* @__PURE__ */ jsx8(Text9, { color: "yellow", children: "r: confirm replace" }),
|
|
911
|
+
msg && /* @__PURE__ */ jsx8(Text9, { color: "cyan", children: msg })
|
|
912
|
+
] });
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// src/screens/surfaces.tsx
|
|
916
|
+
import { Box as Box10, Text as Text10, useInput as useInput8 } from "ink";
|
|
917
|
+
import { useState as useState8 } from "react";
|
|
918
|
+
|
|
919
|
+
// src/toast-presets.ts
|
|
920
|
+
var TOAST_PRESETS = [
|
|
921
|
+
{ label: "Context high", when: "ctxPct>80", text: "Context over 80%" },
|
|
922
|
+
{ label: "Context critical", when: "ctxPct>95", text: "Context over 95%" },
|
|
923
|
+
{ label: "Session limit near", when: "fivePct>90", text: "Session limit near" },
|
|
924
|
+
{ label: "Weekly limit near", when: "weekPct>80", text: "Weekly limit near" }
|
|
925
|
+
];
|
|
926
|
+
|
|
927
|
+
// src/screens/surfaces.tsx
|
|
928
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
929
|
+
var SURFACES3 = ["band", "statusline", "pane", "toasts"];
|
|
930
|
+
function Surfaces({ config, setConfig, goHome }) {
|
|
931
|
+
const [focus, setFocus] = useState8(0);
|
|
932
|
+
const [mode, setMode] = useState8({ kind: "nav" });
|
|
933
|
+
const s = config.surfaces;
|
|
934
|
+
const rules = s.toasts.rules;
|
|
935
|
+
const total = SURFACES3.length + rules.length;
|
|
936
|
+
const setRules = (next) => setConfig({ ...config, surfaces: { ...s, toasts: { ...s.toasts, rules: next } } });
|
|
937
|
+
useInput8((input, key) => {
|
|
938
|
+
if (mode.kind === "preset") {
|
|
939
|
+
const n = TOAST_PRESETS.length + 1;
|
|
940
|
+
if (key.escape) {
|
|
941
|
+
setMode({ kind: "nav" });
|
|
942
|
+
} else if (key.upArrow) {
|
|
943
|
+
setMode({ kind: "preset", idx: (mode.idx + n - 1) % n });
|
|
944
|
+
} else if (key.downArrow) {
|
|
945
|
+
setMode({ kind: "preset", idx: (mode.idx + 1) % n });
|
|
946
|
+
} else if (key.return) {
|
|
947
|
+
const p = TOAST_PRESETS[mode.idx];
|
|
948
|
+
if (p) {
|
|
949
|
+
setRules([...rules, { when: p.when, text: p.text, once: true }]);
|
|
950
|
+
setFocus(SURFACES3.length + rules.length);
|
|
951
|
+
setMode({ kind: "nav" });
|
|
952
|
+
} else {
|
|
953
|
+
setMode({ kind: "edit", target: "new", ruleIdx: -1, buf: "" });
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
if (mode.kind === "edit") {
|
|
959
|
+
if (key.escape) {
|
|
960
|
+
setMode({ kind: "nav" });
|
|
961
|
+
} else if (key.return) {
|
|
962
|
+
if (mode.target === "new") {
|
|
963
|
+
if (!isValidWhen(mode.buf)) {
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
setRules([...rules, { when: mode.buf.trim(), text: "Custom alert", once: true }]);
|
|
967
|
+
setFocus(SURFACES3.length + rules.length);
|
|
968
|
+
} else {
|
|
969
|
+
setRules(
|
|
970
|
+
rules.map((r, i) => i === mode.ruleIdx ? { ...r, [mode.target]: mode.buf } : r)
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
setMode({ kind: "nav" });
|
|
974
|
+
} else if (key.backspace || key.delete) {
|
|
975
|
+
setMode({ ...mode, buf: mode.buf.slice(0, -1) });
|
|
976
|
+
} else if (input && !key.ctrl && !key.meta && !key.upArrow && !key.downArrow && !key.leftArrow && !key.rightArrow && !key.tab) {
|
|
977
|
+
setMode({ ...mode, buf: mode.buf + input });
|
|
978
|
+
}
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
const ruleIdx = focus - SURFACES3.length;
|
|
982
|
+
if (key.escape) {
|
|
983
|
+
goHome();
|
|
984
|
+
} else if (key.upArrow) {
|
|
985
|
+
setFocus((f) => Math.max(0, f - 1));
|
|
986
|
+
} else if (key.downArrow) {
|
|
987
|
+
setFocus((f) => Math.min(total - 1, f + 1));
|
|
988
|
+
} else if (input === "a") {
|
|
989
|
+
setMode({ kind: "preset", idx: 0 });
|
|
990
|
+
} else if (input === " ") {
|
|
991
|
+
if (ruleIdx < 0) {
|
|
992
|
+
const name = SURFACES3[focus];
|
|
993
|
+
if (name) {
|
|
994
|
+
setConfig({
|
|
995
|
+
...config,
|
|
996
|
+
surfaces: { ...s, [name]: { ...s[name], enabled: !s[name].enabled } }
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
} else if (rules[ruleIdx]) {
|
|
1000
|
+
setRules(rules.map((r, i) => i === ruleIdx ? { ...r, once: !r.once } : r));
|
|
1001
|
+
}
|
|
1002
|
+
} else if (ruleIdx >= 0) {
|
|
1003
|
+
const r = rules[ruleIdx];
|
|
1004
|
+
if (r && input === "w") {
|
|
1005
|
+
setMode({ kind: "edit", target: "when", ruleIdx, buf: r.when });
|
|
1006
|
+
} else if (r && input === "t") {
|
|
1007
|
+
setMode({ kind: "edit", target: "text", ruleIdx, buf: r.text });
|
|
1008
|
+
} else if (r && input === "x") {
|
|
1009
|
+
setRules(rules.filter((_, i) => i !== ruleIdx));
|
|
1010
|
+
setFocus((f) => Math.max(0, f - 1));
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
const cur = (i) => focus === i ? "\u25B8 " : " ";
|
|
1015
|
+
return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
|
|
1016
|
+
/* @__PURE__ */ jsx9(Text10, { bold: true, children: "Surfaces" }),
|
|
1017
|
+
SURFACES3.map((name, i) => /* @__PURE__ */ jsxs9(Text10, { color: focus === i ? "cyan" : void 0, children: [
|
|
1018
|
+
cur(i),
|
|
1019
|
+
"[",
|
|
1020
|
+
s[name].enabled ? "x" : " ",
|
|
1021
|
+
"] ",
|
|
1022
|
+
name
|
|
1023
|
+
] }, name)),
|
|
1024
|
+
/* @__PURE__ */ jsx9(Text10, { bold: true, children: "Toast rules" }),
|
|
1025
|
+
rules.length === 0 && /* @__PURE__ */ jsx9(Text10, { dimColor: true, children: " (none \u2014 press a to add)" }),
|
|
1026
|
+
rules.map((r, i) => {
|
|
1027
|
+
const idx = SURFACES3.length + i;
|
|
1028
|
+
const ok = isValidWhen(r.when);
|
|
1029
|
+
const editing = mode.kind === "edit" && mode.ruleIdx === i;
|
|
1030
|
+
return /* @__PURE__ */ jsxs9(Box10, { children: [
|
|
1031
|
+
/* @__PURE__ */ jsx9(Text10, { color: focus === idx ? "cyan" : void 0, children: cur(idx) }),
|
|
1032
|
+
/* @__PURE__ */ jsxs9(Text10, { color: ok ? void 0 : "red", children: [
|
|
1033
|
+
"when: ",
|
|
1034
|
+
editing && mode.target === "when" ? mode.buf + "_" : r.when,
|
|
1035
|
+
ok ? "" : " \u26A0 invalid"
|
|
1036
|
+
] }),
|
|
1037
|
+
/* @__PURE__ */ jsxs9(Text10, { color: focus === idx ? "cyan" : void 0, children: [
|
|
1038
|
+
" ",
|
|
1039
|
+
"text: ",
|
|
1040
|
+
editing && mode.target === "text" ? mode.buf + "_" : `"${r.text}"`,
|
|
1041
|
+
" [",
|
|
1042
|
+
r.once ? "x" : " ",
|
|
1043
|
+
"] once"
|
|
1044
|
+
] })
|
|
1045
|
+
] }, i);
|
|
1046
|
+
}),
|
|
1047
|
+
mode.kind === "preset" && /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
|
|
1048
|
+
/* @__PURE__ */ jsx9(Text10, { bold: true, children: "Add rule \u2014 pick a preset" }),
|
|
1049
|
+
[...TOAST_PRESETS.map((p) => `${p.label} (${p.when})`), "Custom\u2026"].map((l, i) => /* @__PURE__ */ jsxs9(Text10, { color: mode.idx === i ? "cyan" : void 0, children: [
|
|
1050
|
+
mode.idx === i ? "\u25B8 " : " ",
|
|
1051
|
+
l
|
|
1052
|
+
] }, l))
|
|
1053
|
+
] }),
|
|
1054
|
+
mode.kind === "edit" && mode.target === "new" && /* @__PURE__ */ jsxs9(Text10, { children: [
|
|
1055
|
+
"when (e.g. ctxPct>80): ",
|
|
1056
|
+
mode.buf,
|
|
1057
|
+
"_",
|
|
1058
|
+
mode.buf && !isValidWhen(mode.buf) ? " \u26A0 invalid" : ""
|
|
1059
|
+
] }),
|
|
1060
|
+
/* @__PURE__ */ jsx9(Text10, { dimColor: true, children: "\u2191/\u2193 nav \xB7 space toggle \xB7 a add \xB7 w when \xB7 t text \xB7 x remove \xB7 esc back" })
|
|
1061
|
+
] });
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// src/screens/themes.tsx
|
|
1065
|
+
import { Box as Box11, Text as Text11, useInput as useInput9 } from "ink";
|
|
1066
|
+
import { useState as useState9 } from "react";
|
|
1067
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1068
|
+
var FG2 = NAMED_COLORS.filter((c) => !c.startsWith("bg"));
|
|
1069
|
+
var BG2 = NAMED_COLORS.filter((c) => c.startsWith("bg"));
|
|
1070
|
+
var FIELDS3 = ["fg", "bg"];
|
|
1071
|
+
function uniqueName(base, themes) {
|
|
1072
|
+
if (!(base in themes)) {
|
|
1073
|
+
return base;
|
|
1074
|
+
}
|
|
1075
|
+
let i = 2;
|
|
1076
|
+
while (`${base}-${i}` in themes) {
|
|
1077
|
+
i++;
|
|
1078
|
+
}
|
|
1079
|
+
return `${base}-${i}`;
|
|
1080
|
+
}
|
|
1081
|
+
function Themes({ config, setConfig, goHome }) {
|
|
1082
|
+
const names = Object.keys(config.themes);
|
|
1083
|
+
const [sel, setSel] = useState9(Math.max(0, names.indexOf(config.theme)));
|
|
1084
|
+
const [mode, setMode] = useState9("list");
|
|
1085
|
+
const [w, setW] = useState9(0);
|
|
1086
|
+
const [field, setField] = useState9(0);
|
|
1087
|
+
const [pick, setPick] = useState9(0);
|
|
1088
|
+
const name = names[Math.min(sel, names.length - 1)];
|
|
1089
|
+
const theme = name && config.themes[name] || {};
|
|
1090
|
+
const widgets = Object.keys(theme);
|
|
1091
|
+
const widget = widgets[Math.min(w, widgets.length - 1)];
|
|
1092
|
+
const list = FIELDS3[field] === "fg" ? FG2 : BG2;
|
|
1093
|
+
const setThemes = (themes, active = config.theme) => setConfig({ ...config, theme: active, themes });
|
|
1094
|
+
useInput9((input, key) => {
|
|
1095
|
+
if (mode === "list") {
|
|
1096
|
+
if (key.escape) {
|
|
1097
|
+
goHome();
|
|
1098
|
+
} else if (key.upArrow) {
|
|
1099
|
+
setSel((s) => Math.max(0, s - 1));
|
|
1100
|
+
} else if (key.downArrow) {
|
|
1101
|
+
setSel((s) => Math.min(names.length - 1, s + 1));
|
|
1102
|
+
} else if (key.return) {
|
|
1103
|
+
if (name) {
|
|
1104
|
+
setConfig({ ...config, theme: name });
|
|
1105
|
+
}
|
|
1106
|
+
} else if (key.rightArrow) {
|
|
1107
|
+
if (widgets.length) {
|
|
1108
|
+
setW(0);
|
|
1109
|
+
setMode("colors");
|
|
1110
|
+
}
|
|
1111
|
+
} else if (input === "n") {
|
|
1112
|
+
const n = uniqueName("custom", config.themes);
|
|
1113
|
+
setThemes({ ...config.themes, [n]: {} });
|
|
1114
|
+
setSel(names.length);
|
|
1115
|
+
} else if (input === "d" && name) {
|
|
1116
|
+
const n = uniqueName(`${name}-copy`, config.themes);
|
|
1117
|
+
setThemes({ ...config.themes, [n]: structuredClone(theme) });
|
|
1118
|
+
setSel(names.length);
|
|
1119
|
+
}
|
|
1120
|
+
} else if (mode === "colors") {
|
|
1121
|
+
if (key.escape || key.leftArrow && field === 0) {
|
|
1122
|
+
setMode("list");
|
|
1123
|
+
} else if (key.upArrow) {
|
|
1124
|
+
setW((i) => Math.max(0, i - 1));
|
|
1125
|
+
} else if (key.downArrow) {
|
|
1126
|
+
setW((i) => Math.min(widgets.length - 1, i + 1));
|
|
1127
|
+
} else if (key.leftArrow) {
|
|
1128
|
+
setField(0);
|
|
1129
|
+
} else if (key.rightArrow) {
|
|
1130
|
+
setField(1);
|
|
1131
|
+
} else if (key.return && widget) {
|
|
1132
|
+
const cur = theme[widget]?.[FIELDS3[field] ?? "fg"];
|
|
1133
|
+
setPick(Math.max(0, list.indexOf(cur ?? "")));
|
|
1134
|
+
setMode("picker");
|
|
1135
|
+
}
|
|
1136
|
+
} else {
|
|
1137
|
+
if (key.escape) {
|
|
1138
|
+
setMode("colors");
|
|
1139
|
+
} else if (key.upArrow) {
|
|
1140
|
+
setPick((p) => Math.max(0, p - 1));
|
|
1141
|
+
} else if (key.downArrow) {
|
|
1142
|
+
setPick((p) => Math.min(list.length - 1, p + 1));
|
|
1143
|
+
} else if (key.return && name && widget) {
|
|
1144
|
+
const f = FIELDS3[field] ?? "fg";
|
|
1145
|
+
const chosen = list[pick];
|
|
1146
|
+
if (chosen) {
|
|
1147
|
+
const entry = { ...theme[widget], [f]: chosen };
|
|
1148
|
+
setThemes({ ...config.themes, [name]: { ...theme, [widget]: entry } });
|
|
1149
|
+
setMode("colors");
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
});
|
|
1154
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
|
|
1155
|
+
/* @__PURE__ */ jsx10(Text11, { bold: true, children: "Themes" }),
|
|
1156
|
+
names.map((n, i) => /* @__PURE__ */ jsxs10(Text11, { color: i === sel && mode === "list" ? "cyan" : void 0, children: [
|
|
1157
|
+
i === sel ? "\u25B8 " : " ",
|
|
1158
|
+
n,
|
|
1159
|
+
n === config.theme ? " (active)" : ""
|
|
1160
|
+
] }, n)),
|
|
1161
|
+
/* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
|
|
1162
|
+
name,
|
|
1163
|
+
" colors:"
|
|
1164
|
+
] }),
|
|
1165
|
+
widgets.map((wt, i) => {
|
|
1166
|
+
const c = theme[wt] ?? {};
|
|
1167
|
+
const on = mode !== "list" && i === w;
|
|
1168
|
+
return /* @__PURE__ */ jsxs10(Text11, { children: [
|
|
1169
|
+
on ? "\u25B8 " : " ",
|
|
1170
|
+
wt.padEnd(20),
|
|
1171
|
+
/* @__PURE__ */ jsxs10(Text11, { color: inkColor(c.fg), inverse: on && field === 0, children: [
|
|
1172
|
+
" ",
|
|
1173
|
+
c.fg ?? "(none)",
|
|
1174
|
+
" "
|
|
1175
|
+
] }),
|
|
1176
|
+
/* @__PURE__ */ jsxs10(Text11, { backgroundColor: inkBg(c.bg), inverse: on && field === 1, children: [
|
|
1177
|
+
" ",
|
|
1178
|
+
c.bg ?? "(none)",
|
|
1179
|
+
" "
|
|
1180
|
+
] })
|
|
1181
|
+
] }, wt);
|
|
1182
|
+
}),
|
|
1183
|
+
mode === "picker" && /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
|
|
1184
|
+
/* @__PURE__ */ jsxs10(Text11, { bold: true, children: [
|
|
1185
|
+
"Pick ",
|
|
1186
|
+
FIELDS3[field],
|
|
1187
|
+
" for ",
|
|
1188
|
+
widget
|
|
1189
|
+
] }),
|
|
1190
|
+
list.map((c, i) => /* @__PURE__ */ jsxs10(Text11, { color: i === pick ? "cyan" : void 0, children: [
|
|
1191
|
+
i === pick ? "\u25B8 " : " ",
|
|
1192
|
+
/* @__PURE__ */ jsxs10(
|
|
1193
|
+
Text11,
|
|
1194
|
+
{
|
|
1195
|
+
color: FIELDS3[field] === "fg" ? inkColor(c) : void 0,
|
|
1196
|
+
backgroundColor: FIELDS3[field] === "bg" ? inkBg(c) : void 0,
|
|
1197
|
+
children: [
|
|
1198
|
+
" ",
|
|
1199
|
+
c,
|
|
1200
|
+
" "
|
|
1201
|
+
]
|
|
1202
|
+
}
|
|
1203
|
+
)
|
|
1204
|
+
] }, c))
|
|
1205
|
+
] }),
|
|
1206
|
+
/* @__PURE__ */ jsx10(Text11, { dimColor: true, children: mode === "list" ? "\u2191/\u2193 select \xB7 \u23CE activate \xB7 \u2192 colors \xB7 n new \xB7 d duplicate \xB7 esc back" : mode === "colors" ? "\u2191/\u2193 widget \xB7 \u2190/\u2192 fg/bg \xB7 \u23CE pick \xB7 esc back" : "\u2191/\u2193 choose \xB7 \u23CE set \xB7 esc cancel" })
|
|
1207
|
+
] });
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// src/app.tsx
|
|
1211
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1212
|
+
function Placeholder({ id, goHome }) {
|
|
1213
|
+
useInput10((_i, key) => {
|
|
1214
|
+
if (key.escape) {
|
|
1215
|
+
goHome();
|
|
1216
|
+
}
|
|
1217
|
+
});
|
|
1218
|
+
return /* @__PURE__ */ jsxs11(Text12, { children: [
|
|
1219
|
+
id,
|
|
1220
|
+
" \u2026coming soon (esc)\u2026"
|
|
1221
|
+
] });
|
|
1222
|
+
}
|
|
1223
|
+
function PlaceholderOrScreen({
|
|
1224
|
+
id,
|
|
1225
|
+
snapshot,
|
|
1226
|
+
source,
|
|
1227
|
+
...props
|
|
1228
|
+
}) {
|
|
1229
|
+
switch (id) {
|
|
1230
|
+
case "defaults":
|
|
1231
|
+
return /* @__PURE__ */ jsx11(Defaults, { ...props });
|
|
1232
|
+
case "themes":
|
|
1233
|
+
return /* @__PURE__ */ jsx11(Themes, { ...props });
|
|
1234
|
+
case "powerline":
|
|
1235
|
+
return /* @__PURE__ */ jsx11(Powerline, { ...props });
|
|
1236
|
+
case "items":
|
|
1237
|
+
return /* @__PURE__ */ jsx11(Items, { ...props });
|
|
1238
|
+
case "surfaces":
|
|
1239
|
+
return /* @__PURE__ */ jsx11(Surfaces, { ...props });
|
|
1240
|
+
case "statusline":
|
|
1241
|
+
return /* @__PURE__ */ jsx11(Statusline, { ...props });
|
|
1242
|
+
case "import":
|
|
1243
|
+
return /* @__PURE__ */ jsx11(Import, { ...props });
|
|
1244
|
+
case "preview":
|
|
1245
|
+
return /* @__PURE__ */ jsx11(PreviewScreen, { ...props, snapshot, source });
|
|
1246
|
+
default:
|
|
1247
|
+
return /* @__PURE__ */ jsx11(Placeholder, { id, goHome: props.goHome });
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
function App({ modProbe } = {}) {
|
|
1251
|
+
const { exit } = useApp();
|
|
1252
|
+
const probe = modProbe ?? {
|
|
1253
|
+
detect: () => detectModStatus(),
|
|
1254
|
+
install: () => installMod()
|
|
1255
|
+
};
|
|
1256
|
+
const loaded = useMemo2(() => loadConfigFile(configPath()), []);
|
|
1257
|
+
const [config, setConfig] = useState10(loaded.config);
|
|
1258
|
+
const [screen, setScreen] = useState10("menu");
|
|
1259
|
+
const [saveError, setSaveError] = useState10(null);
|
|
1260
|
+
const [saveWarnings, setSaveWarnings] = useState10([]);
|
|
1261
|
+
const [saved, setSaved] = useState10(false);
|
|
1262
|
+
const [modStatus, setModStatus] = useState10(null);
|
|
1263
|
+
const [installing, setInstalling] = useState10(false);
|
|
1264
|
+
const [installMsg, setInstallMsg] = useState10(null);
|
|
1265
|
+
const [installOk, setInstallOk] = useState10(true);
|
|
1266
|
+
const installStartedRef = useRef(false);
|
|
1267
|
+
const mountedRef = useRef(true);
|
|
1268
|
+
useEffect(
|
|
1269
|
+
() => () => {
|
|
1270
|
+
mountedRef.current = false;
|
|
1271
|
+
},
|
|
1272
|
+
[]
|
|
1273
|
+
);
|
|
1274
|
+
const snap = readSnapshot(snapshotPath());
|
|
1275
|
+
const width = process.stdout.columns || 120;
|
|
1276
|
+
useEffect(() => {
|
|
1277
|
+
probe.detect().then((s) => {
|
|
1278
|
+
if (mountedRef.current) {
|
|
1279
|
+
setModStatus(s);
|
|
1280
|
+
}
|
|
1281
|
+
}).catch(() => {
|
|
1282
|
+
if (mountedRef.current) {
|
|
1283
|
+
setModStatus("unknown");
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
}, []);
|
|
1287
|
+
useInput10((input) => {
|
|
1288
|
+
if (screen !== "menu") {
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
if (input === "q") {
|
|
1292
|
+
exit();
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
if (input === "i" && modStatus === "absent" && !installStartedRef.current) {
|
|
1296
|
+
installStartedRef.current = true;
|
|
1297
|
+
setInstalling(true);
|
|
1298
|
+
probe.install().then((r) => {
|
|
1299
|
+
if (!mountedRef.current) {
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
setInstallMsg(r.message);
|
|
1303
|
+
setInstallOk(r.ok);
|
|
1304
|
+
if (r.ok) {
|
|
1305
|
+
setModStatus("installed");
|
|
1306
|
+
}
|
|
1307
|
+
}).catch((e) => {
|
|
1308
|
+
if (!mountedRef.current) {
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
setInstallMsg(e instanceof Error ? e.message : String(e));
|
|
1312
|
+
setInstallOk(false);
|
|
1313
|
+
}).finally(() => {
|
|
1314
|
+
installStartedRef.current = false;
|
|
1315
|
+
if (mountedRef.current) {
|
|
1316
|
+
setInstalling(false);
|
|
1317
|
+
}
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
});
|
|
1321
|
+
const save = () => {
|
|
1322
|
+
try {
|
|
1323
|
+
setSaveWarnings(saveConfigFile(configPath(), config));
|
|
1324
|
+
setSaveError(null);
|
|
1325
|
+
setSaved(true);
|
|
1326
|
+
return true;
|
|
1327
|
+
} catch (e) {
|
|
1328
|
+
setSaveError(e instanceof Error ? e.message : String(e));
|
|
1329
|
+
return false;
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
const common = { config, setConfig, goHome: () => setScreen("menu") };
|
|
1333
|
+
return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingX: 1, children: [
|
|
1334
|
+
screen === "menu" ? /* @__PURE__ */ jsx11(
|
|
1335
|
+
Menu,
|
|
1336
|
+
{
|
|
1337
|
+
onSelect: (id) => {
|
|
1338
|
+
if (id === "quit") {
|
|
1339
|
+
exit();
|
|
1340
|
+
} else if (id === "save") {
|
|
1341
|
+
if (save()) {
|
|
1342
|
+
exit();
|
|
1343
|
+
}
|
|
1344
|
+
} else {
|
|
1345
|
+
setScreen(id);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
) : /* @__PURE__ */ jsx11(
|
|
1350
|
+
PlaceholderOrScreen,
|
|
1351
|
+
{
|
|
1352
|
+
id: screen,
|
|
1353
|
+
...common,
|
|
1354
|
+
snapshot: snap.snapshot,
|
|
1355
|
+
source: snap.source
|
|
1356
|
+
}
|
|
1357
|
+
),
|
|
1358
|
+
!saved && loaded.warnings.length > 0 && /* @__PURE__ */ jsxs11(Text12, { color: "yellow", children: [
|
|
1359
|
+
"\u26A0 existing config: ",
|
|
1360
|
+
loaded.warnings.join("; "),
|
|
1361
|
+
" \u2014 editing from defaults; saving replaces it (a .bak is kept)"
|
|
1362
|
+
] }),
|
|
1363
|
+
saveWarnings.length > 0 && /* @__PURE__ */ jsxs11(Text12, { color: "yellow", children: [
|
|
1364
|
+
"\u26A0 saved with changes: ",
|
|
1365
|
+
saveWarnings.join("; ")
|
|
1366
|
+
] }),
|
|
1367
|
+
saveError && /* @__PURE__ */ jsxs11(Text12, { color: "red", children: [
|
|
1368
|
+
"Could not save config: ",
|
|
1369
|
+
saveError
|
|
1370
|
+
] }),
|
|
1371
|
+
installMsg ? /* @__PURE__ */ jsxs11(Text12, { color: installOk ? "green" : "red", children: [
|
|
1372
|
+
installOk ? "\u2713 " : "\u26A0 ",
|
|
1373
|
+
installMsg
|
|
1374
|
+
] }) : modStatus === "absent" ? /* @__PURE__ */ jsxs11(Text12, { color: "yellow", children: [
|
|
1375
|
+
"\u26A0 ccstatus mod not installed \u2014 press i to install (",
|
|
1376
|
+
PLUGIN_ID,
|
|
1377
|
+
")",
|
|
1378
|
+
installing ? " \u2026 installing" : ""
|
|
1379
|
+
] }) : modStatus === "disabled" ? /* @__PURE__ */ jsxs11(Text12, { color: "yellow", children: [
|
|
1380
|
+
"\u26A0 ccstatus mod installed but disabled \u2014 enable it: claude plugin enable ",
|
|
1381
|
+
PLUGIN_ID
|
|
1382
|
+
] }) : modStatus === "unknown" ? /* @__PURE__ */ jsxs11(Text12, { color: "yellow", children: [
|
|
1383
|
+
"\u26A0 couldn't check if the mod is installed \u2014 if the bar isn't showing, run: claude plugin marketplace add ",
|
|
1384
|
+
MARKETPLACE_SLUG,
|
|
1385
|
+
" && claude plugin install ",
|
|
1386
|
+
PLUGIN_ID
|
|
1387
|
+
] }) : null,
|
|
1388
|
+
/* @__PURE__ */ jsx11(Preview, { config, snapshot: snap.snapshot, source: snap.source, width })
|
|
1389
|
+
] });
|
|
1390
|
+
}
|
|
1391
|
+
export {
|
|
1392
|
+
App
|
|
1393
|
+
};
|