project-compass 2.7.1 โ 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 +167 -519
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
|
@@ -172,9 +172,9 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
172
172
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
173
173
|
const [viewMode, setViewMode] = useState('list');
|
|
174
174
|
const [mainView, setMainView] = useState(initialView);
|
|
175
|
-
const [
|
|
175
|
+
const [tasks, setTasks] = useState([]);
|
|
176
|
+
const [activeTaskId, setActiveTaskId] = useState(null);
|
|
176
177
|
const [logOffset, setLogOffset] = useState(0);
|
|
177
|
-
const [running, setRunning] = useState(false);
|
|
178
178
|
const [lastAction, setLastAction] = useState(null);
|
|
179
179
|
const [customMode, setCustomMode] = useState(false);
|
|
180
180
|
const [customInput, setCustomInput] = useState('');
|
|
@@ -187,16 +187,20 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
187
187
|
const [showHelp, setShowHelp] = useState(false);
|
|
188
188
|
const [recentRuns, setRecentRuns] = useState([]);
|
|
189
189
|
const selectedProject = projects[selectedIndex] || null;
|
|
190
|
-
const
|
|
190
|
+
const runningProcessMap = useRef(new Map());
|
|
191
191
|
const lastCommandRef = useRef(null);
|
|
192
192
|
|
|
193
|
-
const
|
|
194
|
-
|
|
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;
|
|
195
199
|
const normalized = typeof line === 'string' ? line : JSON.stringify(line);
|
|
196
200
|
const lines = normalized.split(/\r?\n/).filter(l => l.trim().length > 0);
|
|
197
|
-
const
|
|
198
|
-
return
|
|
199
|
-
});
|
|
201
|
+
const nextLogs = [...t.logs, ...lines];
|
|
202
|
+
return { ...t, logs: nextLogs.length > 500 ? nextLogs.slice(-500) : nextLogs };
|
|
203
|
+
}));
|
|
200
204
|
}, []);
|
|
201
205
|
|
|
202
206
|
const detailCommands = useMemo(() => buildDetailCommands(selectedProject, config), [selectedProject, config]);
|
|
@@ -212,85 +216,71 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
212
216
|
|
|
213
217
|
const runProjectCommand = useCallback(async (commandMeta, targetProject = selectedProject) => {
|
|
214
218
|
const project = targetProject || selectedProject;
|
|
215
|
-
if (!project)
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
if (!commandMeta || !Array.isArray(commandMeta.command) || commandMeta.command.length === 0) {
|
|
219
|
-
addLog(kleur.gray('(no command configured)'));
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
if (running) {
|
|
223
|
-
addLog(kleur.yellow('โ Wait for the current task to finish.'));
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
219
|
+
if (!project) return;
|
|
220
|
+
if (!commandMeta || !Array.isArray(commandMeta.command) || commandMeta.command.length === 0) return;
|
|
226
221
|
|
|
227
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);
|
|
228
234
|
lastCommandRef.current = {project, commandMeta};
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const fullCmd = commandMeta.command;
|
|
232
|
-
addLog(kleur.cyan(`> ${fullCmd.join(' ')}`));
|
|
235
|
+
setLastAction(newTask.name);
|
|
236
|
+
|
|
233
237
|
setRecentRuns((prev) => {
|
|
234
238
|
const entry = {project: project.name, command: commandLabel, time: new Date().toLocaleTimeString()};
|
|
235
239
|
return [entry, ...prev].slice(0, RECENT_RUN_LIMIT);
|
|
236
240
|
});
|
|
237
241
|
|
|
238
242
|
try {
|
|
239
|
-
const subprocess = execa(
|
|
243
|
+
const subprocess = execa(commandMeta.command[0], commandMeta.command.slice(1), {
|
|
240
244
|
cwd: project.path,
|
|
241
245
|
env: process.env,
|
|
242
246
|
stdin: 'pipe'
|
|
243
247
|
});
|
|
244
|
-
|
|
248
|
+
runningProcessMap.current.set(taskId, subprocess);
|
|
245
249
|
|
|
246
|
-
subprocess.stdout?.on('data', (chunk) =>
|
|
247
|
-
|
|
248
|
-
});
|
|
249
|
-
subprocess.stderr?.on('data', (chunk) => {
|
|
250
|
-
addLog(kleur.red(chunk.toString()));
|
|
251
|
-
});
|
|
250
|
+
subprocess.stdout?.on('data', (chunk) => addLogToTask(taskId, chunk.toString()));
|
|
251
|
+
subprocess.stderr?.on('data', (chunk) => addLogToTask(taskId, kleur.red(chunk.toString())));
|
|
252
252
|
|
|
253
253
|
await subprocess;
|
|
254
|
-
|
|
254
|
+
setTasks(prev => prev.map(t => t.id === taskId ? {...t, status: 'finished'} : t));
|
|
255
|
+
addLogToTask(taskId, kleur.green(`โ ${commandLabel} finished`));
|
|
255
256
|
} catch (error) {
|
|
256
|
-
|
|
257
|
+
setTasks(prev => prev.map(t => t.id === taskId ? {...t, status: 'failed'} : t));
|
|
258
|
+
addLogToTask(taskId, kleur.red(`โ ${commandLabel} failed: ${error.shortMessage || error.message}`));
|
|
257
259
|
} finally {
|
|
258
|
-
|
|
259
|
-
setStdinBuffer('');
|
|
260
|
-
setStdinCursor(0);
|
|
261
|
-
runningProcessRef.current = null;
|
|
260
|
+
runningProcessMap.current.delete(taskId);
|
|
262
261
|
}
|
|
263
|
-
}, [
|
|
262
|
+
}, [addLogToTask, selectedProject]);
|
|
264
263
|
|
|
265
264
|
const handleAddCustomCommand = useCallback((label, commandTokens) => {
|
|
266
|
-
if (!selectedProject)
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
265
|
+
if (!selectedProject) return;
|
|
269
266
|
setConfig((prev) => {
|
|
270
267
|
const projectKey = selectedProject.path;
|
|
271
268
|
const existing = prev.customCommands?.[projectKey] || [];
|
|
272
|
-
const nextCustom = [...existing, {label, command: commandTokens}];
|
|
273
269
|
const nextConfig = {
|
|
274
270
|
...prev,
|
|
275
271
|
customCommands: {
|
|
276
272
|
...prev.customCommands,
|
|
277
|
-
[projectKey]:
|
|
273
|
+
[projectKey]: [...existing, {label, command: commandTokens}]
|
|
278
274
|
}
|
|
279
275
|
};
|
|
280
276
|
saveConfig(nextConfig);
|
|
281
277
|
return nextConfig;
|
|
282
278
|
});
|
|
283
|
-
|
|
284
|
-
}, [selectedProject, addLog]);
|
|
279
|
+
}, [selectedProject]);
|
|
285
280
|
|
|
286
281
|
const handleCustomSubmit = useCallback(() => {
|
|
287
282
|
const raw = customInput.trim();
|
|
288
|
-
if (!selectedProject) {
|
|
289
|
-
setCustomMode(false);
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
if (!raw) {
|
|
293
|
-
addLog(kleur.gray('Canceled custom command (empty).'));
|
|
283
|
+
if (!selectedProject || !raw) {
|
|
294
284
|
setCustomMode(false);
|
|
295
285
|
setCustomInput('');
|
|
296
286
|
setCustomCursor(0);
|
|
@@ -299,44 +289,33 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
299
289
|
const [labelPart, commandPart] = raw.split('|');
|
|
300
290
|
const commandTokens = (commandPart || labelPart).trim().split(/\s+/).filter(Boolean);
|
|
301
291
|
if (!commandTokens.length) {
|
|
302
|
-
addLog(kleur.red('Custom command needs at least one token.'));
|
|
303
292
|
setCustomMode(false);
|
|
304
293
|
setCustomInput('');
|
|
305
294
|
setCustomCursor(0);
|
|
306
295
|
return;
|
|
307
296
|
}
|
|
308
297
|
const label = commandPart ? labelPart.trim() : `Custom ${selectedProject.name}`;
|
|
309
|
-
handleAddCustomCommand(label
|
|
298
|
+
handleAddCustomCommand(label, commandTokens);
|
|
310
299
|
setCustomMode(false);
|
|
311
300
|
setCustomInput('');
|
|
312
301
|
setCustomCursor(0);
|
|
313
|
-
}, [customInput, selectedProject, handleAddCustomCommand
|
|
302
|
+
}, [customInput, selectedProject, handleAddCustomCommand]);
|
|
314
303
|
|
|
315
304
|
const exportLogs = useCallback(() => {
|
|
316
|
-
if (!
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
305
|
+
if (!activeTask || !activeTask.logs.length) return;
|
|
319
306
|
try {
|
|
320
|
-
const exportPath = path.resolve(process.cwd(), `compass
|
|
321
|
-
fs.writeFileSync(exportPath,
|
|
322
|
-
|
|
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}`));
|
|
323
310
|
} catch (err) {
|
|
324
|
-
|
|
311
|
+
addLogToTask(activeTaskId, kleur.red(`โ Export failed: ${err.message}`));
|
|
325
312
|
}
|
|
326
|
-
}, [
|
|
313
|
+
}, [activeTask, activeTaskId, addLogToTask]);
|
|
327
314
|
|
|
328
315
|
useInput((input, key) => {
|
|
329
316
|
if (customMode) {
|
|
330
|
-
if (key.return) {
|
|
331
|
-
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
if (key.escape) {
|
|
335
|
-
setCustomMode(false);
|
|
336
|
-
setCustomInput('');
|
|
337
|
-
setCustomCursor(0);
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
317
|
+
if (key.return) { handleCustomSubmit(); return; }
|
|
318
|
+
if (key.escape) { setCustomMode(false); setCustomInput(''); setCustomCursor(0); return; }
|
|
340
319
|
if (key.backspace || key.delete) {
|
|
341
320
|
if (customCursor > 0) {
|
|
342
321
|
setCustomInput((prev) => prev.slice(0, customCursor - 1) + prev.slice(customCursor));
|
|
@@ -344,14 +323,8 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
344
323
|
}
|
|
345
324
|
return;
|
|
346
325
|
}
|
|
347
|
-
if (key.leftArrow) {
|
|
348
|
-
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
if (key.rightArrow) {
|
|
352
|
-
setCustomCursor(c => Math.min(customInput.length, c + 1));
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
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; }
|
|
355
328
|
if (input) {
|
|
356
329
|
setCustomInput((prev) => prev.slice(0, customCursor) + input + prev.slice(customCursor));
|
|
357
330
|
setCustomCursor(c => c + input.length);
|
|
@@ -360,51 +333,28 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
360
333
|
}
|
|
361
334
|
|
|
362
335
|
const normalizedInput = input?.toLowerCase();
|
|
363
|
-
const ctrlCombo = (char) => key.ctrl && normalizedInput === char;
|
|
364
336
|
const shiftCombo = (char) => key.shift && normalizedInput === char;
|
|
365
|
-
|
|
366
|
-
if (
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
if (
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}
|
|
374
|
-
if (toggleShortcut('a')) {
|
|
375
|
-
setMainView((prev) => (prev === 'navigator' ? 'studio' : 'navigator'));
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
if (toggleShortcut('x')) {
|
|
379
|
-
setLogLines([]);
|
|
380
|
-
setLogOffset(0);
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
if (shiftCombo('e')) {
|
|
384
|
-
exportLogs();
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
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; }
|
|
387
345
|
|
|
388
346
|
const scrollLogs = (delta) => {
|
|
389
347
|
setLogOffset((prev) => {
|
|
390
|
-
const
|
|
348
|
+
const logs = activeTask?.logs || [];
|
|
349
|
+
const maxScroll = Math.max(0, logs.length - OUTPUT_WINDOW_SIZE);
|
|
391
350
|
return Math.max(0, Math.min(maxScroll, prev + delta));
|
|
392
351
|
});
|
|
393
352
|
};
|
|
394
353
|
|
|
395
|
-
if (running &&
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
setStdinCursor(0);
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
if (key.return) {
|
|
403
|
-
runningProcessRef.current.stdin?.write(stdinBuffer + '\n');
|
|
404
|
-
setStdinBuffer('');
|
|
405
|
-
setStdinCursor(0);
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
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; }
|
|
408
358
|
if (key.backspace || key.delete) {
|
|
409
359
|
if (stdinCursor > 0) {
|
|
410
360
|
setStdinBuffer(prev => prev.slice(0, stdinCursor - 1) + prev.slice(stdinCursor));
|
|
@@ -412,14 +362,8 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
412
362
|
}
|
|
413
363
|
return;
|
|
414
364
|
}
|
|
415
|
-
if (key.leftArrow) {
|
|
416
|
-
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
if (key.rightArrow) {
|
|
420
|
-
setStdinCursor(c => Math.min(stdinBuffer.length, c + 1));
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
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; }
|
|
423
367
|
if (input) {
|
|
424
368
|
setStdinBuffer(prev => prev.slice(0, stdinCursor) + input + prev.slice(stdinCursor));
|
|
425
369
|
setStdinCursor(c => c + input.length);
|
|
@@ -427,49 +371,29 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
427
371
|
return;
|
|
428
372
|
}
|
|
429
373
|
|
|
430
|
-
if (key.shift && key.upArrow) {
|
|
431
|
-
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
if (key.shift && key.downArrow) {
|
|
435
|
-
scrollLogs(1);
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
374
|
+
if (key.shift && key.upArrow) { scrollLogs(-1); return; }
|
|
375
|
+
if (key.shift && key.downArrow) { scrollLogs(1); return; }
|
|
438
376
|
|
|
439
|
-
if (normalizedInput === '?') {
|
|
440
|
-
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
if (shiftCombo('l') && lastCommandRef.current) {
|
|
444
|
-
runProjectCommand(lastCommandRef.current.commandMeta, lastCommandRef.current.project);
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
377
|
+
if (normalizedInput === '?') { setShowHelp((prev) => !prev); return; }
|
|
378
|
+
if (shiftCombo('l') && lastCommandRef.current) { runProjectCommand(lastCommandRef.current.commandMeta, lastCommandRef.current.project); return; }
|
|
447
379
|
|
|
448
|
-
if (
|
|
449
|
-
|
|
450
|
-
return;
|
|
451
|
-
|
|
452
|
-
if (key.downArrow && !key.shift && projects.length > 0) {
|
|
453
|
-
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; }
|
|
454
384
|
return;
|
|
455
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; }
|
|
456
389
|
if (key.return) {
|
|
457
|
-
if (!selectedProject)
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
390
|
+
if (!selectedProject) return;
|
|
460
391
|
setViewMode((prev) => (prev === 'detail' ? 'list' : 'detail'));
|
|
461
392
|
return;
|
|
462
393
|
}
|
|
463
|
-
if (shiftCombo('q')) {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
if (shiftCombo('c') && viewMode === 'detail' && selectedProject) {
|
|
468
|
-
setCustomMode(true);
|
|
469
|
-
setCustomInput('');
|
|
470
|
-
setCustomCursor(0);
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
394
|
+
if (shiftCombo('q')) { exit(); return; }
|
|
395
|
+
if (shiftCombo('c') && viewMode === 'detail' && selectedProject) { setCustomMode(true); setCustomInput(''); setCustomCursor(0); return; }
|
|
396
|
+
|
|
473
397
|
const actionKey = normalizedInput && ACTION_MAP[normalizedInput];
|
|
474
398
|
if (actionKey) {
|
|
475
399
|
const commandMeta = selectedProject?.commands?.[actionKey];
|
|
@@ -481,18 +405,30 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
481
405
|
}
|
|
482
406
|
});
|
|
483
407
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
if (
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
+
);
|
|
494
424
|
}
|
|
495
|
-
|
|
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) {
|
|
496
432
|
projects.forEach((project, index) => {
|
|
497
433
|
const isSelected = index === selectedIndex;
|
|
498
434
|
const frameworkBadges = (project.frameworks || []).map((frame) => `${frame.icon} ${frame.name}`).join(', ');
|
|
@@ -504,14 +440,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
504
440
|
create(
|
|
505
441
|
Box,
|
|
506
442
|
{flexDirection: 'row'},
|
|
507
|
-
create(
|
|
508
|
-
Text,
|
|
509
|
-
{
|
|
510
|
-
color: isSelected ? 'cyan' : 'white',
|
|
511
|
-
bold: isSelected
|
|
512
|
-
},
|
|
513
|
-
`${project.icon} ${project.name}`
|
|
514
|
-
),
|
|
443
|
+
create(Text, {color: isSelected ? 'cyan' : 'white', bold: isSelected}, `${project.icon} ${project.name}`),
|
|
515
444
|
hasMissingRuntime && create(Text, {color: 'red', bold: true}, ' โ ๏ธ Runtime missing')
|
|
516
445
|
),
|
|
517
446
|
create(Text, {dimColor: true}, ` ${project.type} ยท ${path.relative(rootPath, project.path) || '.'}`),
|
|
@@ -524,359 +453,88 @@ function Compass({rootPath, initialView = 'navigator'}) {
|
|
|
524
453
|
const detailContent = [];
|
|
525
454
|
if (viewMode === 'detail' && selectedProject) {
|
|
526
455
|
detailContent.push(
|
|
527
|
-
create(
|
|
528
|
-
Box,
|
|
529
|
-
{key: 'title-row', flexDirection: 'row'},
|
|
456
|
+
create(Box, {key: 'title-row', flexDirection: 'row'},
|
|
530
457
|
create(Text, {color: 'cyan', bold: true}, `${selectedProject.icon} ${selectedProject.name}`),
|
|
531
458
|
selectedProject.missingBinaries && selectedProject.missingBinaries.length > 0 && create(Text, {color: 'red', bold: true}, ' โ ๏ธ MISSING RUNTIME')
|
|
532
459
|
),
|
|
533
460
|
create(Text, {key: 'manifest', dimColor: true}, `${selectedProject.type} ยท ${selectedProject.manifest || 'detected manifest'}`),
|
|
534
461
|
create(Text, {key: 'loc', dimColor: true}, `Location: ${path.relative(rootPath, selectedProject.path) || '.'}`)
|
|
535
462
|
);
|
|
536
|
-
if (selectedProject.description) {
|
|
537
|
-
detailContent.push(create(Text, {key: 'desc'}, selectedProject.description));
|
|
538
|
-
}
|
|
463
|
+
if (selectedProject.description) detailContent.push(create(Text, {key: 'desc'}, selectedProject.description));
|
|
539
464
|
const frameworks = (selectedProject.frameworks || []).map((lib) => `${lib.icon} ${lib.name}`).join(', ');
|
|
540
|
-
if (frameworks) {
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
if (selectedProject.extra?.scripts && selectedProject.extra.scripts.length) {
|
|
544
|
-
detailContent.push(create(Text, {key: 'scripts', dimColor: true}, `Scripts: ${selectedProject.extra.scripts.join(', ')}`));
|
|
545
|
-
}
|
|
546
|
-
|
|
465
|
+
if (frameworks) detailContent.push(create(Text, {key: 'frames', dimColor: true}, `Frameworks: ${frameworks}`));
|
|
466
|
+
|
|
547
467
|
if (selectedProject.missingBinaries && selectedProject.missingBinaries.length > 0) {
|
|
548
468
|
detailContent.push(
|
|
549
|
-
create(Text, {key: '
|
|
550
|
-
create(Text, {key: '
|
|
551
|
-
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(', ')}`)
|
|
552
471
|
);
|
|
553
472
|
}
|
|
554
473
|
|
|
555
|
-
detailContent.push(create(Text, {key: 'config-path', dimColor: true, marginTop: 1}, `Custom commands stored in ${CONFIG_PATH}`));
|
|
556
|
-
detailContent.push(create(Text, {key: 'plugin-path', dimColor: true, marginBottom: 1}, `Extend frameworks via ${PLUGIN_FILE}`));
|
|
557
474
|
detailContent.push(create(Text, {key: 'cmd-header', bold: true, marginTop: 1}, 'Commands'));
|
|
558
475
|
detailedIndexed.forEach((command) => {
|
|
559
476
|
detailContent.push(
|
|
560
|
-
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(' ')}`)
|
|
561
479
|
);
|
|
562
|
-
detailContent.push(create(Text, {key: `detail-line-${command.shortcut}-${command.label}`, dimColor: true}, ` โณ ${command.command.join(' ')}`));
|
|
563
480
|
});
|
|
564
|
-
|
|
565
|
-
detailContent.push(create(Text, {key: 'no-cmds', dimColor: true}, 'No built-in commands yet. Add a custom command with Shift+C.'));
|
|
566
|
-
}
|
|
567
|
-
const setupHints = selectedProject.extra?.setupHints || [];
|
|
568
|
-
if (setupHints.length) {
|
|
569
|
-
detailContent.push(create(Text, {key: 'setup-header', dimColor: true, marginTop: 1}, 'Setup hints:'));
|
|
570
|
-
setupHints.forEach((hint, hidx) => detailContent.push(create(Text, {key: `hint-${hidx}`, dimColor: true}, ` โข ${hint}`)));
|
|
571
|
-
}
|
|
572
|
-
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.'));
|
|
573
482
|
} else {
|
|
574
|
-
detailContent.push(create(Text, {key: '
|
|
483
|
+
detailContent.push(create(Text, {key: 'e-h', dimColor: true}, 'Press Enter on a project to reveal details.'));
|
|
575
484
|
}
|
|
576
485
|
|
|
577
486
|
if (customMode) {
|
|
578
|
-
detailContent.push(
|
|
579
|
-
create(
|
|
580
|
-
Box,
|
|
581
|
-
{key: 'custom-input-box', flexDirection: 'row'},
|
|
582
|
-
create(Text, {color: 'cyan'}, 'Type label|cmd (Enter: save, Esc: cancel): '),
|
|
583
|
-
create(CursorText, {value: customInput, cursorIndex: customCursor})
|
|
584
|
-
)
|
|
585
|
-
);
|
|
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})));
|
|
586
488
|
}
|
|
587
489
|
|
|
588
|
-
const artTileNodes =
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
{
|
|
603
|
-
label: 'Focus',
|
|
604
|
-
detail: selectedName,
|
|
605
|
-
accent: 'cyan',
|
|
606
|
-
icon: 'โ',
|
|
607
|
-
subtext: `${selectedType} ยท ${selectedLocation}`
|
|
608
|
-
},
|
|
609
|
-
{
|
|
610
|
-
label: 'Rhythm',
|
|
611
|
-
detail: `${detailCommands.length} commands`,
|
|
612
|
-
accent: 'yellow',
|
|
613
|
-
icon: 'โ ',
|
|
614
|
-
subtext: statusNarrative
|
|
615
|
-
}
|
|
616
|
-
];
|
|
617
|
-
return tileDefinition.map((tile) =>
|
|
618
|
-
create(
|
|
619
|
-
Box,
|
|
620
|
-
{
|
|
621
|
-
key: tile.label,
|
|
622
|
-
flexDirection: 'column',
|
|
623
|
-
padding: 1,
|
|
624
|
-
marginRight: 1,
|
|
625
|
-
borderStyle: 'single',
|
|
626
|
-
borderColor: tile.accent,
|
|
627
|
-
minWidth: 24
|
|
628
|
-
},
|
|
629
|
-
create(Text, {color: tile.accent, bold: true}, `${tile.icon} ${tile.label}`),
|
|
630
|
-
create(Text, {bold: true}, tile.detail),
|
|
631
|
-
create(Text, {dimColor: true}, tile.subtext)
|
|
632
|
-
)
|
|
633
|
-
);
|
|
634
|
-
}, [projectCountLabel, rootPath, selectedProject, detailCommands.length, running, lastAction]);
|
|
635
|
-
|
|
636
|
-
const artBoard = create(
|
|
637
|
-
Box,
|
|
638
|
-
{
|
|
639
|
-
flexDirection: 'column',
|
|
640
|
-
marginTop: 1,
|
|
641
|
-
borderStyle: 'round',
|
|
642
|
-
borderColor: 'gray',
|
|
643
|
-
padding: 1
|
|
644
|
-
},
|
|
645
|
-
create(
|
|
646
|
-
Box,
|
|
647
|
-
{flexDirection: 'row', justifyContent: 'space-between'},
|
|
648
|
-
create(Text, {color: 'magenta', bold: true}, 'Art-coded build atlas'),
|
|
649
|
-
create(Text, {dimColor: true}, 'press ? for overlay help')
|
|
650
|
-
),
|
|
651
|
-
create(
|
|
652
|
-
Box,
|
|
653
|
-
{flexDirection: 'row', marginTop: 1},
|
|
654
|
-
...ART_CHARS.map((char, index) =>
|
|
655
|
-
create(Text, {key: `art-${index}`, color: ART_COLORS[index % ART_COLORS.length]}, char.repeat(2))
|
|
656
|
-
)
|
|
657
|
-
),
|
|
658
|
-
create(
|
|
659
|
-
Box,
|
|
660
|
-
{flexDirection: 'row', marginTop: 1},
|
|
661
|
-
...artTileNodes
|
|
662
|
-
),
|
|
663
|
-
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)
|
|
664
504
|
);
|
|
665
505
|
|
|
666
|
-
const
|
|
667
|
-
const
|
|
668
|
-
const
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
: [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.')];
|
|
672
511
|
|
|
673
512
|
const helpCards = [
|
|
674
|
-
{
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
body: [
|
|
678
|
-
'โ / โ move focus, Enter: details',
|
|
679
|
-
'Shift+โ / โ scroll output',
|
|
680
|
-
'Shift+H toggle help cards',
|
|
681
|
-
'? opens the overlay help'
|
|
682
|
-
]
|
|
683
|
-
},
|
|
684
|
-
{
|
|
685
|
-
label: 'Command flow',
|
|
686
|
-
color: 'cyan',
|
|
687
|
-
body: [
|
|
688
|
-
'B / T / R build/test/run',
|
|
689
|
-
'1-9 run detail commands',
|
|
690
|
-
'Shift+L rerun last command',
|
|
691
|
-
'Shift+X clear / Shift+E export'
|
|
692
|
-
]
|
|
693
|
-
},
|
|
694
|
-
{
|
|
695
|
-
label: 'System & Studio',
|
|
696
|
-
color: 'yellow',
|
|
697
|
-
body: [
|
|
698
|
-
'Shift+A open Omni-Studio',
|
|
699
|
-
'Shift+S toggle structure guide',
|
|
700
|
-
'Shift+C save custom action',
|
|
701
|
-
'Shift+Q quit application'
|
|
702
|
-
]
|
|
703
|
-
}
|
|
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']}
|
|
704
516
|
];
|
|
705
|
-
const helpSection = showHelpCards
|
|
706
|
-
? create(
|
|
707
|
-
Box,
|
|
708
|
-
{marginTop: 1, flexDirection: 'row', justifyContent: 'space-between', flexWrap: 'wrap'},
|
|
709
|
-
...helpCards.map((card, index) =>
|
|
710
|
-
create(
|
|
711
|
-
Box,
|
|
712
|
-
{
|
|
713
|
-
key: card.label,
|
|
714
|
-
flexGrow: 1,
|
|
715
|
-
flexBasis: 0,
|
|
716
|
-
minWidth: HELP_CARD_MIN_WIDTH,
|
|
717
|
-
marginRight: index < helpCards.length - 1 ? 1 : 0,
|
|
718
|
-
marginBottom: 1,
|
|
719
|
-
borderStyle: 'round',
|
|
720
|
-
borderColor: card.color,
|
|
721
|
-
padding: 1,
|
|
722
|
-
flexDirection: 'column'
|
|
723
|
-
},
|
|
724
|
-
create(Text, {color: card.color, bold: true, marginBottom: 1}, card.label),
|
|
725
|
-
...card.body.map((line, lineIndex) =>
|
|
726
|
-
create(Text, {key: `${card.label}-${lineIndex}`, dimColor: card.color === 'yellow'}, line)
|
|
727
|
-
)
|
|
728
|
-
)
|
|
729
|
-
)
|
|
730
|
-
)
|
|
731
|
-
: create(Text, {key: 'help-hint', dimColor: true, marginTop: 1}, 'Help cards hidden ยท press Shift+H to show navigation, command flow, and recent runs.');
|
|
732
|
-
|
|
733
|
-
const structureGuide = showStructureGuide
|
|
734
|
-
? create(
|
|
735
|
-
Box,
|
|
736
|
-
{
|
|
737
|
-
flexDirection: 'column',
|
|
738
|
-
borderStyle: 'round',
|
|
739
|
-
borderColor: 'blue',
|
|
740
|
-
marginTop: 1,
|
|
741
|
-
padding: 1
|
|
742
|
-
},
|
|
743
|
-
create(Text, {color: 'cyan', bold: true}, 'Project structure guide ยท press Shift+S to hide'),
|
|
744
|
-
...SCHEMA_GUIDE.map((entry) =>
|
|
745
|
-
create(Text, {key: entry.type, dimColor: true}, `โข ${entry.icon} ${entry.label}: ${entry.files.join(', ')}`)
|
|
746
|
-
),
|
|
747
|
-
create(Text, {dimColor: true, marginTop: 1}, 'SCHEMAS describe the manifests that trigger each language type.')
|
|
748
|
-
)
|
|
749
|
-
: null;
|
|
750
|
-
|
|
751
|
-
const helpOverlay = showHelp
|
|
752
|
-
? create(
|
|
753
|
-
Box,
|
|
754
|
-
{
|
|
755
|
-
flexDirection: 'column',
|
|
756
|
-
borderStyle: 'double',
|
|
757
|
-
borderColor: 'cyan',
|
|
758
|
-
marginTop: 1,
|
|
759
|
-
padding: 1
|
|
760
|
-
},
|
|
761
|
-
create(Text, {color: 'cyan', bold: true}, 'Help overlay ยท press ? to hide'),
|
|
762
|
-
create(Text, null, 'Shift+โ/โ scrolls logs; Shift+X clears; Shift+E exports to file; Shift+A Omni-Studio.'),
|
|
763
|
-
create(Text, null, 'B/T/R run build/test/run; 1-9 detail commands; Shift+L reruns previous command.'),
|
|
764
|
-
create(Text, null, 'Shift+H help cards, Shift+S structure guide, ? overlay, Shift+Q quits.'),
|
|
765
|
-
create(Text, null, 'Projects + Details stay paired while Output keeps its own full-width band.'),
|
|
766
|
-
create(Text, null, 'Structure guide lists the manifests that trigger each language detection.')
|
|
767
|
-
)
|
|
768
|
-
: null;
|
|
769
|
-
|
|
770
|
-
if (mainView === 'studio') {
|
|
771
|
-
return create(Studio);
|
|
772
|
-
}
|
|
773
517
|
|
|
774
|
-
const toggleHint = showHelpCards ? 'Shift+H
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
return create(
|
|
780
|
-
Box,
|
|
781
|
-
{flexDirection: 'column', padding: 1},
|
|
782
|
-
create(
|
|
783
|
-
Box,
|
|
784
|
-
{justifyContent: 'space-between'},
|
|
785
|
-
create(
|
|
786
|
-
Box,
|
|
787
|
-
{flexDirection: 'column'},
|
|
788
|
-
create(Text, {color: 'magenta', bold: true}, 'Project Compass'),
|
|
789
|
-
create(Text, {dimColor: true}, loading ? 'Scanning workspacesโฆ' : `${projectCountLabel} detected in ${rootPath}`),
|
|
790
|
-
create(Text, {dimColor: true}, 'Use keyboard arrows to navigate, ? for help.')
|
|
791
|
-
),
|
|
792
|
-
create(
|
|
793
|
-
Box,
|
|
794
|
-
{flexDirection: 'column', alignItems: 'flex-end'},
|
|
795
|
-
create(Text, {color: running ? 'yellow' : 'green'}, running ? 'Busy streaming...' : lastAction ? `Last action ยท ${lastAction}` : 'Idle'),
|
|
796
|
-
create(Text, {dimColor: true}, headerHint)
|
|
797
|
-
)
|
|
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`))
|
|
798
523
|
),
|
|
799
524
|
artBoard,
|
|
800
|
-
create(
|
|
801
|
-
Box,
|
|
802
|
-
{
|
|
803
|
-
create(
|
|
804
|
-
Box,
|
|
805
|
-
{
|
|
806
|
-
flexGrow: 1,
|
|
807
|
-
flexBasis: 0,
|
|
808
|
-
flexShrink: 1,
|
|
809
|
-
minWidth: PROJECTS_MIN_WIDTH,
|
|
810
|
-
marginRight: 1,
|
|
811
|
-
borderStyle: 'round',
|
|
812
|
-
borderColor: 'magenta',
|
|
813
|
-
padding: 1
|
|
814
|
-
},
|
|
815
|
-
create(Text, {bold: true, color: 'magenta'}, 'Projects'),
|
|
816
|
-
create(Box, {flexDirection: 'column', marginTop: 1}, ...projectRows)
|
|
817
|
-
),
|
|
818
|
-
create(
|
|
819
|
-
Box,
|
|
820
|
-
{
|
|
821
|
-
flexGrow: 1.3,
|
|
822
|
-
flexBasis: 0,
|
|
823
|
-
flexShrink: 1,
|
|
824
|
-
minWidth: DETAILS_MIN_WIDTH,
|
|
825
|
-
borderStyle: 'round',
|
|
826
|
-
borderColor: 'cyan',
|
|
827
|
-
padding: 1,
|
|
828
|
-
flexDirection: 'column'
|
|
829
|
-
},
|
|
830
|
-
create(Text, {bold: true, color: 'cyan'}, 'Details'),
|
|
831
|
-
...detailContent
|
|
832
|
-
)
|
|
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)
|
|
833
528
|
),
|
|
834
|
-
create(
|
|
835
|
-
Box,
|
|
836
|
-
{
|
|
837
|
-
create(
|
|
838
|
-
|
|
839
|
-
{flexDirection: 'row', justifyContent: 'space-between'},
|
|
840
|
-
create(Text, {bold: true, color: 'yellow'}, `Output ${running ? 'ยท Running' : ''}`),
|
|
841
|
-
create(Text, {dimColor: true}, logOffset ? `Scrolled ${logOffset} lines` : 'Live log view')
|
|
842
|
-
),
|
|
843
|
-
create(
|
|
844
|
-
Box,
|
|
845
|
-
{
|
|
846
|
-
flexDirection: 'column',
|
|
847
|
-
borderStyle: 'round',
|
|
848
|
-
borderColor: 'yellow',
|
|
849
|
-
padding: 1,
|
|
850
|
-
minHeight: OUTPUT_WINDOW_HEIGHT,
|
|
851
|
-
maxHeight: OUTPUT_WINDOW_HEIGHT,
|
|
852
|
-
height: OUTPUT_WINDOW_HEIGHT,
|
|
853
|
-
overflow: 'hidden',
|
|
854
|
-
flexShrink: 0
|
|
855
|
-
},
|
|
856
|
-
...logNodes
|
|
857
|
-
),
|
|
858
|
-
create(
|
|
859
|
-
Box,
|
|
860
|
-
{marginTop: 1, flexDirection: 'row', justifyContent: 'space-between'},
|
|
861
|
-
create(Text, {dimColor: true}, running ? 'Type to feed stdin; Enter submits, Ctrl+C aborts.' : 'Run a command or press ? for extra help.'),
|
|
862
|
-
create(Text, {dimColor: true}, `${toggleHint}, Shift+S toggles the structure guide`)
|
|
863
|
-
),
|
|
864
|
-
create(
|
|
865
|
-
Box,
|
|
866
|
-
{
|
|
867
|
-
marginTop: 1,
|
|
868
|
-
flexDirection: 'row',
|
|
869
|
-
borderStyle: 'round',
|
|
870
|
-
borderColor: running ? 'green' : 'gray',
|
|
871
|
-
paddingX: 1
|
|
872
|
-
},
|
|
873
|
-
create(Text, {bold: true, color: running ? 'green' : 'white'}, running ? ' Stdin buffer ' : ' Input ready '),
|
|
874
|
-
create(Box, {marginLeft: 1}, create(CursorText, {value: stdinBuffer || (running ? '' : 'Start a command to feed stdin'), cursorIndex: stdinCursor, active: running}))
|
|
875
|
-
)
|
|
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})))
|
|
876
534
|
),
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
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.'))
|
|
880
538
|
);
|
|
881
539
|
}
|
|
882
540
|
|
|
@@ -886,17 +544,10 @@ function parseArgs() {
|
|
|
886
544
|
const tokens = process.argv.slice(2);
|
|
887
545
|
for (let i = 0; i < tokens.length; i += 1) {
|
|
888
546
|
const token = tokens[i];
|
|
889
|
-
if ((token === '--dir' || token === '--path') && tokens[i + 1]) {
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
args.mode = tokens[i + 1];
|
|
894
|
-
i += 1;
|
|
895
|
-
} else if (token === '--help' || token === '-h') {
|
|
896
|
-
args.help = true;
|
|
897
|
-
} else if (token === '--studio') {
|
|
898
|
-
args.view = 'studio';
|
|
899
|
-
}
|
|
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';
|
|
900
551
|
}
|
|
901
552
|
return args;
|
|
902
553
|
}
|
|
@@ -918,9 +569,11 @@ async function main() {
|
|
|
918
569
|
console.log(' โ / โ Move project focus');
|
|
919
570
|
console.log(' Enter Toggle detail view for selected project');
|
|
920
571
|
console.log(' Shift+A Switch to Omni-Studio (Environment Health)');
|
|
921
|
-
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');
|
|
922
575
|
console.log(' Shift+E Export current logs to a .txt file');
|
|
923
|
-
console.log(' Shift+โ / โ Scroll the output logs
|
|
576
|
+
console.log(' Shift+โ / โ Scroll the output logs');
|
|
924
577
|
console.log(' Shift+Q Quit application');
|
|
925
578
|
console.log('');
|
|
926
579
|
console.log(kleur.bold('Execution shortcuts:'));
|
|
@@ -936,16 +589,11 @@ async function main() {
|
|
|
936
589
|
if (args.mode === 'test') {
|
|
937
590
|
const projects = await discoverProjects(rootPath);
|
|
938
591
|
console.log(`Detected ${projects.length} project(s) under ${rootPath}`);
|
|
939
|
-
projects.forEach((project) => {
|
|
940
|
-
console.log(` โข [${project.type}] ${project.name} (${project.path})`);
|
|
941
|
-
});
|
|
592
|
+
projects.forEach((project) => { console.log(` โข [${project.type}] ${project.name} (${project.path})`); });
|
|
942
593
|
return;
|
|
943
594
|
}
|
|
944
595
|
|
|
945
596
|
render(create(Compass, {rootPath, initialView: args.view || 'navigator'}));
|
|
946
597
|
}
|
|
947
598
|
|
|
948
|
-
main().catch((error) => {
|
|
949
|
-
console.error(error);
|
|
950
|
-
process.exit(1);
|
|
951
|
-
});
|
|
599
|
+
main().catch((error) => { console.error(error); process.exit(1); });
|