@sigx/cli 0.2.7 → 0.3.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.
Files changed (37) hide show
  1. package/README.md +8 -59
  2. package/dist/cli.js +4 -3
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/create.d.ts +1 -1
  5. package/dist/commands/create.js +48 -413
  6. package/dist/commands/create.js.map +1 -1
  7. package/dist/commands/scaffold.js +165 -0
  8. package/dist/commands/scaffold.js.map +1 -0
  9. package/dist/index.d.ts +1 -1
  10. package/dist/plugin.d.ts +54 -0
  11. package/dist/plugin.js.map +1 -1
  12. package/dist/shell/index.d.ts +23 -0
  13. package/dist/shell/index.js +503 -0
  14. package/dist/shell/index.js.map +1 -0
  15. package/dist/templates/basic-daisyui/src/App.tsx +15 -4
  16. package/dist/templates/lynx/src/lynx-env.d.ts +1 -0
  17. package/dist/templates/lynx/src/main.tsx +2 -2
  18. package/dist/templates/lynx-daisyui/src/lynx-env.d.ts +1 -0
  19. package/dist/templates/lynx-daisyui/src/main.tsx +2 -2
  20. package/dist/templates/lynx-tailwind/src/lynx-env.d.ts +1 -0
  21. package/dist/templates/lynx-tailwind/src/main.tsx +2 -2
  22. package/dist/templates/ssg/package.json +4 -3
  23. package/dist/templates/ssg-daisyui/package.json +4 -3
  24. package/dist/templates/ssg-tailwind/package.json +4 -3
  25. package/dist/templates/ssr-daisyui/src/pages/Home.tsx +16 -4
  26. package/package.json +7 -3
  27. package/templates/basic-daisyui/src/App.tsx +15 -4
  28. package/templates/lynx/src/lynx-env.d.ts +1 -0
  29. package/templates/lynx/src/main.tsx +2 -2
  30. package/templates/lynx-daisyui/src/lynx-env.d.ts +1 -0
  31. package/templates/lynx-daisyui/src/main.tsx +2 -2
  32. package/templates/lynx-tailwind/src/lynx-env.d.ts +1 -0
  33. package/templates/lynx-tailwind/src/main.tsx +2 -2
  34. package/templates/ssg/package.json +4 -3
  35. package/templates/ssg-daisyui/package.json +4 -3
  36. package/templates/ssg-tailwind/package.json +4 -3
  37. package/templates/ssr-daisyui/src/pages/Home.tsx +16 -4
@@ -0,0 +1,503 @@
1
+ import { Box, Col, Divider, KeyHints, Spacer, StatusBar, SuggestionList, Tabs, Text, TextArea, component, createLogStore, createViewStack, defineApp, exitTerminal, getTerminalSize, isEsc, layoutText, listThemes, onKey, onMounted, onUnmounted, paintToken, printStatic, renderPixelArt, setTheme, signal, terminalMount } from "@sigx/terminal";
2
+ import { jsx, jsxs } from "@sigx/terminal/jsx-runtime";
3
+ //#region src/shell/contributions.ts
4
+ /** Collect the `tui` contributions of every plugin, in discovery order. */
5
+ function collectTuiContributions(plugins) {
6
+ return plugins.map((p) => p.tui).filter((t) => !!t);
7
+ }
8
+ /**
9
+ * Merge peer-plugin contributions into the host's config. Host entries come
10
+ * first and win conflicts: a duplicate tab `id`, command `name`, or shortcut
11
+ * `key` from a later contributor is skipped (with a warn when a logger is
12
+ * given). `status` providers concatenate.
13
+ */
14
+ function mergeShellConfig(config, contributions, logger) {
15
+ const tabs = [...config.tabs];
16
+ const commands = [...config.commands ?? []];
17
+ const shortcuts = [...config.shortcuts ?? []];
18
+ const statusProviders = config.status ? [config.status] : [];
19
+ const tabIds = new Set(tabs.map((t) => t.id));
20
+ const commandNames = new Set(commands.map((c) => c.name));
21
+ const shortcutKeys = new Set(shortcuts.map((s) => s.key));
22
+ for (const tui of contributions) {
23
+ for (const tab of tui.tabs ?? []) {
24
+ if (tabIds.has(tab.id)) {
25
+ logger?.warn(`shell: duplicate tab "${tab.id}" skipped`);
26
+ continue;
27
+ }
28
+ tabIds.add(tab.id);
29
+ tabs.push(tab);
30
+ }
31
+ for (const cmd of tui.commands ?? []) {
32
+ if (commandNames.has(cmd.name)) {
33
+ logger?.warn(`shell: duplicate command "${cmd.name}" skipped`);
34
+ continue;
35
+ }
36
+ commandNames.add(cmd.name);
37
+ commands.push(cmd);
38
+ }
39
+ for (const sc of tui.shortcuts ?? []) {
40
+ if (shortcutKeys.has(sc.key)) {
41
+ logger?.warn(`shell: duplicate shortcut "${sc.key}" skipped`);
42
+ continue;
43
+ }
44
+ shortcutKeys.add(sc.key);
45
+ shortcuts.push(sc);
46
+ }
47
+ if (tui.status) statusProviders.push(tui.status);
48
+ }
49
+ return {
50
+ ...config,
51
+ tabs,
52
+ commands,
53
+ shortcuts,
54
+ status: statusProviders.length ? () => statusProviders.flatMap((s) => s()) : void 0
55
+ };
56
+ }
57
+ //#endregion
58
+ //#region src/shell/runShell.tsx
59
+ /** @jsxImportSource @sigx/terminal */
60
+ /**
61
+ * The persistent dev-shell runtime plugin commands host their dashboards in.
62
+ *
63
+ * Two layouts:
64
+ *
65
+ * - `mode: 'fullscreen'` (dashboards — k9s/showcase shape): alt-screen app
66
+ * with a title bar, segmented tab strip, full-height tab body, and a
67
+ * pinned status/hints line. `/` summons a command-palette overlay with
68
+ * intellisense. `say()` streams into the log store (visible live in a
69
+ * Logs tab) AND queues into the normal terminal buffer, so quitting
70
+ * leaves a post-mortem trail in real scrollback.
71
+ *
72
+ * - `mode: 'inline'` (default — transcript shape): header printed once into
73
+ * scrollback, permanent transcript via `say`, tabs + bottom-anchored
74
+ * `/`-command input. The conversation IS the terminal scrollback.
75
+ *
76
+ * Both: plugin-contributed tabs/commands/shortcuts/status (mergeShellConfig),
77
+ * single-key shortcuts, 1–9 tab switching, Esc-pop views, and Ctrl+C that
78
+ * runs onExit cleanup BEFORE exiting. In non-TTY environments nothing
79
+ * mounts — callers get a plain streaming handle with the same shape.
80
+ */
81
+ var CTRL_C = String.fromCharCode(3);
82
+ var MAX_INPUT_ROWS = 6;
83
+ async function runShell(config, opts = {}) {
84
+ const merged = mergeShellConfig(config, collectTuiContributions(config.plugins ?? []));
85
+ const fullscreen = merged.mode === "fullscreen";
86
+ if (!(opts.interactive ?? (!!process.stdout.isTTY && !!process.stdin.isTTY))) return plainShell(merged);
87
+ const theme = merged.theme ?? "obsidian";
88
+ if (listThemes().includes(theme)) setTheme(theme);
89
+ const store = createLogStore();
90
+ const status = signal({ items: [] });
91
+ const transcript = signal({ lines: 0 });
92
+ const tab = signal({ active: merged.tabs[0]?.id ?? "" });
93
+ const views = createViewStack("shell");
94
+ const say = (text = "") => {
95
+ if (fullscreen) {
96
+ store.push(text + "\n");
97
+ printStatic(text);
98
+ return;
99
+ }
100
+ transcript.lines += text.split("\n").length;
101
+ printStatic(text);
102
+ };
103
+ const exitSubs = [];
104
+ let exiting = false;
105
+ const shell = {
106
+ isInteractive: true,
107
+ say,
108
+ store,
109
+ setStatus: (items) => {
110
+ status.items = items;
111
+ },
112
+ switchTab: (id) => {
113
+ if (merged.tabs.some((t) => t.id === id)) tab.active = id;
114
+ },
115
+ pushView: (id) => views.push(id),
116
+ popView: () => views.pop(),
117
+ onExit: (cb) => {
118
+ exitSubs.push(cb);
119
+ return () => {
120
+ const i = exitSubs.indexOf(cb);
121
+ if (i >= 0) exitSubs.splice(i, 1);
122
+ };
123
+ },
124
+ exit: (code = 0) => {
125
+ if (exiting) return;
126
+ exiting = true;
127
+ (async () => {
128
+ try {
129
+ await runTeardown(merged, exitSubs);
130
+ } finally {
131
+ exitTerminal();
132
+ process.exit(code);
133
+ }
134
+ })();
135
+ }
136
+ };
137
+ wireSignals(shell);
138
+ const builtins = [{
139
+ name: "/help",
140
+ description: "list available commands",
141
+ run: () => {
142
+ say("");
143
+ for (const c of allCommands()) say(` ${paintToken(c.name, "accent")} ${paintToken(c.description, "dim")}`);
144
+ }
145
+ }, {
146
+ name: "/quit",
147
+ description: "exit",
148
+ run: () => shell.exit(0)
149
+ }];
150
+ const allCommands = () => [...merged.commands ?? [], ...builtins];
151
+ const runCommand = async (name) => {
152
+ const cmd = allCommands().find((c) => c.name === name);
153
+ if (!cmd) {
154
+ say(paintToken(`unknown command ${name} — try /help`, "dim"));
155
+ return;
156
+ }
157
+ try {
158
+ await cmd.run(shell);
159
+ } catch (err) {
160
+ say(paintToken(`${name} failed: ${err instanceof Error ? err.message : String(err)}`, "danger"));
161
+ }
162
+ };
163
+ defineApp(/* @__PURE__ */ jsx(component(() => {
164
+ const input = signal({ value: "" });
165
+ const palette = signal({ open: false });
166
+ const offs = [];
167
+ const inputActive = () => fullscreen ? palette.open : input.value !== "";
168
+ const closePalette = () => {
169
+ palette.open = false;
170
+ input.value = "";
171
+ };
172
+ const submit = (text) => {
173
+ const trimmed = text.trim();
174
+ input.value = "";
175
+ if (fullscreen) palette.open = false;
176
+ if (!trimmed) return;
177
+ if (trimmed.startsWith("/")) {
178
+ runCommand(trimmed.split(/\s/)[0]);
179
+ return;
180
+ }
181
+ say(paintToken(`type / for commands`, "dim"));
182
+ };
183
+ onMounted(() => {
184
+ if (!fullscreen) {
185
+ if (merged.logo) say(renderPixelArt(merged.logo.rows, merged.logo.palette).join("\n"));
186
+ say(`${paintToken(merged.title, "accent")}${merged.version ? ` ${paintToken(merged.version, "dim")}` : ""}`);
187
+ say("");
188
+ }
189
+ offs.push(onKey((key) => {
190
+ if (!isEsc(key)) return;
191
+ if (fullscreen && palette.open) {
192
+ closePalette();
193
+ return true;
194
+ }
195
+ if (views.depth() > 1) {
196
+ views.pop();
197
+ return true;
198
+ }
199
+ }, { layer: "view" }));
200
+ offs.push(onKey((key) => {
201
+ if (inputActive()) return;
202
+ if (fullscreen && key === "/") {
203
+ palette.open = true;
204
+ return true;
205
+ }
206
+ const digit = key.length === 1 ? key.charCodeAt(0) - 48 : 0;
207
+ if (digit >= 1 && digit <= Math.min(9, merged.tabs.length)) {
208
+ tab.active = merged.tabs[digit - 1].id;
209
+ return true;
210
+ }
211
+ const sc = (merged.shortcuts ?? []).find((s) => s.key === key);
212
+ if (sc) {
213
+ Promise.resolve(sc.run(shell)).catch((err) => {
214
+ say(paintToken(`${sc.label} failed: ${err instanceof Error ? err.message : String(err)}`, "danger"));
215
+ });
216
+ return true;
217
+ }
218
+ }, { layer: "overlay" }));
219
+ offs.push(onKey((key) => {
220
+ if (key === CTRL_C) {
221
+ shell.exit(130);
222
+ return true;
223
+ }
224
+ }, { layer: "global" }));
225
+ });
226
+ onUnmounted(() => {
227
+ for (const off of offs) off();
228
+ });
229
+ const suggestionsFor = (value) => value.startsWith("/") ? allCommands().filter((c) => c.name.startsWith(value.trim().split(/\s/)[0])).map((c) => ({
230
+ value: c.name,
231
+ label: c.name,
232
+ description: c.description
233
+ })) : [];
234
+ const statusLine = () => {
235
+ const items = [...merged.status?.() ?? [], ...status.items];
236
+ if (items.length === 0) return null;
237
+ return /* @__PURE__ */ jsx("box", { children: items.flatMap((s, i) => [
238
+ ...i > 0 ? [/* @__PURE__ */ jsx(Text, {
239
+ color: "dim",
240
+ children: " · "
241
+ })] : [],
242
+ /* @__PURE__ */ jsxs(Text, {
243
+ color: "dim",
244
+ children: [s.label, " "]
245
+ }),
246
+ /* @__PURE__ */ jsx(Text, {
247
+ color: s.tone ?? "fg",
248
+ children: s.value
249
+ })
250
+ ]) });
251
+ };
252
+ const resolveTab = () => {
253
+ const viewId = views.current();
254
+ return viewId !== "shell" ? merged.tabs.find((t) => t.id === viewId) : merged.tabs.find((t) => t.id === tab.active);
255
+ };
256
+ const hintList = () => [
257
+ ...(merged.shortcuts ?? []).map((s) => ({
258
+ key: s.key,
259
+ label: s.label
260
+ })),
261
+ {
262
+ key: "/",
263
+ label: "commands"
264
+ },
265
+ {
266
+ key: "^C",
267
+ label: "quit"
268
+ }
269
+ ];
270
+ const renderFullscreen = () => {
271
+ const { columns: cols } = getTerminalSize();
272
+ const activeTab = resolveTab();
273
+ const suggestions = suggestionsFor(input.value);
274
+ const strip = merged.tabs.map((t, i) => {
275
+ const on = t.id === tab.active;
276
+ return /* @__PURE__ */ jsxs(Text, {
277
+ bg: on ? "accent" : "accentSoft",
278
+ color: on ? "accentText" : "dim",
279
+ children: [
280
+ " ",
281
+ String(i + 1),
282
+ " ",
283
+ t.label,
284
+ " "
285
+ ]
286
+ });
287
+ });
288
+ const barItems = [
289
+ ...[...merged.status?.() ?? [], ...status.items].map((s) => ({
290
+ key: s.value,
291
+ label: s.label
292
+ })),
293
+ ...(merged.shortcuts ?? []).map((s) => ({
294
+ key: s.key,
295
+ label: s.label
296
+ })),
297
+ ...merged.tabs.length > 1 ? [{
298
+ key: `1-${merged.tabs.length}`,
299
+ label: "tabs"
300
+ }] : [],
301
+ {
302
+ key: "/",
303
+ label: "commands"
304
+ },
305
+ {
306
+ key: "^C",
307
+ label: "quit"
308
+ }
309
+ ];
310
+ return /* @__PURE__ */ jsxs(Col, { children: [
311
+ /* @__PURE__ */ jsxs(Box, {
312
+ border: "thick",
313
+ borderColor: "accent",
314
+ padX: 1,
315
+ children: [/* @__PURE__ */ jsx(Text, {
316
+ color: "accent",
317
+ bold: true,
318
+ children: merged.title
319
+ }), merged.version ? /* @__PURE__ */ jsx(Text, {
320
+ color: "dim",
321
+ children: ` ${merged.version}`
322
+ }) : null]
323
+ }),
324
+ merged.tabs.length > 1 && /* @__PURE__ */ jsx("box", { children: strip }),
325
+ /* @__PURE__ */ jsx(Spacer, { size: 1 }),
326
+ activeTab ? /* @__PURE__ */ jsx(Box, {
327
+ border: "rounded",
328
+ borderColor: "line",
329
+ label: activeTab.label,
330
+ labelColor: "accent",
331
+ padX: 1,
332
+ dropShadow: true,
333
+ children: activeTab.render()
334
+ }) : null,
335
+ /* @__PURE__ */ jsx(Spacer, { size: 1 }),
336
+ palette.open ? /* @__PURE__ */ jsxs(Col, { children: [
337
+ /* @__PURE__ */ jsx(Divider, {
338
+ width: Math.min(cols, 120),
339
+ label: "command"
340
+ }),
341
+ /* @__PURE__ */ jsx(TextArea, {
342
+ autofocus: true,
343
+ model: () => input.value,
344
+ placeholder: "/command",
345
+ maxRows: 2,
346
+ onSubmit: submit
347
+ }),
348
+ suggestions.length > 0 && /* @__PURE__ */ jsx(SuggestionList, {
349
+ items: suggestions,
350
+ onAccept: (name) => {
351
+ closePalette();
352
+ runCommand(name);
353
+ },
354
+ onDismiss: closePalette
355
+ })
356
+ ] }) : /* @__PURE__ */ jsx(StatusBar, { items: barItems })
357
+ ] });
358
+ };
359
+ const renderInline = () => {
360
+ const { columns: cols, rows } = getTerminalSize();
361
+ const innerWidth = Math.max(4, Math.max(20, cols - 4) - 2);
362
+ const inputRows = Math.min(MAX_INPUT_ROWS, layoutText(input.value, innerWidth).rows.length);
363
+ const suggestions = suggestionsFor(input.value);
364
+ const viewId = views.current();
365
+ const activeTab = resolveTab();
366
+ const chrome = 1 + inputRows + suggestions.length + 1;
367
+ const filler = Math.max(0, rows - transcript.lines - 18 - chrome - 1);
368
+ return /* @__PURE__ */ jsxs(Col, { children: [
369
+ statusLine(),
370
+ viewId === "shell" && merged.tabs.length > 1 && /* @__PURE__ */ jsx(Tabs, {
371
+ options: merged.tabs.map((t) => ({
372
+ value: t.id,
373
+ label: t.label
374
+ })),
375
+ model: () => tab.active,
376
+ onChange: (id) => {
377
+ tab.active = id;
378
+ }
379
+ }),
380
+ activeTab ? activeTab.render() : null,
381
+ filler > 0 && /* @__PURE__ */ jsx(Spacer, { size: filler }),
382
+ /* @__PURE__ */ jsx(Divider, { width: Math.min(cols, 120) }),
383
+ /* @__PURE__ */ jsx(TextArea, {
384
+ autofocus: true,
385
+ model: () => input.value,
386
+ placeholder: "/ for commands",
387
+ maxRows: MAX_INPUT_ROWS,
388
+ onSubmit: submit
389
+ }),
390
+ suggestions.length > 0 && /* @__PURE__ */ jsx(SuggestionList, {
391
+ items: suggestions,
392
+ onAccept: (name) => {
393
+ input.value = "";
394
+ runCommand(name);
395
+ },
396
+ onDismiss: () => {
397
+ input.value = "";
398
+ }
399
+ }),
400
+ /* @__PURE__ */ jsx(KeyHints, { hints: hintList() })
401
+ ] });
402
+ };
403
+ return () => fullscreen ? renderFullscreen() : renderInline();
404
+ }, { name: "ShellApp" }), {})).mount(fullscreen ? {
405
+ mode: "fullscreen",
406
+ exitOnCtrlC: false
407
+ } : {
408
+ mode: "inline",
409
+ clearConsole: true,
410
+ exitOnCtrlC: false
411
+ }, terminalMount);
412
+ await runSetups(merged, shell);
413
+ await merged.onReady?.(shell);
414
+ return shell;
415
+ }
416
+ /** Host onExit first, then subscriber teardowns most-recent-first; failures
417
+ * in one never block the rest. */
418
+ async function runTeardown(merged, subs) {
419
+ try {
420
+ await merged.onExit?.();
421
+ } catch {}
422
+ for (const cb of [...subs].reverse()) try {
423
+ await cb();
424
+ } catch {}
425
+ }
426
+ /** Run contributed `setup` lifecycles; returned functions become teardowns. */
427
+ async function runSetups(merged, shell) {
428
+ for (const plugin of merged.plugins ?? []) {
429
+ const setup = plugin.tui?.setup;
430
+ if (!setup) continue;
431
+ try {
432
+ const teardown = await setup(shell);
433
+ if (typeof teardown === "function") shell.onExit(teardown);
434
+ } catch (err) {
435
+ shell.say(`plugin ${plugin.name} setup failed: ${err instanceof Error ? err.message : String(err)}`);
436
+ }
437
+ }
438
+ }
439
+ /** External signals (kill, CI cancel, closed terminal) must run teardown
440
+ * too — Ctrl+C only covers the raw-mode keypress path. */
441
+ function wireSignals(shell) {
442
+ process.once("SIGTERM", () => shell.exit(143));
443
+ process.once("SIGHUP", () => shell.exit(129));
444
+ process.once("SIGINT", () => shell.exit(130));
445
+ }
446
+ /** Non-TTY fallback: no mount, plain streaming — same handle shape. */
447
+ function plainShell(merged) {
448
+ const store = createLogStore({ passthrough: true });
449
+ const exitSubs = [];
450
+ let exiting = false;
451
+ const shell = {
452
+ isInteractive: false,
453
+ say: (text = "") => {
454
+ console.log(text);
455
+ },
456
+ store,
457
+ setStatus: () => {},
458
+ switchTab: () => {},
459
+ pushView: () => {},
460
+ popView: () => {},
461
+ onExit: (cb) => {
462
+ exitSubs.push(cb);
463
+ return () => {
464
+ const i = exitSubs.indexOf(cb);
465
+ if (i >= 0) exitSubs.splice(i, 1);
466
+ };
467
+ },
468
+ exit: (code = 0) => {
469
+ if (exiting) return;
470
+ exiting = true;
471
+ (async () => {
472
+ try {
473
+ await runTeardown(merged, exitSubs);
474
+ } finally {
475
+ process.exit(code);
476
+ }
477
+ })();
478
+ }
479
+ };
480
+ wireSignals(shell);
481
+ (async () => {
482
+ await runSetups(merged, shell);
483
+ await merged.onReady?.(shell);
484
+ })();
485
+ return shell;
486
+ }
487
+ //#endregion
488
+ //#region src/shell/logger.ts
489
+ /**
490
+ * Bridge an existing `Logger`-shaped code path (e.g. a dev server that takes
491
+ * `{ logger }`) into a running shell's transcript.
492
+ */
493
+ function createShellLogger(shell) {
494
+ return {
495
+ log: (msg) => shell.say(msg),
496
+ warn: (msg) => shell.say(paintToken(msg, "warn")),
497
+ error: (msg) => shell.say(paintToken(msg, "danger"))
498
+ };
499
+ }
500
+ //#endregion
501
+ export { collectTuiContributions, createShellLogger, mergeShellConfig, runShell };
502
+
503
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/shell/contributions.ts","../../src/shell/runShell.tsx","../../src/shell/logger.ts"],"sourcesContent":["import type { SigxPlugin, TuiContribution, StatusItem } from '../plugin.js';\nimport type { ShellConfig } from './types.js';\nimport type { Logger } from '../plugin.js';\n\n/** Collect the `tui` contributions of every plugin, in discovery order. */\nexport function collectTuiContributions(plugins: SigxPlugin[]): TuiContribution[] {\n return plugins\n .map((p) => p.tui)\n .filter((t): t is TuiContribution => !!t);\n}\n\n/**\n * Merge peer-plugin contributions into the host's config. Host entries come\n * first and win conflicts: a duplicate tab `id`, command `name`, or shortcut\n * `key` from a later contributor is skipped (with a warn when a logger is\n * given). `status` providers concatenate.\n */\nexport function mergeShellConfig(\n config: ShellConfig,\n contributions: TuiContribution[],\n logger?: Logger,\n): ShellConfig {\n const tabs = [...config.tabs];\n const commands = [...(config.commands ?? [])];\n const shortcuts = [...(config.shortcuts ?? [])];\n const statusProviders = config.status ? [config.status] : [];\n\n const tabIds = new Set(tabs.map((t) => t.id));\n const commandNames = new Set(commands.map((c) => c.name));\n const shortcutKeys = new Set(shortcuts.map((s) => s.key));\n\n for (const tui of contributions) {\n for (const tab of tui.tabs ?? []) {\n if (tabIds.has(tab.id)) {\n logger?.warn(`shell: duplicate tab \"${tab.id}\" skipped`);\n continue;\n }\n tabIds.add(tab.id);\n tabs.push(tab);\n }\n for (const cmd of tui.commands ?? []) {\n if (commandNames.has(cmd.name)) {\n logger?.warn(`shell: duplicate command \"${cmd.name}\" skipped`);\n continue;\n }\n commandNames.add(cmd.name);\n commands.push(cmd);\n }\n for (const sc of tui.shortcuts ?? []) {\n if (shortcutKeys.has(sc.key)) {\n logger?.warn(`shell: duplicate shortcut \"${sc.key}\" skipped`);\n continue;\n }\n shortcutKeys.add(sc.key);\n shortcuts.push(sc);\n }\n if (tui.status) statusProviders.push(tui.status);\n }\n\n return {\n ...config,\n tabs,\n commands,\n shortcuts,\n status: statusProviders.length\n ? () => statusProviders.flatMap((s): StatusItem[] => s())\n : undefined,\n };\n}\n","/** @jsxImportSource @sigx/terminal */\n/**\n * The persistent dev-shell runtime plugin commands host their dashboards in.\n *\n * Two layouts:\n *\n * - `mode: 'fullscreen'` (dashboards — k9s/showcase shape): alt-screen app\n * with a title bar, segmented tab strip, full-height tab body, and a\n * pinned status/hints line. `/` summons a command-palette overlay with\n * intellisense. `say()` streams into the log store (visible live in a\n * Logs tab) AND queues into the normal terminal buffer, so quitting\n * leaves a post-mortem trail in real scrollback.\n *\n * - `mode: 'inline'` (default — transcript shape): header printed once into\n * scrollback, permanent transcript via `say`, tabs + bottom-anchored\n * `/`-command input. The conversation IS the terminal scrollback.\n *\n * Both: plugin-contributed tabs/commands/shortcuts/status (mergeShellConfig),\n * single-key shortcuts, 1–9 tab switching, Esc-pop views, and Ctrl+C that\n * runs onExit cleanup BEFORE exiting. In non-TTY environments nothing\n * mounts — callers get a plain streaming handle with the same shape.\n */\nimport {\n defineApp, component, signal, onMounted, onUnmounted, terminalMount, exitTerminal,\n TextArea, SuggestionList, Tabs, Divider, KeyHints, Text, Col, Spacer, Box, StatusBar,\n renderPixelArt, createViewStack, onKey, isEsc, printStatic, paintToken,\n getTerminalSize, layoutText, createLogStore, setTheme, listThemes,\n} from '@sigx/terminal';\nimport type { ShellHandle, SlashCommand, StatusItem } from '../plugin.js';\nimport type { ShellConfig } from './types.js';\nimport { collectTuiContributions, mergeShellConfig } from './contributions.js';\n\nconst CTRL_C = String.fromCharCode(3);\nconst MAX_INPUT_ROWS = 6;\n\nexport async function runShell(\n config: ShellConfig,\n opts: { interactive?: boolean } = {},\n): Promise<ShellHandle> {\n const merged = mergeShellConfig(\n config,\n collectTuiContributions(config.plugins ?? []),\n );\n const fullscreen = merged.mode === 'fullscreen';\n\n const interactive = opts.interactive ?? (!!process.stdout.isTTY && !!process.stdin.isTTY);\n if (!interactive) {\n return plainShell(merged);\n }\n\n // Design theme (the themed canvas paints the whole alt-screen from it).\n const theme = merged.theme ?? 'obsidian';\n if (listThemes().includes(theme)) setTheme(theme);\n\n const store = createLogStore();\n const status = signal({ items: [] as StatusItem[] });\n const transcript = signal({ lines: 0 });\n const tab = signal({ active: merged.tabs[0]?.id ?? '' });\n const views = createViewStack<string>('shell');\n\n const say = (text = '') => {\n if (fullscreen) {\n // Live: into the log store (a Logs tab shows it immediately).\n // Permanent: printStatic queues fullscreen statics and flushes\n // them into the normal buffer on exit — the post-mortem trail.\n store.push(text + '\\n');\n printStatic(text);\n return;\n }\n // ORDER MATTERS: bump the counter first — the signal write re-renders\n // synchronously, so printStatic's immediate repaint uses the shrunk\n // filler and the bottom anchor never overflows the viewport.\n transcript.lines += text.split('\\n').length;\n printStatic(text);\n };\n\n const exitSubs: Array<() => void | Promise<void>> = [];\n let exiting = false;\n const shell: ShellHandle = {\n isInteractive: true,\n say,\n store,\n setStatus: (items) => { status.items = items; },\n switchTab: (id) => {\n if (merged.tabs.some((t) => t.id === id)) tab.active = id;\n },\n pushView: (id) => views.push(id),\n popView: () => views.pop(),\n onExit: (cb) => {\n exitSubs.push(cb);\n return () => {\n const i = exitSubs.indexOf(cb);\n if (i >= 0) exitSubs.splice(i, 1);\n };\n },\n exit: (code = 0) => {\n if (exiting) return;\n exiting = true;\n void (async () => {\n try {\n await runTeardown(merged, exitSubs);\n } finally {\n exitTerminal();\n process.exit(code);\n }\n })();\n },\n };\n wireSignals(shell);\n\n const builtins: SlashCommand[] = [\n {\n name: '/help',\n description: 'list available commands',\n run: () => {\n say('');\n for (const c of allCommands()) {\n say(` ${paintToken(c.name, 'accent')} ${paintToken(c.description, 'dim')}`);\n }\n },\n },\n { name: '/quit', description: 'exit', run: () => shell.exit(0) },\n ];\n const allCommands = (): SlashCommand[] => [...(merged.commands ?? []), ...builtins];\n\n const runCommand = async (name: string) => {\n const cmd = allCommands().find((c) => c.name === name);\n if (!cmd) {\n say(paintToken(`unknown command ${name} — try /help`, 'dim'));\n return;\n }\n try {\n await cmd.run(shell);\n } catch (err) {\n say(paintToken(`${name} failed: ${err instanceof Error ? err.message : String(err)}`, 'danger'));\n }\n };\n\n const ShellApp = component(() => {\n const input = signal({ value: '' });\n // Fullscreen: the command input is a summoned palette, not permanent\n // chrome. `/` opens it; Esc / submit closes it.\n const palette = signal({ open: false });\n const offs: Array<() => void> = [];\n\n const inputActive = () => (fullscreen ? palette.open : input.value !== '');\n\n const closePalette = () => {\n palette.open = false;\n input.value = '';\n };\n\n const submit = (text: string) => {\n const trimmed = text.trim();\n input.value = '';\n if (fullscreen) palette.open = false;\n if (!trimmed) return;\n if (trimmed.startsWith('/')) {\n void runCommand(trimmed.split(/\\s/)[0]);\n return;\n }\n say(paintToken(`type / for commands`, 'dim'));\n };\n\n onMounted(() => {\n if (!fullscreen) {\n // Header — once, into scrollback. (Fullscreen renders the\n // title bar inside the frame instead.)\n if (merged.logo) say(renderPixelArt(merged.logo.rows, merged.logo.palette).join('\\n'));\n say(`${paintToken(merged.title, 'accent')}${merged.version ? ` ${paintToken(merged.version, 'dim')}` : ''}`);\n say('');\n }\n\n // Esc: close the palette first; then pop pushed views.\n offs.push(onKey((key) => {\n if (!isEsc(key)) return;\n if (fullscreen && palette.open) {\n closePalette();\n return true;\n }\n if (views.depth() > 1) {\n views.pop();\n return true;\n }\n }, { layer: 'view' }));\n\n // `/` summons the palette (fullscreen); single-key shortcuts +\n // 1–9 tab switching. Overlay layer so they run BEFORE the\n // TextArea — inactive whenever the input owns the keyboard.\n offs.push(onKey((key) => {\n if (inputActive()) return;\n if (fullscreen && key === '/') {\n palette.open = true;\n return true;\n }\n const digit = key.length === 1 ? key.charCodeAt(0) - 48 : 0;\n if (digit >= 1 && digit <= Math.min(9, merged.tabs.length)) {\n tab.active = merged.tabs[digit - 1].id;\n return true;\n }\n const sc = (merged.shortcuts ?? []).find((s) => s.key === key);\n if (sc) {\n void Promise.resolve(sc.run(shell)).catch((err) => {\n say(paintToken(`${sc.label} failed: ${err instanceof Error ? err.message : String(err)}`, 'danger'));\n });\n return true;\n }\n }, { layer: 'overlay' }));\n\n // Ctrl+C → cleanup, then exit (mount has exitOnCtrlC: false).\n offs.push(onKey((key) => {\n if (key === CTRL_C) {\n shell.exit(130);\n return true;\n }\n }, { layer: 'global' }));\n });\n onUnmounted(() => { for (const off of offs) off(); });\n\n const suggestionsFor = (value: string) => (value.startsWith('/')\n ? allCommands()\n .filter((c) => c.name.startsWith(value.trim().split(/\\s/)[0]))\n .map((c) => ({ value: c.name, label: c.name, description: c.description }))\n : []);\n\n const statusLine = () => {\n const items = [...(merged.status?.() ?? []), ...status.items];\n if (items.length === 0) return null;\n return (\n <box>\n {items.flatMap((s, i) => [\n ...(i > 0 ? [<Text color=\"dim\"> · </Text>] : []),\n <Text color=\"dim\">{s.label} </Text>,\n <Text color={s.tone ?? 'fg'}>{s.value}</Text>,\n ])}\n </box>\n );\n };\n\n const resolveTab = () => {\n const viewId = views.current();\n return viewId !== 'shell'\n ? merged.tabs.find((t) => t.id === viewId)\n : merged.tabs.find((t) => t.id === tab.active);\n };\n\n const hintList = () => [\n ...(merged.shortcuts ?? []).map((s) => ({ key: s.key, label: s.label })),\n { key: '/', label: 'commands' },\n { key: '^C', label: 'quit' },\n ];\n\n // ── Fullscreen — the showcase recipe verbatim: bordered title bar,\n // segmented filled tab strip, labeled rounded panel (drop shadow)\n // around the body, chip-style StatusBar; `/` palette swaps in\n // over the bottom chrome.\n const renderFullscreen = () => {\n const { columns: cols } = getTerminalSize();\n const activeTab = resolveTab();\n const suggestions = suggestionsFor(input.value);\n\n // Segmented strip — filled chips, the showcase look (NOT the\n // bordered Tabs widget).\n const strip = merged.tabs.map((t, i) => {\n const on = t.id === tab.active;\n return (\n <Text bg={on ? 'accent' : 'accentSoft'} color={on ? 'accentText' : 'dim'}>\n {' '}{String(i + 1)} {t.label}{' '}\n </Text>\n );\n });\n\n const statusItems = [...(merged.status?.() ?? []), ...status.items];\n const barItems = [\n // Status reads as chips too: value chip + dim label.\n ...statusItems.map((s) => ({ key: s.value, label: s.label })),\n ...(merged.shortcuts ?? []).map((s) => ({ key: s.key, label: s.label })),\n ...(merged.tabs.length > 1 ? [{ key: `1-${merged.tabs.length}`, label: 'tabs' }] : []),\n { key: '/', label: 'commands' },\n { key: '^C', label: 'quit' },\n ];\n\n return (\n <Col>\n <Box border=\"thick\" borderColor=\"accent\" padX={1}>\n <Text color=\"accent\" bold>{merged.title}</Text>\n {merged.version ? <Text color=\"dim\">{` ${merged.version}`}</Text> : null}\n </Box>\n {merged.tabs.length > 1 && <box>{strip}</box>}\n <Spacer size={1} />\n {activeTab ? (\n <Box border=\"rounded\" borderColor=\"line\" label={activeTab.label} labelColor=\"accent\" padX={1} dropShadow={true}>\n {activeTab.render() as never}\n </Box>\n ) : null}\n <Spacer size={1} />\n {palette.open ? (\n <Col>\n <Divider width={Math.min(cols, 120)} label=\"command\" />\n <TextArea\n autofocus\n model={() => input.value}\n placeholder=\"/command\"\n maxRows={2}\n onSubmit={submit}\n />\n {suggestions.length > 0 && <SuggestionList\n items={suggestions}\n onAccept={(name: string) => { closePalette(); void runCommand(name); }}\n onDismiss={closePalette}\n />}\n </Col>\n ) : (\n <StatusBar items={barItems} />\n )}\n </Col>\n );\n };\n\n // ── Inline (transcript shape): bottom-anchored input + tabs.\n const renderInline = () => {\n const { columns: cols, rows } = getTerminalSize();\n const innerWidth = Math.max(4, Math.max(20, cols - 4) - 2);\n const inputRows = Math.min(MAX_INPUT_ROWS, layoutText(input.value, innerWidth).rows.length);\n const suggestions = suggestionsFor(input.value);\n const viewId = views.current();\n const activeTab = resolveTab();\n\n const chrome = 1 /* divider */ + inputRows + suggestions.length + 1 /* hints */;\n const bodyLines = 1 /* status */ + 1 /* tabs */ + 16 /* nominal body */;\n const filler = Math.max(0, rows - transcript.lines - bodyLines - chrome - 1);\n\n return (\n <Col>\n {statusLine()}\n {viewId === 'shell' && merged.tabs.length > 1 && (\n <Tabs\n options={merged.tabs.map((t) => ({ value: t.id, label: t.label }))}\n model={() => tab.active}\n onChange={(id: string) => { tab.active = id; }}\n />\n )}\n {activeTab ? (activeTab.render() as never) : null}\n {filler > 0 && <Spacer size={filler} />}\n <Divider width={Math.min(cols, 120)} />\n <TextArea\n autofocus\n model={() => input.value}\n placeholder=\"/ for commands\"\n maxRows={MAX_INPUT_ROWS}\n onSubmit={submit}\n />\n {suggestions.length > 0 && <SuggestionList\n items={suggestions}\n onAccept={(name: string) => { input.value = ''; void runCommand(name); }}\n onDismiss={() => { input.value = ''; }}\n />}\n <KeyHints hints={hintList()} />\n </Col>\n );\n };\n\n return () => (fullscreen ? renderFullscreen() : renderInline());\n }, { name: 'ShellApp' });\n\n defineApp(<ShellApp />).mount(\n fullscreen\n ? { mode: 'fullscreen', exitOnCtrlC: false }\n : { mode: 'inline', clearConsole: true, exitOnCtrlC: false },\n terminalMount,\n );\n\n await runSetups(merged, shell);\n await merged.onReady?.(shell);\n return shell;\n}\n\n/** Host onExit first, then subscriber teardowns most-recent-first; failures\n * in one never block the rest. */\nasync function runTeardown(merged: ShellConfig, subs: Array<() => void | Promise<void>>): Promise<void> {\n try {\n await merged.onExit?.();\n } catch { /* teardown is best-effort */ }\n for (const cb of [...subs].reverse()) {\n try {\n await cb();\n } catch { /* teardown is best-effort */ }\n }\n}\n\n/** Run contributed `setup` lifecycles; returned functions become teardowns. */\nasync function runSetups(merged: ShellConfig, shell: ShellHandle): Promise<void> {\n for (const plugin of merged.plugins ?? []) {\n const setup = plugin.tui?.setup;\n if (!setup) continue;\n try {\n const teardown = await setup(shell);\n if (typeof teardown === 'function') shell.onExit(teardown);\n } catch (err) {\n shell.say(`plugin ${plugin.name} setup failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n}\n\n/** External signals (kill, CI cancel, closed terminal) must run teardown\n * too — Ctrl+C only covers the raw-mode keypress path. */\nfunction wireSignals(shell: ShellHandle): void {\n process.once('SIGTERM', () => shell.exit(143));\n process.once('SIGHUP', () => shell.exit(129));\n process.once('SIGINT', () => shell.exit(130));\n}\n\n/** Non-TTY fallback: no mount, plain streaming — same handle shape. */\nfunction plainShell(merged: ShellConfig): ShellHandle {\n const store = createLogStore({ passthrough: true });\n const exitSubs: Array<() => void | Promise<void>> = [];\n let exiting = false;\n const shell: ShellHandle = {\n isInteractive: false,\n say: (text = '') => { console.log(text); },\n store,\n setStatus: () => { },\n switchTab: () => { },\n pushView: () => { },\n popView: () => { },\n onExit: (cb) => {\n exitSubs.push(cb);\n return () => {\n const i = exitSubs.indexOf(cb);\n if (i >= 0) exitSubs.splice(i, 1);\n };\n },\n exit: (code = 0) => {\n if (exiting) return;\n exiting = true;\n void (async () => {\n try {\n await runTeardown(merged, exitSubs);\n } finally {\n process.exit(code);\n }\n })();\n },\n };\n wireSignals(shell);\n void (async () => {\n await runSetups(merged, shell);\n await merged.onReady?.(shell);\n })();\n return shell;\n}\n","import { paintToken } from '@sigx/terminal';\nimport type { Logger, ShellHandle } from '../plugin.js';\n\n/**\n * Bridge an existing `Logger`-shaped code path (e.g. a dev server that takes\n * `{ logger }`) into a running shell's transcript.\n */\nexport function createShellLogger(shell: ShellHandle): Logger {\n return {\n log: (msg: string) => shell.say(msg),\n warn: (msg: string) => shell.say(paintToken(msg, 'warn')),\n error: (msg: string) => shell.say(paintToken(msg, 'danger')),\n };\n}\n"],"mappings":"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"}
@@ -7,7 +7,6 @@ import {
7
7
  Badge,
8
8
  Toggle,
9
9
  Stats,
10
- Stat,
11
10
  Hero,
12
11
  Footer,
13
12
  } from "@sigx/daisyui";
@@ -73,9 +72,21 @@ const App = component(({ signal }) => {
73
72
  {/* Stats */}
74
73
  <div class="px-6 -mt-8 relative z-10 flex justify-center">
75
74
  <Stats class="w-full max-w-3xl bg-base-100">
76
- <Stat title="Counter" value={state.count} description="Reactive signal" />
77
- <Stat title="Doubled" value={state.count * 2} description="Computed value" />
78
- <Stat title="Uptime" value={formatTime(state.elapsed)} description="Live timer" />
75
+ <Stats.Item>
76
+ <Stats.Title>Counter</Stats.Title>
77
+ <Stats.Value>{state.count}</Stats.Value>
78
+ <Stats.Desc>Reactive signal</Stats.Desc>
79
+ </Stats.Item>
80
+ <Stats.Item>
81
+ <Stats.Title>Doubled</Stats.Title>
82
+ <Stats.Value>{state.count * 2}</Stats.Value>
83
+ <Stats.Desc>Computed value</Stats.Desc>
84
+ </Stats.Item>
85
+ <Stats.Item>
86
+ <Stats.Title>Uptime</Stats.Title>
87
+ <Stats.Value>{formatTime(state.elapsed)}</Stats.Value>
88
+ <Stats.Desc>Live timer</Stats.Desc>
89
+ </Stats.Item>
79
90
  </Stats>
80
91
  </div>
81
92
 
@@ -0,0 +1 @@
1
+ /// <reference types="@sigx/lynx/client" />
@@ -3,6 +3,6 @@ import App from './App';
3
3
 
4
4
  defineApp(<App />).mount(null);
5
5
 
6
- if ((module as any).hot) {
7
- (module as any).hot.accept();
6
+ if (module.hot) {
7
+ module.hot.accept();
8
8
  }
@@ -0,0 +1 @@
1
+ /// <reference types="@sigx/lynx/client" />
@@ -5,6 +5,6 @@ import App from './App';
5
5
 
6
6
  defineApp(<App />).mount(null);
7
7
 
8
- if ((module as any).hot) {
9
- (module as any).hot.accept();
8
+ if (module.hot) {
9
+ module.hot.accept();
10
10
  }
@@ -0,0 +1 @@
1
+ /// <reference types="@sigx/lynx/client" />
@@ -4,6 +4,6 @@ import App from './App';
4
4
 
5
5
  defineApp(<App />).mount(null);
6
6
 
7
- if ((module as any).hot) {
8
- (module as any).hot.accept();
7
+ if (module.hot) {
8
+ module.hot.accept();
9
9
  }
@@ -4,9 +4,9 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
7
- "dev": "ssg dev",
8
- "build": "ssg build",
9
- "preview": "ssg preview"
7
+ "dev": "sigx dev",
8
+ "build": "sigx build",
9
+ "preview": "sigx preview"
10
10
  },
11
11
  "dependencies": {
12
12
  "sigx": "latest",
@@ -15,6 +15,7 @@
15
15
  "@sigx/server-renderer": "latest"
16
16
  },
17
17
  "devDependencies": {
18
+ "@sigx/cli": "latest",
18
19
  "@sigx/vite": "latest",
19
20
  "vite": "^8.0.3",
20
21
  "typescript": "^5.9.3"