project-compass 2.7.0 โ 2.8.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/README.md +9 -16
- package/package.json +1 -1
- package/src/cli.js +171 -530
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Project Compass (v2.
|
|
1
|
+
# Project Compass (v2.8.0)
|
|
2
2
|
|
|
3
3
|
Project Compass is a futuristic CLI navigator built with [Ink](https://github.com/vadimdemedes/ink) that scans your current folder tree for familiar code projects and gives you one-keystroke access to build, test, or run them.
|
|
4
4
|
|
|
@@ -9,6 +9,7 @@ Project Compass is a futuristic CLI navigator built with [Ink](https://github.co
|
|
|
9
9
|
- ๐ **New Keyboard-Centric UX**: Shortcuts now use **Shift** instead of Ctrl to avoid terminal interference.
|
|
10
10
|
- ๐ก **Refined Output**: Improved stdin buffer with proper spacing and reliable scrolling (Shift+โ/โ).
|
|
11
11
|
- ๐ง **Smart Detection**: Support for 20+ frameworks including **Spring Boot** (Maven/Gradle), **ASP.NET Core**, **Rocket/Actix** (Rust), **Laravel** (PHP), **Vite**, **Prisma**, and more.
|
|
12
|
+
- ๐ฐ๏ธ **Orbit Task Manager**: Run commands and **Detach** (**Shift+D**) them to the background. Manage multiple processes via **Shift+T**.
|
|
12
13
|
- โ ๏ธ **Runtime Health**: Automatically checks if the required language/runtime (e.g., `node`, `python`, `cargo`) is installed and warns you if it's missing.
|
|
13
14
|
- ๐ **Omni-Studio**: A new interactive environment intelligence mode to see all installed runtimes and versions.
|
|
14
15
|
- ๐ **Log Management**: Clear output with **Shift+X** or export logs to a text file with **Shift+E**.
|
|
@@ -33,11 +34,13 @@ project-compass [--dir /path/to/workspace] [--studio]
|
|
|
33
34
|
| โ / โ | Move focus, **Enter**: toggle details |
|
|
34
35
|
| B / T / R | Build / Test / Run |
|
|
35
36
|
| 1โ9 | Execute numbered detail commands |
|
|
37
|
+
| **Shift+T** | Open **Orbit Task Manager** |
|
|
38
|
+
| **Shift+D** | **Detach** from active task (runs in background) |
|
|
36
39
|
| **Shift+A** | Open **Omni-Studio** (Environment View) |
|
|
37
40
|
| **Shift+C** | Add a custom command (`label|cmd`) |
|
|
38
41
|
| **Shift+X** | **Clear output logs** |
|
|
39
42
|
| **Shift+E** | **Export logs to .txt** |
|
|
40
|
-
| **Shift โ / โ** | Scroll output buffer
|
|
43
|
+
| **Shift โ / โ** | Scroll output buffer |
|
|
41
44
|
| **Shift+L** | Rerun last command |
|
|
42
45
|
| **Shift+H** | Toggle help cards |
|
|
43
46
|
| **Shift+S** | Toggle structure guide |
|
|
@@ -45,23 +48,13 @@ project-compass [--dir /path/to/workspace] [--studio]
|
|
|
45
48
|
| ? | Toggle help overlay |
|
|
46
49
|
| Ctrl+C | Interrupt running command |
|
|
47
50
|
|
|
48
|
-
##
|
|
49
|
-
|
|
50
|
-
Launch with `project-compass --studio` or press **Shift+A** inside the app. Omni-Studio provides real-time intelligence on your installed development environments, checking versions for Node, Python, Rust, Go, Java, and more.
|
|
51
|
+
## Orbit Task Manager
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
Project Compass v2.8 introduces background task management. You can start a build, press **Shift+D** to detach and return to the navigator, then start another task. Switch between them or view logs via the Task Manager (**Shift+T**).
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
## Frameworks
|
|
55
|
+
## Omni-Studio
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
- **Node.js**: Next.js, React, Vue, NestJS, Vite, Prisma, Tailwind
|
|
60
|
-
- **Python**: FastAPI, Django, Flask
|
|
61
|
-
- **Java/Kotlin**: Spring Boot (Maven & Gradle)
|
|
62
|
-
- **Rust**: Rocket, Actix Web
|
|
63
|
-
- **.NET**: ASP.NET Core
|
|
64
|
-
- **PHP**: Laravel
|
|
57
|
+
Launch with `project-compass --studio` or press **Shift+A** inside the app. Omni-Studio provides real-time intelligence on your installed development environments, checking versions for Node, Python, Rust, Go, Java, and more.
|
|
65
58
|
|
|
66
59
|
## License
|
|
67
60
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -142,9 +142,9 @@ function Studio() {
|
|
|
142
142
|
{flexDirection: 'column'},
|
|
143
143
|
...runtimes.map(r => create(
|
|
144
144
|
Box,
|
|
145
|
-
{key: r.name, marginBottom:
|
|
146
|
-
create(Text, {width:
|
|
147
|
-
create(Text, {dimColor: r.status !== 'ok'}, r.version)
|
|
145
|
+
{key: r.name, marginBottom: 0},
|
|
146
|
+
create(Text, {width: 20, color: r.status === 'ok' ? 'green' : 'red'}, `${r.status === 'ok' ? 'โ' : 'โ'} ${r.name}`),
|
|
147
|
+
create(Text, {dimColor: r.status !== 'ok'}, `: ${r.version}`)
|
|
148
148
|
)),
|
|
149
149
|
create(Text, {marginTop: 1, color: 'yellow'}, '๐ ๏ธ Interactive Project Creator coming soon in v3.0'),
|
|
150
150
|
create(Text, {dimColor: true}, 'Press Shift+A to return to Navigator.')
|
|
@@ -153,13 +153,6 @@ function Studio() {
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
function CursorText({value, cursorIndex, active = true}) {
|
|
156
|
-
const [visible, setVisible] = useState(true);
|
|
157
|
-
useEffect(() => {
|
|
158
|
-
if (!active) return;
|
|
159
|
-
const interval = setInterval(() => setVisible(v => !v), 500);
|
|
160
|
-
return () => clearInterval(interval);
|
|
161
|
-
}, [active]);
|
|
162
|
-
|
|
163
156
|
const before = value.slice(0, cursorIndex);
|
|
164
157
|
const charAt = value[cursorIndex] || ' ';
|
|
165
158
|
const after = value.slice(cursorIndex + 1);
|
|
@@ -168,7 +161,7 @@ function CursorText({value, cursorIndex, active = true}) {
|
|
|
168
161
|
Text,
|
|
169
162
|
null,
|
|
170
163
|
before,
|
|
171
|
-
active
|
|
164
|
+
active ? create(Text, {backgroundColor: 'white', color: 'black'}, charAt) : charAt,
|
|
172
165
|
after
|
|
173
166
|
);
|
|
174
167
|
}
|
|
@@ -179,9 +172,9 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
179
172
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
180
173
|
const [viewMode, setViewMode] = useState('list');
|
|
181
174
|
const [mainView, setMainView] = useState(initialView);
|
|
182
|
-
const [
|
|
175
|
+
const [tasks, setTasks] = useState([]);
|
|
176
|
+
const [activeTaskId, setActiveTaskId] = useState(null);
|
|
183
177
|
const [logOffset, setLogOffset] = useState(0);
|
|
184
|
-
const [running, setRunning] = useState(false);
|
|
185
178
|
const [lastAction, setLastAction] = useState(null);
|
|
186
179
|
const [customMode, setCustomMode] = useState(false);
|
|
187
180
|
const [customInput, setCustomInput] = useState('');
|
|
@@ -194,16 +187,20 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
194
187
|
const [showHelp, setShowHelp] = useState(false);
|
|
195
188
|
const [recentRuns, setRecentRuns] = useState([]);
|
|
196
189
|
const selectedProject = projects[selectedIndex] || null;
|
|
197
|
-
const
|
|
190
|
+
const runningProcessMap = useRef(new Map());
|
|
198
191
|
const lastCommandRef = useRef(null);
|
|
199
192
|
|
|
200
|
-
const
|
|
201
|
-
|
|
193
|
+
const activeTask = useMemo(() => tasks.find(t => t.id === activeTaskId), [tasks, activeTaskId]);
|
|
194
|
+
const running = activeTask?.status === 'running';
|
|
195
|
+
|
|
196
|
+
const addLogToTask = useCallback((taskId, line) => {
|
|
197
|
+
setTasks(prev => prev.map(t => {
|
|
198
|
+
if (t.id !== taskId) return t;
|
|
202
199
|
const normalized = typeof line === 'string' ? line : JSON.stringify(line);
|
|
203
200
|
const lines = normalized.split(/\r?\n/).filter(l => l.trim().length > 0);
|
|
204
|
-
const
|
|
205
|
-
return
|
|
206
|
-
});
|
|
201
|
+
const nextLogs = [...t.logs, ...lines];
|
|
202
|
+
return { ...t, logs: nextLogs.length > 500 ? nextLogs.slice(-500) : nextLogs };
|
|
203
|
+
}));
|
|
207
204
|
}, []);
|
|
208
205
|
|
|
209
206
|
const detailCommands = useMemo(() => buildDetailCommands(selectedProject, config), [selectedProject, config]);
|
|
@@ -219,85 +216,71 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
219
216
|
|
|
220
217
|
const runProjectCommand = useCallback(async (commandMeta, targetProject = selectedProject) => {
|
|
221
218
|
const project = targetProject || selectedProject;
|
|
222
|
-
if (!project)
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
if (!commandMeta || !Array.isArray(commandMeta.command) || commandMeta.command.length === 0) {
|
|
226
|
-
addLog(kleur.gray('(no command configured)'));
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
if (running) {
|
|
230
|
-
addLog(kleur.yellow('โ Wait for the current task to finish.'));
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
219
|
+
if (!project) return;
|
|
220
|
+
if (!commandMeta || !Array.isArray(commandMeta.command) || commandMeta.command.length === 0) return;
|
|
233
221
|
|
|
234
222
|
const commandLabel = commandMeta.label || commandMeta.command.join(' ');
|
|
223
|
+
const taskId = `task-${Date.now()}`;
|
|
224
|
+
const newTask = {
|
|
225
|
+
id: taskId,
|
|
226
|
+
name: `${project.name} ยท ${commandLabel}`,
|
|
227
|
+
status: 'running',
|
|
228
|
+
logs: [kleur.cyan(`> ${commandMeta.command.join(' ')}`)],
|
|
229
|
+
project: project.name
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
setTasks(prev => [...prev, newTask]);
|
|
233
|
+
setActiveTaskId(taskId);
|
|
235
234
|
lastCommandRef.current = {project, commandMeta};
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const fullCmd = commandMeta.command;
|
|
239
|
-
addLog(kleur.cyan(`> ${fullCmd.join(' ')}`));
|
|
235
|
+
setLastAction(newTask.name);
|
|
236
|
+
|
|
240
237
|
setRecentRuns((prev) => {
|
|
241
238
|
const entry = {project: project.name, command: commandLabel, time: new Date().toLocaleTimeString()};
|
|
242
239
|
return [entry, ...prev].slice(0, RECENT_RUN_LIMIT);
|
|
243
240
|
});
|
|
244
241
|
|
|
245
242
|
try {
|
|
246
|
-
const subprocess = execa(
|
|
243
|
+
const subprocess = execa(commandMeta.command[0], commandMeta.command.slice(1), {
|
|
247
244
|
cwd: project.path,
|
|
248
245
|
env: process.env,
|
|
249
246
|
stdin: 'pipe'
|
|
250
247
|
});
|
|
251
|
-
|
|
248
|
+
runningProcessMap.current.set(taskId, subprocess);
|
|
252
249
|
|
|
253
|
-
subprocess.stdout?.on('data', (chunk) =>
|
|
254
|
-
|
|
255
|
-
});
|
|
256
|
-
subprocess.stderr?.on('data', (chunk) => {
|
|
257
|
-
addLog(kleur.red(chunk.toString()));
|
|
258
|
-
});
|
|
250
|
+
subprocess.stdout?.on('data', (chunk) => addLogToTask(taskId, chunk.toString()));
|
|
251
|
+
subprocess.stderr?.on('data', (chunk) => addLogToTask(taskId, kleur.red(chunk.toString())));
|
|
259
252
|
|
|
260
253
|
await subprocess;
|
|
261
|
-
|
|
254
|
+
setTasks(prev => prev.map(t => t.id === taskId ? {...t, status: 'finished'} : t));
|
|
255
|
+
addLogToTask(taskId, kleur.green(`โ ${commandLabel} finished`));
|
|
262
256
|
} catch (error) {
|
|
263
|
-
|
|
257
|
+
setTasks(prev => prev.map(t => t.id === taskId ? {...t, status: 'failed'} : t));
|
|
258
|
+
addLogToTask(taskId, kleur.red(`โ ${commandLabel} failed: ${error.shortMessage || error.message}`));
|
|
264
259
|
} finally {
|
|
265
|
-
|
|
266
|
-
setStdinBuffer('');
|
|
267
|
-
setStdinCursor(0);
|
|
268
|
-
runningProcessRef.current = null;
|
|
260
|
+
runningProcessMap.current.delete(taskId);
|
|
269
261
|
}
|
|
270
|
-
}, [
|
|
262
|
+
}, [addLogToTask, selectedProject]);
|
|
271
263
|
|
|
272
264
|
const handleAddCustomCommand = useCallback((label, commandTokens) => {
|
|
273
|
-
if (!selectedProject)
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
265
|
+
if (!selectedProject) return;
|
|
276
266
|
setConfig((prev) => {
|
|
277
267
|
const projectKey = selectedProject.path;
|
|
278
268
|
const existing = prev.customCommands?.[projectKey] || [];
|
|
279
|
-
const nextCustom = [...existing, {label, command: commandTokens}];
|
|
280
269
|
const nextConfig = {
|
|
281
270
|
...prev,
|
|
282
271
|
customCommands: {
|
|
283
272
|
...prev.customCommands,
|
|
284
|
-
[projectKey]:
|
|
273
|
+
[projectKey]: [...existing, {label, command: commandTokens}]
|
|
285
274
|
}
|
|
286
275
|
};
|
|
287
276
|
saveConfig(nextConfig);
|
|
288
277
|
return nextConfig;
|
|
289
278
|
});
|
|
290
|
-
|
|
291
|
-
}, [selectedProject, addLog]);
|
|
279
|
+
}, [selectedProject]);
|
|
292
280
|
|
|
293
281
|
const handleCustomSubmit = useCallback(() => {
|
|
294
282
|
const raw = customInput.trim();
|
|
295
|
-
if (!selectedProject) {
|
|
296
|
-
setCustomMode(false);
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
if (!raw) {
|
|
300
|
-
addLog(kleur.gray('Canceled custom command (empty).'));
|
|
283
|
+
if (!selectedProject || !raw) {
|
|
301
284
|
setCustomMode(false);
|
|
302
285
|
setCustomInput('');
|
|
303
286
|
setCustomCursor(0);
|
|
@@ -306,44 +289,33 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
306
289
|
const [labelPart, commandPart] = raw.split('|');
|
|
307
290
|
const commandTokens = (commandPart || labelPart).trim().split(/\s+/).filter(Boolean);
|
|
308
291
|
if (!commandTokens.length) {
|
|
309
|
-
addLog(kleur.red('Custom command needs at least one token.'));
|
|
310
292
|
setCustomMode(false);
|
|
311
293
|
setCustomInput('');
|
|
312
294
|
setCustomCursor(0);
|
|
313
295
|
return;
|
|
314
296
|
}
|
|
315
297
|
const label = commandPart ? labelPart.trim() : `Custom ${selectedProject.name}`;
|
|
316
|
-
handleAddCustomCommand(label
|
|
298
|
+
handleAddCustomCommand(label, commandTokens);
|
|
317
299
|
setCustomMode(false);
|
|
318
300
|
setCustomInput('');
|
|
319
301
|
setCustomCursor(0);
|
|
320
|
-
}, [customInput, selectedProject, handleAddCustomCommand
|
|
302
|
+
}, [customInput, selectedProject, handleAddCustomCommand]);
|
|
321
303
|
|
|
322
304
|
const exportLogs = useCallback(() => {
|
|
323
|
-
if (!
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
305
|
+
if (!activeTask || !activeTask.logs.length) return;
|
|
326
306
|
try {
|
|
327
|
-
const exportPath = path.resolve(process.cwd(), `compass
|
|
328
|
-
fs.writeFileSync(exportPath,
|
|
329
|
-
|
|
307
|
+
const exportPath = path.resolve(process.cwd(), `compass-${activeTask.id}.txt`);
|
|
308
|
+
fs.writeFileSync(exportPath, activeTask.logs.join('\n'));
|
|
309
|
+
addLogToTask(activeTaskId, kleur.green(`โ Logs exported to ${exportPath}`));
|
|
330
310
|
} catch (err) {
|
|
331
|
-
|
|
311
|
+
addLogToTask(activeTaskId, kleur.red(`โ Export failed: ${err.message}`));
|
|
332
312
|
}
|
|
333
|
-
}, [
|
|
313
|
+
}, [activeTask, activeTaskId, addLogToTask]);
|
|
334
314
|
|
|
335
315
|
useInput((input, key) => {
|
|
336
316
|
if (customMode) {
|
|
337
|
-
if (key.return) {
|
|
338
|
-
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
if (key.escape) {
|
|
342
|
-
setCustomMode(false);
|
|
343
|
-
setCustomInput('');
|
|
344
|
-
setCustomCursor(0);
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
317
|
+
if (key.return) { handleCustomSubmit(); return; }
|
|
318
|
+
if (key.escape) { setCustomMode(false); setCustomInput(''); setCustomCursor(0); return; }
|
|
347
319
|
if (key.backspace || key.delete) {
|
|
348
320
|
if (customCursor > 0) {
|
|
349
321
|
setCustomInput((prev) => prev.slice(0, customCursor - 1) + prev.slice(customCursor));
|
|
@@ -351,14 +323,8 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
351
323
|
}
|
|
352
324
|
return;
|
|
353
325
|
}
|
|
354
|
-
if (key.leftArrow) {
|
|
355
|
-
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
if (key.rightArrow) {
|
|
359
|
-
setCustomCursor(c => Math.min(customInput.length, c + 1));
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
326
|
+
if (key.leftArrow) { setCustomCursor(c => Math.max(0, c - 1)); return; }
|
|
327
|
+
if (key.rightArrow) { setCustomCursor(c => Math.min(customInput.length, c + 1)); return; }
|
|
362
328
|
if (input) {
|
|
363
329
|
setCustomInput((prev) => prev.slice(0, customCursor) + input + prev.slice(customCursor));
|
|
364
330
|
setCustomCursor(c => c + input.length);
|
|
@@ -367,51 +333,28 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
367
333
|
}
|
|
368
334
|
|
|
369
335
|
const normalizedInput = input?.toLowerCase();
|
|
370
|
-
const ctrlCombo = (char) => key.ctrl && normalizedInput === char;
|
|
371
336
|
const shiftCombo = (char) => key.shift && normalizedInput === char;
|
|
372
|
-
|
|
373
|
-
if (
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
if (
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
381
|
-
if (toggleShortcut('a')) {
|
|
382
|
-
setMainView((prev) => (prev === 'navigator' ? 'studio' : 'navigator'));
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
if (toggleShortcut('x')) {
|
|
386
|
-
setLogLines([]);
|
|
387
|
-
setLogOffset(0);
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
if (shiftCombo('e')) {
|
|
391
|
-
exportLogs();
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
337
|
+
|
|
338
|
+
if (shiftCombo('h')) { setShowHelpCards((prev) => !prev); return; }
|
|
339
|
+
if (shiftCombo('s')) { setShowStructureGuide((prev) => !prev); return; }
|
|
340
|
+
if (shiftCombo('a')) { setMainView((prev) => (prev === 'navigator' ? 'studio' : 'navigator')); return; }
|
|
341
|
+
if (shiftCombo('x')) { setTasks(prev => prev.map(t => t.id === activeTaskId ? {...t, logs: []} : t)); setLogOffset(0); return; }
|
|
342
|
+
if (shiftCombo('e')) { exportLogs(); return; }
|
|
343
|
+
if (shiftCombo('d')) { setActiveTaskId(null); return; }
|
|
344
|
+
if (shiftCombo('t')) { setMainView('tasks'); return; }
|
|
394
345
|
|
|
395
346
|
const scrollLogs = (delta) => {
|
|
396
347
|
setLogOffset((prev) => {
|
|
397
|
-
const
|
|
348
|
+
const logs = activeTask?.logs || [];
|
|
349
|
+
const maxScroll = Math.max(0, logs.length - OUTPUT_WINDOW_SIZE);
|
|
398
350
|
return Math.max(0, Math.min(maxScroll, prev + delta));
|
|
399
351
|
});
|
|
400
352
|
};
|
|
401
353
|
|
|
402
|
-
if (running &&
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
setStdinCursor(0);
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
if (key.return) {
|
|
410
|
-
runningProcessRef.current.stdin?.write(stdinBuffer + '\n');
|
|
411
|
-
setStdinBuffer('');
|
|
412
|
-
setStdinCursor(0);
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
354
|
+
if (running && activeTaskId && runningProcessMap.current.has(activeTaskId)) {
|
|
355
|
+
const proc = runningProcessMap.current.get(activeTaskId);
|
|
356
|
+
if (key.ctrl && input === 'c') { proc.kill('SIGINT'); setStdinBuffer(''); setStdinCursor(0); return; }
|
|
357
|
+
if (key.return) { proc.stdin?.write(stdinBuffer + '\n'); setStdinBuffer(''); setStdinCursor(0); return; }
|
|
415
358
|
if (key.backspace || key.delete) {
|
|
416
359
|
if (stdinCursor > 0) {
|
|
417
360
|
setStdinBuffer(prev => prev.slice(0, stdinCursor - 1) + prev.slice(stdinCursor));
|
|
@@ -419,14 +362,8 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
419
362
|
}
|
|
420
363
|
return;
|
|
421
364
|
}
|
|
422
|
-
if (key.leftArrow) {
|
|
423
|
-
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
if (key.rightArrow) {
|
|
427
|
-
setStdinCursor(c => Math.min(stdinBuffer.length, c + 1));
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
365
|
+
if (key.leftArrow) { setStdinCursor(c => Math.max(0, c - 1)); return; }
|
|
366
|
+
if (key.rightArrow) { setStdinCursor(c => Math.min(stdinBuffer.length, c + 1)); return; }
|
|
430
367
|
if (input) {
|
|
431
368
|
setStdinBuffer(prev => prev.slice(0, stdinCursor) + input + prev.slice(stdinCursor));
|
|
432
369
|
setStdinCursor(c => c + input.length);
|
|
@@ -434,49 +371,29 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
434
371
|
return;
|
|
435
372
|
}
|
|
436
373
|
|
|
437
|
-
if (key.shift && key.upArrow) {
|
|
438
|
-
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
if (key.shift && key.downArrow) {
|
|
442
|
-
scrollLogs(-1);
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
374
|
+
if (key.shift && key.upArrow) { scrollLogs(-1); return; }
|
|
375
|
+
if (key.shift && key.downArrow) { scrollLogs(1); return; }
|
|
445
376
|
|
|
446
|
-
if (normalizedInput === '?') {
|
|
447
|
-
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
|
-
if (shiftCombo('l') && lastCommandRef.current) {
|
|
451
|
-
runProjectCommand(lastCommandRef.current.commandMeta, lastCommandRef.current.project);
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
377
|
+
if (normalizedInput === '?') { setShowHelp((prev) => !prev); return; }
|
|
378
|
+
if (shiftCombo('l') && lastCommandRef.current) { runProjectCommand(lastCommandRef.current.commandMeta, lastCommandRef.current.project); return; }
|
|
454
379
|
|
|
455
|
-
if (
|
|
456
|
-
|
|
457
|
-
return;
|
|
458
|
-
|
|
459
|
-
if (key.downArrow && !key.shift && projects.length > 0) {
|
|
460
|
-
setSelectedIndex((prev) => (prev + 1) % projects.length);
|
|
380
|
+
if (mainView === 'tasks') {
|
|
381
|
+
if (key.upArrow) { setActiveTaskId(prev => tasks[(tasks.findIndex(t => t.id === prev) - 1 + tasks.length) % tasks.length]?.id); return; }
|
|
382
|
+
if (key.downArrow) { setActiveTaskId(prev => tasks[(tasks.findIndex(t => t.id === prev) + 1) % tasks.length]?.id); return; }
|
|
383
|
+
if (key.return || shiftCombo('t')) { setMainView('navigator'); return; }
|
|
461
384
|
return;
|
|
462
385
|
}
|
|
386
|
+
|
|
387
|
+
if (key.upArrow && !key.shift && projects.length > 0) { setSelectedIndex((prev) => (prev - 1 + projects.length) % projects.length); return; }
|
|
388
|
+
if (key.downArrow && !key.shift && projects.length > 0) { setSelectedIndex((prev) => (prev + 1) % projects.length); return; }
|
|
463
389
|
if (key.return) {
|
|
464
|
-
if (!selectedProject)
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
390
|
+
if (!selectedProject) return;
|
|
467
391
|
setViewMode((prev) => (prev === 'detail' ? 'list' : 'detail'));
|
|
468
392
|
return;
|
|
469
393
|
}
|
|
470
|
-
if (shiftCombo('q')) {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
if (shiftCombo('c') && viewMode === 'detail' && selectedProject) {
|
|
475
|
-
setCustomMode(true);
|
|
476
|
-
setCustomInput('');
|
|
477
|
-
setCustomCursor(0);
|
|
478
|
-
return;
|
|
479
|
-
}
|
|
394
|
+
if (shiftCombo('q')) { exit(); return; }
|
|
395
|
+
if (shiftCombo('c') && viewMode === 'detail' && selectedProject) { setCustomMode(true); setCustomInput(''); setCustomCursor(0); return; }
|
|
396
|
+
|
|
480
397
|
const actionKey = normalizedInput && ACTION_MAP[normalizedInput];
|
|
481
398
|
if (actionKey) {
|
|
482
399
|
const commandMeta = selectedProject?.commands?.[actionKey];
|
|
@@ -488,18 +405,30 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
488
405
|
}
|
|
489
406
|
});
|
|
490
407
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
if (
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
408
|
+
if (mainView === 'studio') return create(Studio);
|
|
409
|
+
|
|
410
|
+
if (mainView === 'tasks') {
|
|
411
|
+
return create(
|
|
412
|
+
Box,
|
|
413
|
+
{flexDirection: 'column', borderStyle: 'round', borderColor: 'yellow', padding: 1},
|
|
414
|
+
create(Text, {bold: true, color: 'yellow'}, '๐ฐ๏ธ Task Manager | Background Processes'),
|
|
415
|
+
create(Text, {dimColor: true, marginBottom: 1}, 'Select a task to view its output logs.'),
|
|
416
|
+
...tasks.map(t => create(
|
|
417
|
+
Box,
|
|
418
|
+
{key: t.id, marginBottom: 0},
|
|
419
|
+
create(Text, {color: t.id === activeTaskId ? 'cyan' : 'white', bold: t.id === activeTaskId}, `${t.id === activeTaskId ? 'โ' : ' '} [${t.status.toUpperCase()}] ${t.name}`)
|
|
420
|
+
)),
|
|
421
|
+
!tasks.length && create(Text, {dimColor: true}, 'No active or background tasks.'),
|
|
422
|
+
create(Text, {marginTop: 1, dimColor: true}, 'Press Enter/Shift+T to return to Navigator, Up/Down to switch focus.')
|
|
423
|
+
);
|
|
501
424
|
}
|
|
502
|
-
|
|
425
|
+
|
|
426
|
+
const projectRows = [];
|
|
427
|
+
if (loading) projectRows.push(create(Text, {key: 'scanning', dimColor: true}, 'Scanning projectsโฆ'));
|
|
428
|
+
if (error) projectRows.push(create(Text, {key: 'error', color: 'red'}, `Unable to scan: ${error}`));
|
|
429
|
+
if (!loading && !error && projects.length === 0) projectRows.push(create(Text, {key: 'empty', dimColor: true}, 'No recognizable project manifests found.'));
|
|
430
|
+
|
|
431
|
+
if (!loading) {
|
|
503
432
|
projects.forEach((project, index) => {
|
|
504
433
|
const isSelected = index === selectedIndex;
|
|
505
434
|
const frameworkBadges = (project.frameworks || []).map((frame) => `${frame.icon} ${frame.name}`).join(', ');
|
|
@@ -511,14 +440,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
511
440
|
create(
|
|
512
441
|
Box,
|
|
513
442
|
{flexDirection: 'row'},
|
|
514
|
-
create(
|
|
515
|
-
Text,
|
|
516
|
-
{
|
|
517
|
-
color: isSelected ? 'cyan' : 'white',
|
|
518
|
-
bold: isSelected
|
|
519
|
-
},
|
|
520
|
-
`${project.icon} ${project.name}`
|
|
521
|
-
),
|
|
443
|
+
create(Text, {color: isSelected ? 'cyan' : 'white', bold: isSelected}, `${project.icon} ${project.name}`),
|
|
522
444
|
hasMissingRuntime && create(Text, {color: 'red', bold: true}, ' โ ๏ธ Runtime missing')
|
|
523
445
|
),
|
|
524
446
|
create(Text, {dimColor: true}, ` ${project.type} ยท ${path.relative(rootPath, project.path) || '.'}`),
|
|
@@ -531,359 +453,88 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
531
453
|
const detailContent = [];
|
|
532
454
|
if (viewMode === 'detail' && selectedProject) {
|
|
533
455
|
detailContent.push(
|
|
534
|
-
create(
|
|
535
|
-
Box,
|
|
536
|
-
{key: 'title-row', flexDirection: 'row'},
|
|
456
|
+
create(Box, {key: 'title-row', flexDirection: 'row'},
|
|
537
457
|
create(Text, {color: 'cyan', bold: true}, `${selectedProject.icon} ${selectedProject.name}`),
|
|
538
458
|
selectedProject.missingBinaries && selectedProject.missingBinaries.length > 0 && create(Text, {color: 'red', bold: true}, ' โ ๏ธ MISSING RUNTIME')
|
|
539
459
|
),
|
|
540
460
|
create(Text, {key: 'manifest', dimColor: true}, `${selectedProject.type} ยท ${selectedProject.manifest || 'detected manifest'}`),
|
|
541
461
|
create(Text, {key: 'loc', dimColor: true}, `Location: ${path.relative(rootPath, selectedProject.path) || '.'}`)
|
|
542
462
|
);
|
|
543
|
-
if (selectedProject.description) {
|
|
544
|
-
detailContent.push(create(Text, {key: 'desc'}, selectedProject.description));
|
|
545
|
-
}
|
|
463
|
+
if (selectedProject.description) detailContent.push(create(Text, {key: 'desc'}, selectedProject.description));
|
|
546
464
|
const frameworks = (selectedProject.frameworks || []).map((lib) => `${lib.icon} ${lib.name}`).join(', ');
|
|
547
|
-
if (frameworks) {
|
|
548
|
-
|
|
549
|
-
}
|
|
550
|
-
if (selectedProject.extra?.scripts && selectedProject.extra.scripts.length) {
|
|
551
|
-
detailContent.push(create(Text, {key: 'scripts', dimColor: true}, `Scripts: ${selectedProject.extra.scripts.join(', ')}`));
|
|
552
|
-
}
|
|
553
|
-
|
|
465
|
+
if (frameworks) detailContent.push(create(Text, {key: 'frames', dimColor: true}, `Frameworks: ${frameworks}`));
|
|
466
|
+
|
|
554
467
|
if (selectedProject.missingBinaries && selectedProject.missingBinaries.length > 0) {
|
|
555
468
|
detailContent.push(
|
|
556
|
-
create(Text, {key: '
|
|
557
|
-
create(Text, {key: '
|
|
558
|
-
create(Text, {key: 'missing-hint', dimColor: true}, 'Project commands may fail until these are in your PATH.')
|
|
469
|
+
create(Text, {key: 'm-t', color: 'red', bold: true, marginTop: 1}, 'MISSING BINARIES:'),
|
|
470
|
+
create(Text, {key: 'm-l', color: 'red'}, `Please install: ${selectedProject.missingBinaries.join(', ')}`)
|
|
559
471
|
);
|
|
560
472
|
}
|
|
561
473
|
|
|
562
|
-
detailContent.push(create(Text, {key: 'config-path', dimColor: true, marginTop: 1}, `Custom commands stored in ${CONFIG_PATH}`));
|
|
563
|
-
detailContent.push(create(Text, {key: 'plugin-path', dimColor: true, marginBottom: 1}, `Extend frameworks via ${PLUGIN_FILE}`));
|
|
564
474
|
detailContent.push(create(Text, {key: 'cmd-header', bold: true, marginTop: 1}, 'Commands'));
|
|
565
475
|
detailedIndexed.forEach((command) => {
|
|
566
476
|
detailContent.push(
|
|
567
|
-
create(Text, {key: `
|
|
477
|
+
create(Text, {key: `d-${command.shortcut}`}, `${command.shortcut}. ${command.label} ${command.source === 'custom' ? kleur.magenta('(custom)') : command.source === 'framework' ? kleur.cyan('(framework)') : ''}`),
|
|
478
|
+
create(Text, {key: `dl-${command.shortcut}`, dimColor: true}, ` โณ ${command.command.join(' ')}`)
|
|
568
479
|
);
|
|
569
|
-
detailContent.push(create(Text, {key: `detail-line-${command.shortcut}-${command.label}`, dimColor: true}, ` โณ ${command.command.join(' ')}`));
|
|
570
480
|
});
|
|
571
|
-
|
|
572
|
-
detailContent.push(create(Text, {key: 'no-cmds', dimColor: true}, 'No built-in commands yet. Add a custom command with Shift+C.'));
|
|
573
|
-
}
|
|
574
|
-
const setupHints = selectedProject.extra?.setupHints || [];
|
|
575
|
-
if (setupHints.length) {
|
|
576
|
-
detailContent.push(create(Text, {key: 'setup-header', dimColor: true, marginTop: 1}, 'Setup hints:'));
|
|
577
|
-
setupHints.forEach((hint, hidx) => detailContent.push(create(Text, {key: `hint-${hidx}`, dimColor: true}, ` โข ${hint}`)));
|
|
578
|
-
}
|
|
579
|
-
detailContent.push(create(Text, {key: 'hint-line', dimColor: true}, 'Press Shift+C โ label|cmd to save custom actions, Enter to close detail view.'));
|
|
481
|
+
detailContent.push(create(Text, {key: 'h-l', dimColor: true, marginTop: 1}, 'Press Shift+C โ label|cmd to save custom actions, Enter to close detail view.'));
|
|
580
482
|
} else {
|
|
581
|
-
detailContent.push(create(Text, {key: '
|
|
483
|
+
detailContent.push(create(Text, {key: 'e-h', dimColor: true}, 'Press Enter on a project to reveal details.'));
|
|
582
484
|
}
|
|
583
485
|
|
|
584
486
|
if (customMode) {
|
|
585
|
-
detailContent.push(
|
|
586
|
-
create(
|
|
587
|
-
Box,
|
|
588
|
-
{key: 'custom-input-box', flexDirection: 'row'},
|
|
589
|
-
create(Text, {color: 'cyan'}, 'Type label|cmd (Enter: save, Esc: cancel): '),
|
|
590
|
-
create(CursorText, {value: customInput, cursorIndex: customCursor})
|
|
591
|
-
)
|
|
592
|
-
);
|
|
487
|
+
detailContent.push(create(Box, {key: 'ci-box', flexDirection: 'row'}, create(Text, {color: 'cyan'}, 'Type label|cmd (Enter: save, Esc: cancel): '), create(CursorText, {value: customInput, cursorIndex: customCursor})));
|
|
593
488
|
}
|
|
594
489
|
|
|
595
|
-
const artTileNodes =
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
{
|
|
610
|
-
label: 'Focus',
|
|
611
|
-
detail: selectedName,
|
|
612
|
-
accent: 'cyan',
|
|
613
|
-
icon: 'โ',
|
|
614
|
-
subtext: `${selectedType} ยท ${selectedLocation}`
|
|
615
|
-
},
|
|
616
|
-
{
|
|
617
|
-
label: 'Rhythm',
|
|
618
|
-
detail: `${detailCommands.length} commands`,
|
|
619
|
-
accent: 'yellow',
|
|
620
|
-
icon: 'โ ',
|
|
621
|
-
subtext: statusNarrative
|
|
622
|
-
}
|
|
623
|
-
];
|
|
624
|
-
return tileDefinition.map((tile) =>
|
|
625
|
-
create(
|
|
626
|
-
Box,
|
|
627
|
-
{
|
|
628
|
-
key: tile.label,
|
|
629
|
-
flexDirection: 'column',
|
|
630
|
-
padding: 1,
|
|
631
|
-
marginRight: 1,
|
|
632
|
-
borderStyle: 'single',
|
|
633
|
-
borderColor: tile.accent,
|
|
634
|
-
minWidth: 24
|
|
635
|
-
},
|
|
636
|
-
create(Text, {color: tile.accent, bold: true}, `${tile.icon} ${tile.label}`),
|
|
637
|
-
create(Text, {bold: true}, tile.detail),
|
|
638
|
-
create(Text, {dimColor: true}, tile.subtext)
|
|
639
|
-
)
|
|
640
|
-
);
|
|
641
|
-
}, [projectCountLabel, rootPath, selectedProject, detailCommands.length, running, lastAction]);
|
|
642
|
-
|
|
643
|
-
const artBoard = create(
|
|
644
|
-
Box,
|
|
645
|
-
{
|
|
646
|
-
flexDirection: 'column',
|
|
647
|
-
marginTop: 1,
|
|
648
|
-
borderStyle: 'round',
|
|
649
|
-
borderColor: 'gray',
|
|
650
|
-
padding: 1
|
|
651
|
-
},
|
|
652
|
-
create(
|
|
653
|
-
Box,
|
|
654
|
-
{flexDirection: 'row', justifyContent: 'space-between'},
|
|
655
|
-
create(Text, {color: 'magenta', bold: true}, 'Art-coded build atlas'),
|
|
656
|
-
create(Text, {dimColor: true}, 'press ? for overlay help')
|
|
657
|
-
),
|
|
658
|
-
create(
|
|
659
|
-
Box,
|
|
660
|
-
{flexDirection: 'row', marginTop: 1},
|
|
661
|
-
...ART_CHARS.map((char, index) =>
|
|
662
|
-
create(Text, {key: `art-${index}`, color: ART_COLORS[index % ART_COLORS.length]}, char.repeat(2))
|
|
663
|
-
)
|
|
664
|
-
),
|
|
665
|
-
create(
|
|
666
|
-
Box,
|
|
667
|
-
{flexDirection: 'row', marginTop: 1},
|
|
668
|
-
...artTileNodes
|
|
669
|
-
),
|
|
670
|
-
create(Text, {dimColor: true, marginTop: 1}, kleur.italic('The art board now follows your layout and stays inside the window.'))
|
|
490
|
+
const artTileNodes = [
|
|
491
|
+
{label: 'Pulse', detail: projectCountLabel, accent: 'magenta', icon: 'โ', subtext: `Workspace ยท ${path.basename(rootPath) || rootPath}`},
|
|
492
|
+
{label: 'Focus', detail: selectedProject?.name || 'Selection', accent: 'cyan', icon: 'โ', subtext: `${selectedProject?.type || 'Stack'}`},
|
|
493
|
+
{label: 'Orbit', detail: `${tasks.length} active tasks`, accent: 'yellow', icon: 'โ ', subtext: running ? 'Busy streaming...' : 'Idle'}
|
|
494
|
+
].map(tile => create(Box, {key: tile.label, flexDirection: 'column', padding: 1, marginRight: 1, borderStyle: 'single', borderColor: tile.accent, minWidth: 24},
|
|
495
|
+
create(Text, {color: tile.accent, bold: true}, `${tile.icon} ${tile.label}`),
|
|
496
|
+
create(Text, {bold: true}, tile.detail),
|
|
497
|
+
create(Text, {dimColor: true}, tile.subtext)
|
|
498
|
+
));
|
|
499
|
+
|
|
500
|
+
const artBoard = create(Box, {flexDirection: 'column', marginTop: 1, borderStyle: 'round', borderColor: 'gray', padding: 1},
|
|
501
|
+
create(Box, {flexDirection: 'row', justifyContent: 'space-between'}, create(Text, {color: 'magenta', bold: true}, 'Art-coded build atlas'), create(Text, {dimColor: true}, 'press ? for overlay help')),
|
|
502
|
+
create(Box, {flexDirection: 'row', marginTop: 1}, ...ART_CHARS.map((char, i) => create(Text, {key: i, color: ART_COLORS[i % ART_COLORS.length]}, char.repeat(2)))),
|
|
503
|
+
create(Box, {flexDirection: 'row', marginTop: 1}, ...artTileNodes)
|
|
671
504
|
);
|
|
672
505
|
|
|
673
|
-
const
|
|
674
|
-
const
|
|
675
|
-
const
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
: [create(Text, {key: 'no-logs', dimColor: true}, 'Logs will appear here once you run a command.')];
|
|
506
|
+
const logs = activeTask?.logs || [];
|
|
507
|
+
const logWindowStart = Math.max(0, logs.length - OUTPUT_WINDOW_SIZE - logOffset);
|
|
508
|
+
const logWindowEnd = Math.max(0, logs.length - logOffset);
|
|
509
|
+
const visibleLogs = logs.slice(logWindowStart, logWindowEnd);
|
|
510
|
+
const logNodes = visibleLogs.length ? visibleLogs.map((line, i) => create(Text, {key: i}, line)) : [create(Text, {dimColor: true}, 'Select a task or run a command to see logs.')];
|
|
679
511
|
|
|
680
512
|
const helpCards = [
|
|
681
|
-
{
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
body: [
|
|
685
|
-
'โ / โ move focus, Enter: details',
|
|
686
|
-
'Shift+โ / โ scroll output',
|
|
687
|
-
'Shift+H toggle help cards',
|
|
688
|
-
'? opens the overlay help'
|
|
689
|
-
]
|
|
690
|
-
},
|
|
691
|
-
{
|
|
692
|
-
label: 'Command flow',
|
|
693
|
-
color: 'cyan',
|
|
694
|
-
body: [
|
|
695
|
-
'B / T / R build/test/run',
|
|
696
|
-
'1-9 run detail commands',
|
|
697
|
-
'Shift+L rerun last command',
|
|
698
|
-
'Shift+X clear / Shift+E export'
|
|
699
|
-
]
|
|
700
|
-
},
|
|
701
|
-
{
|
|
702
|
-
label: 'System & Studio',
|
|
703
|
-
color: 'yellow',
|
|
704
|
-
body: [
|
|
705
|
-
'Shift+A open Omni-Studio',
|
|
706
|
-
'Shift+S toggle structure guide',
|
|
707
|
-
'Shift+C save custom action',
|
|
708
|
-
'Shift+Q quit application'
|
|
709
|
-
]
|
|
710
|
-
}
|
|
513
|
+
{label: 'Navigation', color: 'magenta', body: ['โ / โ move focus, Enter: details', 'Shift+โ / โ scroll output', 'Shift+H toggle help cards', 'Shift+D detach from task']},
|
|
514
|
+
{label: 'Commands', color: 'cyan', body: ['B / T / R build/test/run', '1-9 run detail commands', 'Shift+L rerun last command', 'Shift+X clear / Shift+E export']},
|
|
515
|
+
{label: 'Orbit & Studio', color: 'yellow', body: ['Shift+T task manager', 'Shift+A open Omni-Studio', 'Shift+C save custom action', 'Shift+Q quit application']}
|
|
711
516
|
];
|
|
712
|
-
const helpSection = showHelpCards
|
|
713
|
-
? create(
|
|
714
|
-
Box,
|
|
715
|
-
{marginTop: 1, flexDirection: 'row', justifyContent: 'space-between', flexWrap: 'wrap'},
|
|
716
|
-
...helpCards.map((card, index) =>
|
|
717
|
-
create(
|
|
718
|
-
Box,
|
|
719
|
-
{
|
|
720
|
-
key: card.label,
|
|
721
|
-
flexGrow: 1,
|
|
722
|
-
flexBasis: 0,
|
|
723
|
-
minWidth: HELP_CARD_MIN_WIDTH,
|
|
724
|
-
marginRight: index < helpCards.length - 1 ? 1 : 0,
|
|
725
|
-
marginBottom: 1,
|
|
726
|
-
borderStyle: 'round',
|
|
727
|
-
borderColor: card.color,
|
|
728
|
-
padding: 1,
|
|
729
|
-
flexDirection: 'column'
|
|
730
|
-
},
|
|
731
|
-
create(Text, {color: card.color, bold: true, marginBottom: 1}, card.label),
|
|
732
|
-
...card.body.map((line, lineIndex) =>
|
|
733
|
-
create(Text, {key: `${card.label}-${lineIndex}`, dimColor: card.color === 'yellow'}, line)
|
|
734
|
-
)
|
|
735
|
-
)
|
|
736
|
-
)
|
|
737
|
-
)
|
|
738
|
-
: create(Text, {key: 'help-hint', dimColor: true, marginTop: 1}, 'Help cards hidden ยท press Shift+H to show navigation, command flow, and recent runs.');
|
|
739
|
-
|
|
740
|
-
const structureGuide = showStructureGuide
|
|
741
|
-
? create(
|
|
742
|
-
Box,
|
|
743
|
-
{
|
|
744
|
-
flexDirection: 'column',
|
|
745
|
-
borderStyle: 'round',
|
|
746
|
-
borderColor: 'blue',
|
|
747
|
-
marginTop: 1,
|
|
748
|
-
padding: 1
|
|
749
|
-
},
|
|
750
|
-
create(Text, {color: 'cyan', bold: true}, 'Project structure guide ยท press Shift+S to hide'),
|
|
751
|
-
...SCHEMA_GUIDE.map((entry) =>
|
|
752
|
-
create(Text, {key: entry.type, dimColor: true}, `โข ${entry.icon} ${entry.label}: ${entry.files.join(', ')}`)
|
|
753
|
-
),
|
|
754
|
-
create(Text, {dimColor: true, marginTop: 1}, 'SCHEMAS describe the manifests that trigger each language type.')
|
|
755
|
-
)
|
|
756
|
-
: null;
|
|
757
|
-
|
|
758
|
-
const helpOverlay = showHelp
|
|
759
|
-
? create(
|
|
760
|
-
Box,
|
|
761
|
-
{
|
|
762
|
-
flexDirection: 'column',
|
|
763
|
-
borderStyle: 'double',
|
|
764
|
-
borderColor: 'cyan',
|
|
765
|
-
marginTop: 1,
|
|
766
|
-
padding: 1
|
|
767
|
-
},
|
|
768
|
-
create(Text, {color: 'cyan', bold: true}, 'Help overlay ยท press ? to hide'),
|
|
769
|
-
create(Text, null, 'Shift+โ/โ scrolls logs; Shift+X clears; Shift+E exports to file; Shift+A Omni-Studio.'),
|
|
770
|
-
create(Text, null, 'B/T/R run build/test/run; 1-9 detail commands; Shift+L reruns previous command.'),
|
|
771
|
-
create(Text, null, 'Shift+H help cards, Shift+S structure guide, ? overlay, Shift+Q quits.'),
|
|
772
|
-
create(Text, null, 'Projects + Details stay paired while Output keeps its own full-width band.'),
|
|
773
|
-
create(Text, null, 'Structure guide lists the manifests that trigger each language detection.')
|
|
774
|
-
)
|
|
775
|
-
: null;
|
|
776
|
-
|
|
777
|
-
if (mainView === 'studio') {
|
|
778
|
-
return create(Studio);
|
|
779
|
-
}
|
|
780
517
|
|
|
781
|
-
const toggleHint = showHelpCards ? 'Shift+H
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
return create(
|
|
787
|
-
Box,
|
|
788
|
-
{flexDirection: 'column', padding: 1},
|
|
789
|
-
create(
|
|
790
|
-
Box,
|
|
791
|
-
{justifyContent: 'space-between'},
|
|
792
|
-
create(
|
|
793
|
-
Box,
|
|
794
|
-
{flexDirection: 'column'},
|
|
795
|
-
create(Text, {color: 'magenta', bold: true}, 'Project Compass'),
|
|
796
|
-
create(Text, {dimColor: true}, loading ? 'Scanning workspacesโฆ' : `${projectCountLabel} detected in ${rootPath}`),
|
|
797
|
-
create(Text, {dimColor: true}, 'Use keyboard arrows to navigate, ? for help.')
|
|
798
|
-
),
|
|
799
|
-
create(
|
|
800
|
-
Box,
|
|
801
|
-
{flexDirection: 'column', alignItems: 'flex-end'},
|
|
802
|
-
create(Text, {color: running ? 'yellow' : 'green'}, running ? 'Busy streaming...' : lastAction ? `Last action ยท ${lastAction}` : 'Idle'),
|
|
803
|
-
create(Text, {dimColor: true}, headerHint)
|
|
804
|
-
)
|
|
518
|
+
const toggleHint = showHelpCards ? 'Shift+H hide help' : 'Shift+H show help';
|
|
519
|
+
return create(Box, {flexDirection: 'column', padding: 1},
|
|
520
|
+
create(Box, {justifyContent: 'space-between'},
|
|
521
|
+
create(Box, {flexDirection: 'column'}, create(Text, {color: 'magenta', bold: true}, 'Project Compass'), create(Text, {dimColor: true}, `${projectCountLabel} detected in ${rootPath}`)),
|
|
522
|
+
create(Box, {flexDirection: 'column', alignItems: 'flex-end'}, create(Text, {color: running ? 'yellow' : 'green'}, activeTask ? `[${activeTask.status.toUpperCase()}] ${activeTask.name}` : 'Idle Navigator'), create(Text, {dimColor: true}, `${toggleHint}, Shift+T: Tasks, Shift+Q: Quit`))
|
|
805
523
|
),
|
|
806
524
|
artBoard,
|
|
807
|
-
create(
|
|
808
|
-
Box,
|
|
809
|
-
{
|
|
810
|
-
create(
|
|
811
|
-
Box,
|
|
812
|
-
{
|
|
813
|
-
flexGrow: 1,
|
|
814
|
-
flexBasis: 0,
|
|
815
|
-
flexShrink: 1,
|
|
816
|
-
minWidth: PROJECTS_MIN_WIDTH,
|
|
817
|
-
marginRight: 1,
|
|
818
|
-
borderStyle: 'round',
|
|
819
|
-
borderColor: 'magenta',
|
|
820
|
-
padding: 1
|
|
821
|
-
},
|
|
822
|
-
create(Text, {bold: true, color: 'magenta'}, 'Projects'),
|
|
823
|
-
create(Box, {flexDirection: 'column', marginTop: 1}, ...projectRows)
|
|
824
|
-
),
|
|
825
|
-
create(
|
|
826
|
-
Box,
|
|
827
|
-
{
|
|
828
|
-
flexGrow: 1.3,
|
|
829
|
-
flexBasis: 0,
|
|
830
|
-
flexShrink: 1,
|
|
831
|
-
minWidth: DETAILS_MIN_WIDTH,
|
|
832
|
-
borderStyle: 'round',
|
|
833
|
-
borderColor: 'cyan',
|
|
834
|
-
padding: 1,
|
|
835
|
-
flexDirection: 'column'
|
|
836
|
-
},
|
|
837
|
-
create(Text, {bold: true, color: 'cyan'}, 'Details'),
|
|
838
|
-
...detailContent
|
|
839
|
-
)
|
|
525
|
+
create(Box, {marginTop: 1, flexDirection: 'row', alignItems: 'stretch', width: '100%', flexWrap: 'wrap'},
|
|
526
|
+
create(Box, {flexGrow: 1, flexBasis: 0, minWidth: PROJECTS_MIN_WIDTH, marginRight: 1, borderStyle: 'round', borderColor: 'magenta', padding: 1}, create(Text, {bold: true, color: 'magenta'}, 'Projects'), create(Box, {flexDirection: 'column', marginTop: 1}, ...projectRows)),
|
|
527
|
+
create(Box, {flexGrow: 1.3, flexBasis: 0, minWidth: DETAILS_MIN_WIDTH, borderStyle: 'round', borderColor: 'cyan', padding: 1, flexDirection: 'column'}, create(Text, {bold: true, color: 'cyan'}, 'Details'), ...detailContent)
|
|
840
528
|
),
|
|
841
|
-
create(
|
|
842
|
-
Box,
|
|
843
|
-
{
|
|
844
|
-
create(
|
|
845
|
-
|
|
846
|
-
{flexDirection: 'row', justifyContent: 'space-between'},
|
|
847
|
-
create(Text, {bold: true, color: 'yellow'}, `Output ${running ? 'ยท Running' : ''}`),
|
|
848
|
-
create(Text, {dimColor: true}, logOffset ? `Scrolled ${logOffset} lines` : 'Live log view')
|
|
849
|
-
),
|
|
850
|
-
create(
|
|
851
|
-
Box,
|
|
852
|
-
{
|
|
853
|
-
flexDirection: 'column',
|
|
854
|
-
borderStyle: 'round',
|
|
855
|
-
borderColor: 'yellow',
|
|
856
|
-
padding: 1,
|
|
857
|
-
minHeight: OUTPUT_WINDOW_HEIGHT,
|
|
858
|
-
maxHeight: OUTPUT_WINDOW_HEIGHT,
|
|
859
|
-
height: OUTPUT_WINDOW_HEIGHT,
|
|
860
|
-
overflow: 'hidden',
|
|
861
|
-
flexShrink: 0
|
|
862
|
-
},
|
|
863
|
-
...logNodes
|
|
864
|
-
),
|
|
865
|
-
create(
|
|
866
|
-
Box,
|
|
867
|
-
{marginTop: 1, flexDirection: 'row', justifyContent: 'space-between'},
|
|
868
|
-
create(Text, {dimColor: true}, running ? 'Type to feed stdin; Enter submits, Ctrl+C aborts.' : 'Run a command or press ? for extra help.'),
|
|
869
|
-
create(Text, {dimColor: true}, `${toggleHint}, Shift+S toggles the structure guide`)
|
|
870
|
-
),
|
|
871
|
-
create(
|
|
872
|
-
Box,
|
|
873
|
-
{
|
|
874
|
-
marginTop: 1,
|
|
875
|
-
flexDirection: 'row',
|
|
876
|
-
borderStyle: 'round',
|
|
877
|
-
borderColor: running ? 'green' : 'gray',
|
|
878
|
-
paddingX: 1
|
|
879
|
-
},
|
|
880
|
-
create(Text, {bold: true, color: running ? 'green' : 'white'}, running ? ' Stdin buffer ' : ' Input ready '),
|
|
881
|
-
create(Box, {marginLeft: 1}, create(CursorText, {value: stdinBuffer || (running ? '' : 'Start a command to feed stdin'), cursorIndex: stdinCursor, active: running}))
|
|
882
|
-
)
|
|
529
|
+
create(Box, {marginTop: 1, flexDirection: 'column'},
|
|
530
|
+
create(Box, {flexDirection: 'row', justifyContent: 'space-between'}, create(Text, {bold: true, color: 'yellow'}, `Output: ${activeTask?.name || 'None'}`), create(Text, {dimColor: true}, logOffset ? `Scrolled ${logOffset} lines` : 'Live log view')),
|
|
531
|
+
create(Box, {flexDirection: 'column', borderStyle: 'round', borderColor: 'yellow', padding: 1, minHeight: OUTPUT_WINDOW_HEIGHT, maxHeight: OUTPUT_WINDOW_HEIGHT, height: OUTPUT_WINDOW_HEIGHT, overflow: 'hidden'}, ...logNodes),
|
|
532
|
+
create(Box, {marginTop: 1, flexDirection: 'row', justifyContent: 'space-between'}, create(Text, {dimColor: true}, running ? 'Type to feed stdin; Enter: submit, Ctrl+C: abort.' : 'Run a command or press Shift+T to switch tasks.'), create(Text, {dimColor: true}, `${toggleHint}, Shift+S: Structure Guide`)),
|
|
533
|
+
create(Box, {marginTop: 1, flexDirection: 'row', borderStyle: 'round', borderColor: running ? 'green' : 'gray', paddingX: 1}, create(Text, {bold: true, color: running ? 'green' : 'white'}, running ? ' Stdin buffer ' : ' Input ready '), create(Box, {marginLeft: 1}, create(CursorText, {value: stdinBuffer || (running ? '' : 'Start a command to feed stdin'), cursorIndex: stdinCursor, active: running})))
|
|
883
534
|
),
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
535
|
+
showHelpCards && create(Box, {marginTop: 1, flexDirection: 'row', justifyContent: 'space-between', flexWrap: 'wrap'}, ...helpCards.map((card, idx) => create(Box, {key: card.label, flexGrow: 1, flexBasis: 0, minWidth: HELP_CARD_MIN_WIDTH, marginRight: idx < 2 ? 1 : 0, marginBottom: 1, borderStyle: 'round', borderColor: card.color, padding: 1, flexDirection: 'column'}, create(Text, {color: card.color, bold: true, marginBottom: 1}, card.label), ...card.body.map((line, lidx) => create(Text, {key: lidx, dimColor: card.color === 'yellow'}, line))))),
|
|
536
|
+
showStructureGuide && create(Box, {flexDirection: 'column', borderStyle: 'round', borderColor: 'blue', marginTop: 1, padding: 1}, create(Text, {color: 'cyan', bold: true}, 'Structure guide ยท press Shift+S to hide'), ...SCHEMA_GUIDE.map(e => create(Text, {key: e.type, dimColor: true}, `โข ${e.icon} ${e.label}: ${e.files.join(', ')}`))),
|
|
537
|
+
showHelp && create(Box, {flexDirection: 'column', borderStyle: 'double', borderColor: 'cyan', marginTop: 1, padding: 1}, create(Text, {color: 'cyan', bold: true}, 'Help overlay'), create(Text, null, 'Shift+โ/โ scrolls logs; Shift+X clears; Shift+E exports; Shift+A Studio; Shift+T Tasks; Shift+D Detach.'))
|
|
887
538
|
);
|
|
888
539
|
}
|
|
889
540
|
|
|
@@ -893,17 +544,10 @@ function parseArgs() {
|
|
|
893
544
|
const tokens = process.argv.slice(2);
|
|
894
545
|
for (let i = 0; i < tokens.length; i += 1) {
|
|
895
546
|
const token = tokens[i];
|
|
896
|
-
if ((token === '--dir' || token === '--path') && tokens[i + 1]) {
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
args.mode = tokens[i + 1];
|
|
901
|
-
i += 1;
|
|
902
|
-
} else if (token === '--help' || token === '-h') {
|
|
903
|
-
args.help = true;
|
|
904
|
-
} else if (token === '--studio') {
|
|
905
|
-
args.view = 'studio';
|
|
906
|
-
}
|
|
547
|
+
if ((token === '--dir' || token === '--path') && tokens[i + 1]) { args.root = tokens[i + 1]; i += 1; }
|
|
548
|
+
else if (token === '--mode' && tokens[i + 1]) { args.mode = tokens[i + 1]; i += 1; }
|
|
549
|
+
else if (token === '--help' || token === '-h') args.help = true;
|
|
550
|
+
else if (token === '--studio') args.view = 'studio';
|
|
907
551
|
}
|
|
908
552
|
return args;
|
|
909
553
|
}
|
|
@@ -925,9 +569,11 @@ async function main() {
|
|
|
925
569
|
console.log(' โ / โ Move project focus');
|
|
926
570
|
console.log(' Enter Toggle detail view for selected project');
|
|
927
571
|
console.log(' Shift+A Switch to Omni-Studio (Environment Health)');
|
|
928
|
-
console.log(' Shift+
|
|
572
|
+
console.log(' Shift+T Open Orbit Task Manager (Manage background processes)');
|
|
573
|
+
console.log(' Shift+D Detach from active task (Keep it running in background)');
|
|
574
|
+
console.log(' Shift+X Clear active task output log');
|
|
929
575
|
console.log(' Shift+E Export current logs to a .txt file');
|
|
930
|
-
console.log(' Shift+โ / โ Scroll the output logs
|
|
576
|
+
console.log(' Shift+โ / โ Scroll the output logs');
|
|
931
577
|
console.log(' Shift+Q Quit application');
|
|
932
578
|
console.log('');
|
|
933
579
|
console.log(kleur.bold('Execution shortcuts:'));
|
|
@@ -943,16 +589,11 @@ async function main() {
|
|
|
943
589
|
if (args.mode === 'test') {
|
|
944
590
|
const projects = await discoverProjects(rootPath);
|
|
945
591
|
console.log(`Detected ${projects.length} project(s) under ${rootPath}`);
|
|
946
|
-
projects.forEach((project) => {
|
|
947
|
-
console.log(` โข [${project.type}] ${project.name} (${project.path})`);
|
|
948
|
-
});
|
|
592
|
+
projects.forEach((project) => { console.log(` โข [${project.type}] ${project.name} (${project.path})`); });
|
|
949
593
|
return;
|
|
950
594
|
}
|
|
951
595
|
|
|
952
596
|
render(create(Compass, {rootPath, initialView: args.view || 'navigator'}));
|
|
953
597
|
}
|
|
954
598
|
|
|
955
|
-
main().catch((error) => {
|
|
956
|
-
console.error(error);
|
|
957
|
-
process.exit(1);
|
|
958
|
-
});
|
|
599
|
+
main().catch((error) => { console.error(error); process.exit(1); });
|