@mozole/cli 1.4.3 → 2.0.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 (253) hide show
  1. package/LICENSE +16 -4
  2. package/README.md +85 -112
  3. package/dist/cli.d.ts +3 -2
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +231 -181
  6. package/dist/cli.js.map +1 -1
  7. package/dist/commands/adopt.d.ts +5 -10
  8. package/dist/commands/adopt.d.ts.map +1 -1
  9. package/dist/commands/adopt.js +49 -849
  10. package/dist/commands/adopt.js.map +1 -1
  11. package/dist/commands/doctor.d.ts +1 -7
  12. package/dist/commands/doctor.d.ts.map +1 -1
  13. package/dist/commands/doctor.js +69 -377
  14. package/dist/commands/doctor.js.map +1 -1
  15. package/dist/commands/new.d.ts +8 -10
  16. package/dist/commands/new.d.ts.map +1 -1
  17. package/dist/commands/new.js +32 -197
  18. package/dist/commands/new.js.map +1 -1
  19. package/dist/commands/phase.d.ts +11 -0
  20. package/dist/commands/phase.d.ts.map +1 -0
  21. package/dist/commands/phase.js +106 -0
  22. package/dist/commands/phase.js.map +1 -0
  23. package/dist/commands/prototype.d.ts +6 -0
  24. package/dist/commands/prototype.d.ts.map +1 -0
  25. package/dist/commands/prototype.js +28 -0
  26. package/dist/commands/prototype.js.map +1 -0
  27. package/dist/commands/repomap.d.ts +6 -0
  28. package/dist/commands/repomap.d.ts.map +1 -0
  29. package/dist/commands/repomap.js +71 -0
  30. package/dist/commands/repomap.js.map +1 -0
  31. package/dist/commands/test.d.ts +6 -0
  32. package/dist/commands/test.d.ts.map +1 -0
  33. package/dist/commands/test.js +115 -0
  34. package/dist/commands/test.js.map +1 -0
  35. package/dist/commands/ui.d.ts +6 -41
  36. package/dist/commands/ui.d.ts.map +1 -1
  37. package/dist/commands/ui.js +132 -459
  38. package/dist/commands/ui.js.map +1 -1
  39. package/dist/commands/verify.d.ts +12 -7
  40. package/dist/commands/verify.d.ts.map +1 -1
  41. package/dist/commands/verify.js +180 -65
  42. package/dist/commands/verify.js.map +1 -1
  43. package/dist/qa/a11y.d.ts +24 -0
  44. package/dist/qa/a11y.d.ts.map +1 -0
  45. package/dist/qa/a11y.js +124 -0
  46. package/dist/qa/a11y.js.map +1 -0
  47. package/dist/qa/breakpoints.d.ts +16 -0
  48. package/dist/qa/breakpoints.d.ts.map +1 -0
  49. package/dist/qa/breakpoints.js +45 -0
  50. package/dist/qa/breakpoints.js.map +1 -0
  51. package/dist/qa/contract.d.ts +21 -0
  52. package/dist/qa/contract.d.ts.map +1 -0
  53. package/dist/qa/contract.js +116 -0
  54. package/dist/qa/contract.js.map +1 -0
  55. package/dist/qa/index.d.ts +6 -0
  56. package/dist/qa/index.d.ts.map +1 -0
  57. package/dist/qa/index.js +6 -0
  58. package/dist/qa/index.js.map +1 -0
  59. package/dist/qa/probe.d.ts +48 -0
  60. package/dist/qa/probe.d.ts.map +1 -0
  61. package/dist/qa/probe.js +206 -0
  62. package/dist/qa/probe.js.map +1 -0
  63. package/dist/qa/runner.d.ts +27 -33
  64. package/dist/qa/runner.d.ts.map +1 -1
  65. package/dist/qa/runner.js +286 -332
  66. package/dist/qa/runner.js.map +1 -1
  67. package/dist/scaffold/agents.d.ts +5 -0
  68. package/dist/scaffold/agents.d.ts.map +1 -0
  69. package/dist/scaffold/agents.js +84 -0
  70. package/dist/scaffold/agents.js.map +1 -0
  71. package/dist/scaffold/backend.d.ts +6 -8
  72. package/dist/scaffold/backend.d.ts.map +1 -1
  73. package/dist/scaffold/backend.js +318 -199
  74. package/dist/scaffold/backend.js.map +1 -1
  75. package/dist/scaffold/design.d.ts +4 -3
  76. package/dist/scaffold/design.d.ts.map +1 -1
  77. package/dist/scaffold/design.js +38 -47
  78. package/dist/scaffold/design.js.map +1 -1
  79. package/dist/scaffold/flagship.d.ts +8 -0
  80. package/dist/scaffold/flagship.d.ts.map +1 -0
  81. package/dist/scaffold/flagship.js +337 -0
  82. package/dist/scaffold/flagship.js.map +1 -0
  83. package/dist/scaffold/index.d.ts +9 -0
  84. package/dist/scaffold/index.d.ts.map +1 -0
  85. package/dist/scaffold/index.js +48 -0
  86. package/dist/scaffold/index.js.map +1 -0
  87. package/dist/scaffold/phases.d.ts +10 -0
  88. package/dist/scaffold/phases.d.ts.map +1 -0
  89. package/dist/scaffold/phases.js +165 -0
  90. package/dist/scaffold/phases.js.map +1 -0
  91. package/dist/scaffold/repomap.d.ts +6 -0
  92. package/dist/scaffold/repomap.d.ts.map +1 -0
  93. package/dist/scaffold/repomap.js +89 -0
  94. package/dist/scaffold/repomap.js.map +1 -0
  95. package/dist/scaffold/standard.d.ts +13 -0
  96. package/dist/scaffold/standard.d.ts.map +1 -0
  97. package/dist/scaffold/standard.js +371 -0
  98. package/dist/scaffold/standard.js.map +1 -0
  99. package/dist/scaffold/tokens.d.ts +3 -0
  100. package/dist/scaffold/tokens.d.ts.map +1 -0
  101. package/dist/scaffold/tokens.js +126 -0
  102. package/dist/scaffold/tokens.js.map +1 -0
  103. package/dist/server/manager.d.ts +23 -0
  104. package/dist/server/manager.d.ts.map +1 -0
  105. package/dist/server/manager.js +96 -0
  106. package/dist/server/manager.js.map +1 -0
  107. package/dist/ui/CockpitApp.d.ts +13 -0
  108. package/dist/ui/CockpitApp.d.ts.map +1 -0
  109. package/dist/ui/CockpitApp.js +292 -0
  110. package/dist/ui/CockpitApp.js.map +1 -0
  111. package/dist/utils/ai-trace.d.ts +8 -0
  112. package/dist/utils/ai-trace.d.ts.map +1 -0
  113. package/dist/utils/ai-trace.js +69 -0
  114. package/dist/utils/ai-trace.js.map +1 -0
  115. package/dist/utils/browser.d.ts +10 -0
  116. package/dist/utils/browser.d.ts.map +1 -0
  117. package/dist/utils/browser.js +127 -0
  118. package/dist/utils/browser.js.map +1 -0
  119. package/dist/utils/fs.d.ts +18 -0
  120. package/dist/utils/fs.d.ts.map +1 -0
  121. package/dist/utils/fs.js +296 -0
  122. package/dist/utils/fs.js.map +1 -0
  123. package/dist/utils/git.d.ts +9 -0
  124. package/dist/utils/git.d.ts.map +1 -0
  125. package/dist/utils/git.js +69 -0
  126. package/dist/utils/git.js.map +1 -0
  127. package/dist/utils/process.d.ts +21 -0
  128. package/dist/utils/process.d.ts.map +1 -0
  129. package/dist/utils/process.js +120 -0
  130. package/dist/utils/process.js.map +1 -0
  131. package/dist/version.d.ts +21 -36
  132. package/dist/version.d.ts.map +1 -1
  133. package/dist/version.js +21 -44
  134. package/dist/version.js.map +1 -1
  135. package/package.json +22 -38
  136. package/CHANGELOG.md +0 -38
  137. package/dist/commands/cce.d.ts +0 -7
  138. package/dist/commands/cce.d.ts.map +0 -1
  139. package/dist/commands/cce.js +0 -37
  140. package/dist/commands/cce.js.map +0 -1
  141. package/dist/commands/logs.d.ts +0 -3
  142. package/dist/commands/logs.d.ts.map +0 -1
  143. package/dist/commands/logs.js +0 -40
  144. package/dist/commands/logs.js.map +0 -1
  145. package/dist/commands/qa.d.ts +0 -12
  146. package/dist/commands/qa.d.ts.map +0 -1
  147. package/dist/commands/qa.js +0 -143
  148. package/dist/commands/qa.js.map +0 -1
  149. package/dist/commands/stats.d.ts +0 -37
  150. package/dist/commands/stats.d.ts.map +0 -1
  151. package/dist/commands/stats.js +0 -169
  152. package/dist/commands/stats.js.map +0 -1
  153. package/dist/commands/template.d.ts +0 -8
  154. package/dist/commands/template.d.ts.map +0 -1
  155. package/dist/commands/template.js +0 -392
  156. package/dist/commands/template.js.map +0 -1
  157. package/dist/config/project.d.ts +0 -54
  158. package/dist/config/project.d.ts.map +0 -1
  159. package/dist/config/project.js +0 -100
  160. package/dist/config/project.js.map +0 -1
  161. package/dist/context/cce.d.ts +0 -39
  162. package/dist/context/cce.d.ts.map +0 -1
  163. package/dist/context/cce.js +0 -455
  164. package/dist/context/cce.js.map +0 -1
  165. package/dist/governor/fingerprint.d.ts +0 -7
  166. package/dist/governor/fingerprint.d.ts.map +0 -1
  167. package/dist/governor/fingerprint.js +0 -120
  168. package/dist/governor/fingerprint.js.map +0 -1
  169. package/dist/governor/hook.d.ts +0 -13
  170. package/dist/governor/hook.d.ts.map +0 -1
  171. package/dist/governor/hook.js +0 -389
  172. package/dist/governor/hook.js.map +0 -1
  173. package/dist/governor/state.d.ts +0 -45
  174. package/dist/governor/state.d.ts.map +0 -1
  175. package/dist/governor/state.js +0 -60
  176. package/dist/governor/state.js.map +0 -1
  177. package/dist/integrations/antigravity.d.ts +0 -20
  178. package/dist/integrations/antigravity.d.ts.map +0 -1
  179. package/dist/integrations/antigravity.js +0 -153
  180. package/dist/integrations/antigravity.js.map +0 -1
  181. package/dist/output-safety.d.ts +0 -3
  182. package/dist/output-safety.d.ts.map +0 -1
  183. package/dist/output-safety.js +0 -44
  184. package/dist/output-safety.js.map +0 -1
  185. package/dist/output.d.ts +0 -5
  186. package/dist/output.d.ts.map +0 -1
  187. package/dist/output.js +0 -2
  188. package/dist/output.js.map +0 -1
  189. package/dist/platform/executable.d.ts +0 -11
  190. package/dist/platform/executable.d.ts.map +0 -1
  191. package/dist/platform/executable.js +0 -69
  192. package/dist/platform/executable.js.map +0 -1
  193. package/dist/platform/process.d.ts +0 -27
  194. package/dist/platform/process.d.ts.map +0 -1
  195. package/dist/platform/process.js +0 -189
  196. package/dist/platform/process.js.map +0 -1
  197. package/dist/progress.d.ts +0 -44
  198. package/dist/progress.d.ts.map +0 -1
  199. package/dist/progress.js +0 -123
  200. package/dist/progress.js.map +0 -1
  201. package/dist/qa/artifacts.d.ts +0 -3
  202. package/dist/qa/artifacts.d.ts.map +0 -1
  203. package/dist/qa/artifacts.js +0 -15
  204. package/dist/qa/artifacts.js.map +0 -1
  205. package/dist/qa/diff.d.ts +0 -10
  206. package/dist/qa/diff.d.ts.map +0 -1
  207. package/dist/qa/diff.js +0 -90
  208. package/dist/qa/diff.js.map +0 -1
  209. package/dist/qa/server.d.ts +0 -10
  210. package/dist/qa/server.d.ts.map +0 -1
  211. package/dist/qa/server.js +0 -127
  212. package/dist/qa/server.js.map +0 -1
  213. package/dist/scaffold/adoption.d.ts +0 -17
  214. package/dist/scaffold/adoption.d.ts.map +0 -1
  215. package/dist/scaffold/adoption.js +0 -279
  216. package/dist/scaffold/adoption.js.map +0 -1
  217. package/dist/scaffold/legacy.d.ts +0 -4
  218. package/dist/scaffold/legacy.d.ts.map +0 -1
  219. package/dist/scaffold/legacy.js +0 -139
  220. package/dist/scaffold/legacy.js.map +0 -1
  221. package/dist/scaffold/routes.d.ts +0 -3
  222. package/dist/scaffold/routes.d.ts.map +0 -1
  223. package/dist/scaffold/routes.js +0 -37
  224. package/dist/scaffold/routes.js.map +0 -1
  225. package/dist/scaffold/standalone.d.ts +0 -7
  226. package/dist/scaffold/standalone.d.ts.map +0 -1
  227. package/dist/scaffold/standalone.js +0 -374
  228. package/dist/scaffold/standalone.js.map +0 -1
  229. package/dist/scaffold/templates.d.ts +0 -9
  230. package/dist/scaffold/templates.d.ts.map +0 -1
  231. package/dist/scaffold/templates.js +0 -317
  232. package/dist/scaffold/templates.js.map +0 -1
  233. package/dist/scaffold/write.d.ts +0 -2
  234. package/dist/scaffold/write.d.ts.map +0 -1
  235. package/dist/scaffold/write.js +0 -38
  236. package/dist/scaffold/write.js.map +0 -1
  237. package/dist/telemetry/store.d.ts +0 -33
  238. package/dist/telemetry/store.d.ts.map +0 -1
  239. package/dist/telemetry/store.js +0 -75
  240. package/dist/telemetry/store.js.map +0 -1
  241. package/dist/template/servers.d.ts +0 -21
  242. package/dist/template/servers.d.ts.map +0 -1
  243. package/dist/template/servers.js +0 -193
  244. package/dist/template/servers.js.map +0 -1
  245. package/dist/template/workspace.d.ts +0 -23
  246. package/dist/template/workspace.d.ts.map +0 -1
  247. package/dist/template/workspace.js +0 -152
  248. package/dist/template/workspace.js.map +0 -1
  249. package/dist/verify/runner.d.ts +0 -16
  250. package/dist/verify/runner.d.ts.map +0 -1
  251. package/dist/verify/runner.js +0 -112
  252. package/dist/verify/runner.js.map +0 -1
  253. package/docs/agent-audit.md +0 -24
@@ -1,481 +1,154 @@
1
1
  import path from "node:path";
2
- import { stdin, stdout } from "node:process";
3
- import { createInterface } from "node:readline/promises";
4
- import { sanitizeTerminalText } from "../output-safety.js";
5
- import { renderProgressBar } from "../progress.js";
6
- import { TemplateServerManager, } from "../template/servers.js";
7
- import { findTemplateRoot } from "../template/workspace.js";
8
- import { CLI_VERSION } from "../version.js";
9
- import { adoptCommand } from "./adopt.js";
10
- import { cceInstallCommand } from "./cce.js";
2
+ import { render } from "ink";
3
+ import pc from "picocolors";
4
+ import prompts from "prompts";
5
+ import React from "react";
6
+ import { serverManager } from "../server/manager.js";
7
+ import { CockpitApp } from "../ui/CockpitApp.js";
8
+ import { discoverWorkspaceProjects, findProjectRoot, findPrototypeRoot, } from "../utils/fs.js";
11
9
  import { doctorCommand } from "./doctor.js";
12
- import { newCommand } from "./new.js";
13
- import { qaCommand } from "./qa.js";
14
- import { statsCommand } from "./stats.js";
15
- import { templateAdoptCommand } from "./template.js";
10
+ import { phaseCommand } from "./phase.js";
11
+ import { repomapCommand } from "./repomap.js";
16
12
  import { verifyCommand } from "./verify.js";
17
- const actions = [
18
- {
19
- label: "Doctor",
20
- detail: "Inspect project, toolchain & AI runtime health",
21
- key: "1",
22
- kind: "command",
23
- run: (cwd, output, opts) => doctorCommand(cwd, output, opts),
24
- },
25
- {
26
- label: "Verify",
27
- detail: "Run strict lint, typecheck, tests & build",
28
- key: "2",
29
- kind: "command",
30
- run: (cwd, output, opts) => verifyCommand(cwd, output, opts),
31
- },
32
- {
33
- label: "QA",
34
- detail: "Run deterministic, screenshot-free browser probes",
35
- key: "3",
36
- kind: "command",
37
- run: (cwd, output, opts) => qaCommand(cwd, {
38
- interactive: false,
39
- visual: false,
40
- onProgress: opts?.onProgress,
41
- }, output),
42
- },
43
- {
44
- label: "Stats",
45
- detail: "Inspect token savings & verification cache telemetry",
46
- key: "4",
47
- kind: "command",
48
- run: (cwd, output, opts) => statsCommand(cwd, output, opts),
49
- },
50
- {
51
- label: "Install CCE",
52
- detail: "Provision persistent uv & code-context-engine runtime",
53
- key: "5",
54
- kind: "install",
55
- },
56
- {
57
- label: "New project",
58
- detail: "Scaffold a static-first production-ready application",
59
- key: "6",
60
- kind: "create",
61
- },
62
- {
63
- label: "Adopt project",
64
- detail: "Integrate Mozole toolchain into an existing app",
65
- key: "7",
66
- kind: "adopt",
67
- },
68
- {
69
- label: "Servers",
70
- detail: "Start & stop template project npm servers",
71
- key: "8",
72
- kind: "servers",
73
- },
74
- {
75
- label: "Exit",
76
- detail: "Return to your shell",
77
- key: "9",
78
- kind: "exit",
79
- },
80
- ];
81
- const ansi = {
82
- alternateEnter: "\u001B[?1049h",
83
- alternateExit: "\u001B[?1049l",
84
- clear: "\u001B[2J\u001B[H",
85
- cursorHide: "\u001B[?25l",
86
- cursorShow: "\u001B[?25h",
87
- reset: "\u001B[0m",
88
- };
89
- const ansiSequence = new RegExp(`${String.fromCharCode(27)}\\[[0-?]*[ -/]*[@-~]`, "g");
90
- function paint(value, code, color) {
91
- return color ? `\u001B[${code}m${value}${ansi.reset}` : value;
92
- }
93
- function clamp(value, width) {
94
- if (width < 2)
95
- return "";
96
- const plain = value.replace(ansiSequence, "");
97
- return plain.length <= width
98
- ? `${value}${" ".repeat(width - plain.length)}`
99
- : `${plain.slice(0, Math.max(0, width - 1))}…`;
100
- }
101
- function box(lines, width, color, sectionTitle) {
102
- const inner = Math.max(10, width - 2);
103
- let topBorder = "─".repeat(inner);
104
- if (sectionTitle) {
105
- const titleText = ` ${sectionTitle} `;
106
- if (titleText.length < inner - 4) {
107
- topBorder = `──${titleText}${"─".repeat(inner - 2 - titleText.length)}`;
108
- }
109
- }
110
- const top = `${paint("╭", 90, color)}${paint(topBorder, 90, color)}${paint("╮", 90, color)}`;
111
- const bottom = `${paint("╰", 90, color)}${paint("─".repeat(inner), 90, color)}${paint("╯", 90, color)}`;
112
- return [
113
- top,
114
- ...lines.map((line) => `${paint("│", 90, color)} ${clamp(line, inner - 2)} ${paint("│", 90, color)}`),
115
- bottom,
116
- ];
117
- }
118
- export function uiMenuText() {
119
- return actions.map((action) => `${action.key} ${action.label}`).join("\n");
120
- }
121
- export function uiDashboardFrame(state, dimensions, color = true) {
122
- const width = Math.max(54, Math.min(100, dimensions.columns - 2));
123
- const height = Math.max(20, dimensions.rows);
124
- const title = paint("Mozole-cli", 96, color);
125
- const version = paint(`v${CLI_VERSION}`, 90, color);
126
- const subtitle = paint("COMMAND DECK", 95, color);
127
- const copyright = paint("© MozoleStudio", 90, color);
128
- const projectLabel = paint("PROJECT", 36, color);
129
- const projectName = paint(sanitizeTerminalText(path.basename(state.cwd) || state.cwd), 97, color);
130
- const header = [
131
- ` ${paint("◆", 96, color)} ${title} ${version} ${paint("│", 90, color)} ${subtitle} ${paint("│", 90, color)} ${copyright}`,
132
- ` ${projectLabel} ${projectName}`,
133
- ];
134
- const actionLines = actions.map((action, index) => {
135
- const active = state.selected === index;
136
- const marker = active ? paint("❯", 96, color) : paint("·", 90, color);
137
- const key = paint(`[${action.key}]`, active ? 97 : 36, color);
138
- const label = paint(action.label.padEnd(14), active ? 97 : 37, color);
139
- const detail = paint(action.detail, active ? 96 : 90, color);
140
- return `${marker} ${key} ${label} ${detail}`;
141
- });
142
- let statusBadge = paint("● READY Select an operation", 92, color);
143
- let progressLine;
144
- if (state.busy) {
145
- statusBadge = paint(`⟳ RUNNING ${state.busy}`, 93, color);
146
- if (state.progress) {
147
- const bar = renderProgressBar(state.progress.current / state.progress.total, 14);
148
- const stepInfo = paint(`[${state.progress.current}/${state.progress.total}]`, 97, color);
149
- const msg = paint(sanitizeTerminalText(state.progress.message), 90, color);
150
- progressLine = ` ${paint(bar, 36, color)} ${stepInfo} ${msg}`;
151
- }
13
+ export async function uiCommand(options = {}) {
14
+ const cwd = options.cwd ?? process.cwd();
15
+ const protoRoot = await findPrototypeRoot(cwd);
16
+ let projectRoot = await findProjectRoot(cwd);
17
+ if (projectRoot === protoRoot)
18
+ projectRoot = null;
19
+ const discoveredProjects = protoRoot
20
+ ? await discoverWorkspaceProjects(protoRoot)
21
+ : [];
22
+ let activeProject = options.project ?? null;
23
+ if (!activeProject && projectRoot) {
24
+ const matched = discoveredProjects.find((p) => p.path === projectRoot);
25
+ activeProject = matched ? matched.name : path.basename(projectRoot);
152
26
  }
153
- else if (state.lastResult) {
154
- if (state.lastResult.includes("COMPLETE")) {
155
- statusBadge = paint(`✓ ${state.lastResult}`, 92, color);
27
+ let initialTab = "overview";
28
+ // If in prototype / monorepo workspace and no project specified
29
+ if (!projectRoot && protoRoot && discoveredProjects.length > 0) {
30
+ if (options.project) {
31
+ const matched = discoveredProjects.find((p) => p.name === options.project ||
32
+ p.relative === options.project ||
33
+ path.basename(p.path) === options.project);
34
+ if (matched) {
35
+ activeProject = matched.name;
36
+ projectRoot = matched.path;
37
+ }
38
+ else {
39
+ activeProject = options.project;
40
+ projectRoot = path.join(protoRoot, options.project);
41
+ }
156
42
  }
157
- else if (state.lastResult.includes("FAILED")) {
158
- statusBadge = paint(`✗ ${state.lastResult}`, 91, color);
43
+ else if (process.env.VITEST || options.interactive === false) {
44
+ // In automated tests or headless programmatic mode, prompt for selection if available
45
+ const choice = await prompts({
46
+ type: "select",
47
+ name: "project",
48
+ message: "Select an active client project:",
49
+ choices: discoveredProjects.map((p) => ({ title: p.name, value: p.name })),
50
+ });
51
+ if (choice?.project) {
52
+ const found = discoveredProjects.find((p) => p.name === choice.project);
53
+ activeProject = choice.project;
54
+ projectRoot = found ? found.path : path.join(protoRoot, choice.project);
55
+ }
56
+ else {
57
+ activeProject = discoveredProjects[0].name;
58
+ projectRoot = discoveredProjects[0].path;
59
+ }
159
60
  }
160
61
  else {
161
- statusBadge = paint(state.lastResult, 97, color);
62
+ // In interactive terminal at monorepo root: default to first project and open projects explorer tab!
63
+ activeProject = discoveredProjects[0].name;
64
+ projectRoot = discoveredProjects[0].path;
65
+ initialTab = "projects";
162
66
  }
163
67
  }
164
- const activity = state.log.length
165
- ? state.log.slice(-4).map((l) => paint(` ${l}`, 90, color))
166
- : [paint(" No operation has run in this session.", 90, color)];
167
- const frame = [
168
- "",
169
- ...header,
170
- "",
171
- ...box(actionLines, width, color, "OPERATIONS"),
172
- "",
173
- ...box([
174
- ` ${statusBadge}`,
175
- ...(progressLine ? ["", progressLine] : []),
176
- "",
177
- ...activity,
178
- ], width, color, "STATUS & ACTIVITY"),
179
- "",
180
- paint(" ↑↓ / j k navigate enter run 1–9 jump r redraw q exit", 90, color),
181
- paint(" Normal QA is deterministic and screenshot-free. Visual QA remains explicit. · © MozoleStudio", 90, color),
182
- ];
183
- const padding = Math.max(0, height - frame.length - 1);
184
- return `${ansi.clear}${frame.join("\n")}${"\n".repeat(padding)}`;
185
- }
186
- export function uiServerManagerFrame(root, servers, selected, dimensions, color = true, message = "") {
187
- const width = Math.max(54, Math.min(100, dimensions.columns - 2));
188
- const height = Math.max(20, dimensions.rows);
189
- const rows = servers.length
190
- ? servers.map((server, index) => {
191
- const marker = selected === index ? paint("❯", 96, color) : paint("·", 90, color);
192
- const statusCode = server.status === "running"
193
- ? 92
194
- : server.status === "failed"
195
- ? 91
196
- : server.status === "starting"
197
- ? 93
198
- : 90;
199
- const status = paint(server.status.toUpperCase().padEnd(8), statusCode, color);
200
- const address = server.status === "running"
201
- ? `http://127.0.0.1:${server.port}`
202
- : `preferred :${server.preferredPort}`;
203
- return `${marker} ${status} ${sanitizeTerminalText(server.relativePath).padEnd(22)} ${address}`;
204
- })
205
- : [paint(" No projects found. Run `mozole new <name>` here.", 90, color)];
206
- const frame = [
207
- "",
208
- ` ${paint("◆", 96, color)} ${paint("NPM SERVER MANAGER", 96, color)}`,
209
- ` ${paint("TEMPLATE", 36, color)} ${paint(sanitizeTerminalText(path.basename(root)), 97, color)}`,
210
- "",
211
- ...box(rows, width, color, "PROJECTS"),
212
- "",
213
- ...box([
214
- ` ${sanitizeTerminalText(message || "Select a project and press Enter to start or stop it.")}`,
215
- ], width, color, "STATUS"),
216
- "",
217
- paint(" ↑↓ / j k navigate enter / s start-stop a start all x stop all q / esc back", 90, color),
218
- paint(" Servers live for this UI session. Logs: .mozole/logs/servers/", 90, color),
219
- ];
220
- return `${ansi.clear}${frame.join("\n")}${"\n".repeat(Math.max(0, height - frame.length - 1))}`;
221
- }
222
- function hasColor(output) {
223
- return (Boolean(output.isTTY) &&
224
- !process.env.NO_COLOR &&
225
- process.env.TERM !== "dumb");
226
- }
227
- function prompt(input, output, question) {
228
- const readline = createInterface({ input, output });
229
- return readline
230
- .question(question)
231
- .then((answer) => answer.trim())
232
- .catch(() => undefined)
233
- .finally(() => readline.close());
234
- }
235
- function waitForKey(input) {
236
- return new Promise((resolve) => {
237
- input.once("data", (chunk) => resolve(String(chunk)));
238
- });
239
- }
240
- function statusLine(message) {
241
- return sanitizeTerminalText(String(message))
242
- .split(/\r?\n/)
243
- .map((line) => line.trim())
244
- .filter(Boolean)
245
- .slice(-5);
246
- }
247
- export async function uiCommand(cwd, output = console, streams = {}) {
248
- const input = streams.input ?? stdin;
249
- const terminalOutput = streams.output ?? stdout;
250
- const interactive = streams.interactive ?? Boolean(input.isTTY && terminalOutput.isTTY);
251
- if (!interactive || !input.setRawMode) {
252
- output.error("UI requires an interactive terminal with ANSI support. Use `mozole doctor`, `mozole verify`, `mozole qa`, or `mozole stats` in CI.");
253
- return 2;
68
+ const targetPath = projectRoot ?? cwd;
69
+ const projectName = activeProject ?? path.basename(targetPath);
70
+ // If programmatic action was requested (or prompted during non-interactive test)
71
+ let requestedAction = options.action;
72
+ if (!requestedAction && (process.env.VITEST || options.interactive === false)) {
73
+ const promptRes = await prompts({
74
+ type: "select",
75
+ name: "action",
76
+ message: "Select operation:",
77
+ choices: [
78
+ { title: "[01] Phase Status", value: "phase-status" },
79
+ { title: "[02] Phase Next", value: "phase-next" },
80
+ { title: "[03] Verify", value: "verify" },
81
+ { title: "[04] Server Start", value: "server-start" },
82
+ { title: "[05] Server Stop", value: "server-stop" },
83
+ { title: "[06] Repomap", value: "repomap" },
84
+ { title: "[07] Doctor", value: "doctor" },
85
+ ],
86
+ });
87
+ requestedAction = promptRes?.action;
254
88
  }
255
- const state = { cwd, selected: 0, log: [] };
256
- const directOutput = {
257
- log: (message, ...optional) => output.log(sanitizeTerminalText(String(message ?? "")), ...optional.map((value) => sanitizeTerminalText(String(value ?? "")))),
258
- error: (message, ...optional) => output.error(sanitizeTerminalText(String(message ?? "")), ...optional.map((value) => sanitizeTerminalText(String(value ?? "")))),
259
- };
260
- const color = hasColor(terminalOutput);
261
- let entered = false;
262
- let serverManager;
263
- let serverViewRender;
264
- let requestedExitCode;
265
- const render = () => terminalOutput.write(uiDashboardFrame(state, {
266
- columns: terminalOutput.columns ?? 100,
267
- rows: terminalOutput.rows ?? 32,
268
- }, color));
269
- const enter = () => {
270
- if (entered)
271
- return;
272
- terminalOutput.write(`${ansi.alternateEnter}${ansi.cursorHide}`);
273
- input.setRawMode?.(true);
274
- input.resume?.();
275
- entered = true;
276
- render();
277
- };
278
- const leave = () => {
279
- if (!entered)
280
- return;
281
- input.setRawMode?.(false);
282
- input.pause?.();
283
- terminalOutput.write(`${ansi.cursorShow}${ansi.alternateExit}`);
284
- entered = false;
285
- };
286
- const resize = () => (serverViewRender ?? render)();
287
- terminalOutput.on("resize", resize);
288
- const abort = () => leave();
289
- process.once("SIGINT", abort);
290
- process.once("SIGTERM", abort);
291
- const dashboardOutput = {
292
- log: (message) => {
293
- state.log = statusLine(message);
294
- },
295
- error: (message) => {
296
- state.log = statusLine(message);
297
- },
298
- };
299
- async function runExternal(action) {
300
- leave();
89
+ if (requestedAction) {
301
90
  try {
302
- if (action.kind === "install") {
303
- const code = await cceInstallCommand(cwd, directOutput);
304
- state.lastResult = `${action.label} ${code === 0 ? "COMPLETE" : "FAILED"}`;
91
+ switch (requestedAction) {
92
+ case "phase-status":
93
+ await phaseCommand({ action: "status", cwd: targetPath });
94
+ break;
95
+ case "phase-next":
96
+ await phaseCommand({ action: "next", cwd: targetPath });
97
+ break;
98
+ case "verify":
99
+ await verifyCommand({ cwd: targetPath });
100
+ break;
101
+ case "server-start":
102
+ await serverManager.startServer(projectName, targetPath);
103
+ break;
104
+ case "server-stop":
105
+ serverManager.stopServer(projectName);
106
+ break;
107
+ case "repomap":
108
+ await repomapCommand({ action: "sync", cwd: targetPath });
109
+ break;
110
+ case "doctor":
111
+ await doctorCommand();
112
+ break;
305
113
  }
306
- else {
307
- const question = action.kind === "create"
308
- ? "New project name: "
309
- : "Project path to adopt: ";
310
- const value = await prompt(input, terminalOutput, question);
311
- if (!value) {
312
- state.lastResult = `${action.label} CANCELLED`;
313
- return;
314
- }
315
- const code = action.kind === "create"
316
- ? await newCommand(value, cwd, directOutput)
317
- : (await findTemplateRoot(cwd))
318
- ? await templateAdoptCommand(value, cwd, directOutput)
319
- : await adoptCommand(value, cwd, directOutput);
320
- state.lastResult = `${action.label} ${code === 0 ? "COMPLETE" : "FAILED"}`;
321
- }
322
- await prompt(input, terminalOutput, "\nPress Enter to return to Mozole…");
323
- }
324
- finally {
325
- enter();
326
114
  }
327
- }
328
- async function runServerManager() {
329
- const root = await findTemplateRoot(cwd);
330
- if (!root) {
331
- state.lastResult = "Servers FAILED: no template workspace";
332
- state.log = ["Run `mozole template init <folder>` first."];
333
- render();
334
- return;
335
- }
336
- serverManager ??= new TemplateServerManager(root);
337
- let servers = await serverManager.refresh();
338
- let selected = 0;
339
- let message = "";
340
- const draw = () => terminalOutput.write(uiServerManagerFrame(root, servers, selected, {
341
- columns: terminalOutput.columns ?? 100,
342
- rows: terminalOutput.rows ?? 32,
343
- }, color, message));
344
- serverViewRender = draw;
345
- draw();
346
- while (true) {
347
- const key = await waitForKey(input);
348
- if (key === "\u0003") {
349
- requestedExitCode = 130;
350
- serverViewRender = undefined;
351
- return;
352
- }
353
- if (key === "q" || key === "Q" || key === "\u001B") {
354
- state.lastResult = `${servers.filter((item) => item.status === "running").length} SERVER(S) RUNNING`;
355
- serverViewRender = undefined;
356
- render();
357
- return;
115
+ catch (err) {
116
+ console.log(pc.red("\n┌─ OPERATION ERROR ──────────────────────────────────────────┐"));
117
+ const errMsg = err instanceof Error ? err.message : String(err);
118
+ for (const line of errMsg.split("\n")) {
119
+ console.log(pc.red(`│ [!] ${line}`));
358
120
  }
359
- if (key === "\u001B[A" || key === "k") {
360
- if (servers.length)
361
- selected = (selected + servers.length - 1) % servers.length;
362
- draw();
363
- continue;
364
- }
365
- if (key === "\u001B[B" || key === "j") {
366
- if (servers.length)
367
- selected = (selected + 1) % servers.length;
368
- draw();
369
- continue;
370
- }
371
- if (key === "x" || key === "X") {
372
- message = "Stopping all project servers…";
373
- draw();
374
- await serverManager.stopAll();
375
- servers = serverManager.list();
376
- message = "All project servers stopped.";
377
- draw();
378
- continue;
379
- }
380
- if (key === "a" || key === "A") {
381
- for (const server of servers) {
382
- if (server.status === "running")
383
- continue;
384
- message = `Starting ${server.relativePath}…`;
385
- draw();
386
- await serverManager.start(server.relativePath);
387
- servers = serverManager.list();
388
- }
389
- message = "All available project servers started.";
390
- draw();
391
- continue;
392
- }
393
- if (key !== "s" && key !== "S" && key !== "\r" && key !== "\n")
394
- continue;
395
- const server = servers[selected];
396
- if (!server)
397
- continue;
398
- if (server.status === "running" || server.status === "starting") {
399
- message = `Stopping ${server.relativePath}…`;
400
- draw();
401
- await serverManager.stop(server.relativePath);
402
- }
403
- else {
404
- message = `Starting ${server.relativePath}…`;
405
- draw();
406
- await serverManager.start(server.relativePath);
407
- }
408
- servers = serverManager.list();
409
- const current = servers.find((candidate) => candidate.relativePath === server.relativePath);
410
- message = current?.error
411
- ? `${server.relativePath}: ${current.error}`
412
- : `${server.relativePath}: ${current?.status ?? "stopped"}`;
413
- draw();
121
+ console.log(pc.red("└────────────────────────────────────────────────────────────┘"));
414
122
  }
123
+ return;
415
124
  }
416
- try {
417
- enter();
418
- while (true) {
419
- const key = await waitForKey(input);
420
- if (key === "\u0003")
421
- return 130;
422
- if (key === "q" || key === "Q" || key === "\u001B")
423
- return 0;
424
- if (key === "\u001B[A" || key === "k") {
425
- state.selected = (state.selected + actions.length - 1) % actions.length;
426
- render();
427
- continue;
428
- }
429
- if (key === "\u001B[B" || key === "j") {
430
- state.selected = (state.selected + 1) % actions.length;
431
- render();
432
- continue;
433
- }
434
- if (key === "r") {
435
- render();
436
- continue;
437
- }
438
- const direct = actions.findIndex((action) => action.key === key);
439
- if (direct >= 0)
440
- state.selected = direct;
441
- if (key !== "\r" && key !== "\n" && direct < 0)
442
- continue;
443
- const action = actions[state.selected];
444
- if (!action)
445
- continue;
446
- if (action.kind === "exit")
447
- return 0;
448
- if (action.kind === "servers") {
449
- await runServerManager();
450
- if (requestedExitCode !== undefined)
451
- return requestedExitCode;
452
- continue;
453
- }
454
- if (action.kind !== "command") {
455
- await runExternal(action);
456
- continue;
457
- }
458
- state.busy = action.label;
459
- delete state.progress;
460
- render();
461
- const code = await action.run?.(cwd, dashboardOutput, {
462
- onProgress: (prog) => {
463
- state.progress = prog;
464
- render();
465
- },
466
- });
467
- delete state.busy;
468
- delete state.progress;
469
- state.lastResult = `${action.label} ${code === 0 ? "COMPLETE" : "FAILED"}`;
470
- render();
125
+ // Fullscreen Ink TUI Mode (Alternate Screen Buffer)
126
+ const isTTY = Boolean(process.stdout.isTTY);
127
+ if (isTTY) {
128
+ process.stdout.write("\x1b[?1049h\x1b[H");
129
+ }
130
+ const restore = () => {
131
+ if (isTTY) {
132
+ process.stdout.write("\x1b[?1049l\x1b[?25h");
471
133
  }
134
+ };
135
+ process.once("SIGINT", restore);
136
+ process.once("SIGTERM", restore);
137
+ try {
138
+ const instance = render(React.createElement(CockpitApp, {
139
+ projectRoot: targetPath,
140
+ activeProject: projectName,
141
+ protoRoot,
142
+ projects: discoveredProjects,
143
+ initialTab,
144
+ onExit: restore,
145
+ }));
146
+ await instance.waitUntilExit();
472
147
  }
473
148
  finally {
474
- await serverManager?.stopAll();
475
- leave();
476
- terminalOutput.off("resize", resize);
477
- process.removeListener("SIGINT", abort);
478
- process.removeListener("SIGTERM", abort);
149
+ restore();
150
+ process.removeListener("SIGINT", restore);
151
+ process.removeListener("SIGTERM", restore);
479
152
  }
480
153
  }
481
154
  //# sourceMappingURL=ui.js.map