project-compass 2.8.2 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +120 -69
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-compass",
3
- "version": "2.8.2",
3
+ "version": "2.9.1",
4
4
  "description": "Ink-based project explorer that detects local repos and lets you build/test/run them without memorizing commands.",
5
5
  "main": "src/cli.js",
6
6
  "type": "module",
package/src/cli.js CHANGED
@@ -9,7 +9,6 @@ import {discoverProjects, SCHEMA_GUIDE, checkBinary} from './projectDetection.js
9
9
  import {CONFIG_PATH, ensureConfigDir} from './configPaths.js';
10
10
 
11
11
  const create = React.createElement;
12
- const DEFAULT_CONFIG = {customCommands: {}};
13
12
  const ART_CHARS = ['▁', '▃', '▄', '▅', '▇'];
14
13
  const ART_COLORS = ['magenta', 'blue', 'cyan', 'yellow', 'red'];
15
14
  const OUTPUT_WINDOW_SIZE = 8;
@@ -38,18 +37,15 @@ function loadConfig() {
38
37
  const payload = fs.readFileSync(CONFIG_PATH, 'utf-8');
39
38
  const parsed = JSON.parse(payload || '{}');
40
39
  return {
41
- ...DEFAULT_CONFIG,
40
+ customCommands: {},
41
+ showArtBoard: true,
42
42
  ...parsed,
43
- customCommands: {
44
- ...DEFAULT_CONFIG.customCommands,
45
- ...(parsed.customCommands || {})
46
- }
47
43
  };
48
44
  }
49
45
  } catch (error) {
50
46
  console.error(`Ignoring corrupt config: ${error.message}`);
51
47
  }
52
- return {...DEFAULT_CONFIG};
48
+ return {customCommands: {}, showArtBoard: true};
53
49
  }
54
50
 
55
51
  function useScanner(rootPath) {
@@ -78,9 +74,7 @@ function useScanner(rootPath) {
78
74
  }
79
75
 
80
76
  function buildDetailCommands(project, config) {
81
- if (!project) {
82
- return [];
83
- }
77
+ if (!project) return [];
84
78
  const builtins = Object.entries(project.commands || {}).map(([key, command]) => ({
85
79
  label: command.label || key,
86
80
  command: command.command,
@@ -177,6 +171,10 @@ function Compass({rootPath, initialView = 'navigator'}) {
177
171
  const [customMode, setCustomMode] = useState(false);
178
172
  const [customInput, setCustomInput] = useState('');
179
173
  const [customCursor, setCustomCursor] = useState(0);
174
+ const [renameMode, setRenameMode] = useState(false);
175
+ const [renameInput, setRenameInput] = useState('');
176
+ const [renameCursor, setRenameCursor] = useState(0);
177
+ const [quitConfirm, setQuitConfirm] = useState(false);
180
178
  const [config, setConfig] = useState(() => loadConfig());
181
179
  const [showHelpCards, setShowHelpCards] = useState(false);
182
180
  const [showStructureGuide, setShowStructureGuide] = useState(false);
@@ -189,6 +187,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
189
187
 
190
188
  const activeTask = useMemo(() => tasks.find(t => t.id === activeTaskId), [tasks, activeTaskId]);
191
189
  const running = activeTask?.status === 'running';
190
+ const hasRunningTasks = useMemo(() => tasks.some(t => t.status === 'running'), [tasks]);
192
191
 
193
192
  const addLogToTask = useCallback((taskId, line) => {
194
193
  setTasks(prev => prev.map(t => {
@@ -200,17 +199,24 @@ function Compass({rootPath, initialView = 'navigator'}) {
200
199
  }));
201
200
  }, []);
202
201
 
203
- const detailCommands = useMemo(() => buildDetailCommands(selectedProject, config), [selectedProject, config]);
204
- const detailedIndexed = useMemo(() => detailCommands.map((command, index) => ({
202
+ const detailedIndexed = useMemo(() => buildDetailCommands(selectedProject, config).map((command, index) => ({
205
203
  ...command,
206
204
  shortcut: `${index + 1}`
207
- })), [detailCommands]);
205
+ })), [selectedProject, config]);
206
+
208
207
  const detailShortcutMap = useMemo(() => {
209
208
  const map = new Map();
210
209
  detailedIndexed.forEach((cmd) => map.set(cmd.shortcut, cmd));
211
210
  return map;
212
211
  }, [detailedIndexed]);
213
212
 
213
+ const killAllTasks = useCallback(() => {
214
+ runningProcessMap.current.forEach((proc) => {
215
+ try { proc.kill('SIGINT'); } catch { /* ignore */ }
216
+ });
217
+ runningProcessMap.current.clear();
218
+ }, []);
219
+
214
220
  const runProjectCommand = useCallback(async (commandMeta, targetProject = selectedProject) => {
215
221
  const project = targetProject || selectedProject;
216
222
  if (!project) return;
@@ -245,52 +251,27 @@ function Compass({rootPath, initialView = 'navigator'}) {
245
251
  setTasks(prev => prev.map(t => t.id === taskId ? {...t, status: 'finished'} : t));
246
252
  addLogToTask(taskId, kleur.green(`✓ ${commandLabel} finished`));
247
253
  } catch (error) {
248
- setTasks(prev => prev.map(t => t.id === taskId ? {...t, status: 'failed'} : t));
249
- addLogToTask(taskId, kleur.red(`✗ ${commandLabel} failed: ${error.shortMessage || error.message}`));
254
+ if (error.isCanceled || error.killed) {
255
+ setTasks(prev => prev.map(t => t.id === taskId ? {...t, status: 'killed'} : t));
256
+ addLogToTask(taskId, kleur.yellow(`! Task killed by user`));
257
+ } else {
258
+ setTasks(prev => prev.map(t => t.id === taskId ? {...t, status: 'failed'} : t));
259
+ addLogToTask(taskId, kleur.red(`✗ ${commandLabel} failed: ${error.shortMessage || error.message}`));
260
+ }
250
261
  } finally {
251
262
  runningProcessMap.current.delete(taskId);
252
263
  }
253
264
  }, [addLogToTask, selectedProject]);
254
265
 
255
- const handleAddCustomCommand = useCallback((label, commandTokens) => {
256
- if (!selectedProject) return;
257
- setConfig((prev) => {
258
- const projectKey = selectedProject.path;
259
- const existing = prev.customCommands?.[projectKey] || [];
260
- const nextConfig = {
261
- ...prev,
262
- customCommands: {
263
- ...prev.customCommands,
264
- [projectKey]: [...existing, {label, command: commandTokens}]
265
- }
266
- };
267
- saveConfig(nextConfig);
268
- return nextConfig;
269
- });
270
- }, [selectedProject]);
271
-
272
- const handleCustomSubmit = useCallback(() => {
273
- const raw = customInput.trim();
274
- if (!selectedProject || !raw) {
275
- setCustomMode(false);
276
- setCustomInput('');
277
- setCustomCursor(0);
278
- return;
266
+ const handleKillTask = useCallback((taskId) => {
267
+ const proc = runningProcessMap.current.get(taskId);
268
+ if (proc) {
269
+ proc.kill('SIGINT');
270
+ } else {
271
+ setTasks(prev => prev.filter(t => t.id !== taskId));
272
+ if (activeTaskId === taskId) setActiveTaskId(null);
279
273
  }
280
- const [labelPart, commandPart] = raw.split('|');
281
- const commandTokens = (commandPart || labelPart).trim().split(/\s+/).filter(Boolean);
282
- if (!commandTokens.length) {
283
- setCustomMode(false);
284
- setCustomInput('');
285
- setCustomCursor(0);
286
- return;
287
- }
288
- const label = commandPart ? labelPart.trim() : `Custom ${selectedProject.name}`;
289
- handleAddCustomCommand(label, commandTokens);
290
- setCustomMode(false);
291
- setCustomInput('');
292
- setCustomCursor(0);
293
- }, [customInput, selectedProject, handleAddCustomCommand]);
274
+ }, [activeTaskId]);
294
275
 
295
276
  const exportLogs = useCallback(() => {
296
277
  if (!activeTask || !activeTask.logs.length) return;
@@ -304,8 +285,32 @@ function Compass({rootPath, initialView = 'navigator'}) {
304
285
  }, [activeTask, activeTaskId, addLogToTask]);
305
286
 
306
287
  useInput((input, key) => {
288
+ if (quitConfirm) {
289
+ if (input?.toLowerCase() === 'y') { killAllTasks(); exit(); return; }
290
+ if (input?.toLowerCase() === 'n' || key.escape) { setQuitConfirm(false); return; }
291
+ return;
292
+ }
293
+
307
294
  if (customMode) {
308
- if (key.return) { handleCustomSubmit(); return; }
295
+ if (key.return) {
296
+ const raw = customInput.trim();
297
+ if (selectedProject && raw) {
298
+ const [labelPart, commandPart] = raw.split('|');
299
+ const commandTokens = (commandPart || labelPart).trim().split(/\s+/).filter(Boolean);
300
+ if (commandTokens.length) {
301
+ const label = commandPart ? labelPart.trim() : `Custom ${selectedProject.name}`;
302
+ setConfig((prev) => {
303
+ const projectKey = selectedProject.path;
304
+ const existing = prev.customCommands?.[projectKey] || [];
305
+ const nextConfig = { ...prev, customCommands: { ...prev.customCommands, [projectKey]: [...existing, {label, command: commandTokens}] } };
306
+ saveConfig(nextConfig);
307
+ return nextConfig;
308
+ });
309
+ }
310
+ }
311
+ setCustomMode(false); setCustomInput(''); setCustomCursor(0);
312
+ return;
313
+ }
309
314
  if (key.escape) { setCustomMode(false); setCustomInput(''); setCustomCursor(0); return; }
310
315
  if (key.backspace || key.delete) {
311
316
  if (customCursor > 0) {
@@ -323,6 +328,29 @@ function Compass({rootPath, initialView = 'navigator'}) {
323
328
  return;
324
329
  }
325
330
 
331
+ if (renameMode) {
332
+ if (key.return) {
333
+ setTasks(prev => prev.map(t => t.id === activeTaskId ? {...t, name: renameInput} : t));
334
+ setRenameMode(false); setRenameInput(''); setRenameCursor(0);
335
+ return;
336
+ }
337
+ if (key.escape) { setRenameMode(false); setRenameInput(''); setRenameCursor(0); return; }
338
+ if (key.backspace || key.delete) {
339
+ if (renameCursor > 0) {
340
+ setRenameInput((prev) => prev.slice(0, renameCursor - 1) + prev.slice(renameCursor));
341
+ setRenameCursor(c => Math.max(0, c - 1));
342
+ }
343
+ return;
344
+ }
345
+ if (key.leftArrow) { setRenameCursor(c => Math.max(0, c - 1)); return; }
346
+ if (key.rightArrow) { setRenameCursor(c => Math.min(renameInput.length, c + 1)); return; }
347
+ if (input) {
348
+ setRenameInput((prev) => prev.slice(0, renameCursor) + input + prev.slice(renameCursor));
349
+ setRenameCursor(c => c + input.length);
350
+ }
351
+ return;
352
+ }
353
+
326
354
  const normalizedInput = input?.toLowerCase();
327
355
  const shiftCombo = (char) => key.shift && normalizedInput === char;
328
356
 
@@ -332,6 +360,14 @@ function Compass({rootPath, initialView = 'navigator'}) {
332
360
  if (shiftCombo('x')) { setTasks(prev => prev.map(t => t.id === activeTaskId ? {...t, logs: []} : t)); setLogOffset(0); return; }
333
361
  if (shiftCombo('e')) { exportLogs(); return; }
334
362
  if (shiftCombo('d')) { setActiveTaskId(null); return; }
363
+ if (shiftCombo('b')) {
364
+ setConfig(prev => {
365
+ const next = {...prev, showArtBoard: !prev.showArtBoard};
366
+ saveConfig(next);
367
+ return next;
368
+ });
369
+ return;
370
+ }
335
371
 
336
372
  if (shiftCombo('t')) {
337
373
  setMainView((prev) => {
@@ -354,6 +390,11 @@ function Compass({rootPath, initialView = 'navigator'}) {
354
390
  if (tasks.length > 0) {
355
391
  if (key.upArrow) { setActiveTaskId(prev => tasks[(tasks.findIndex(t => t.id === prev) - 1 + tasks.length) % tasks.length]?.id); return; }
356
392
  if (key.downArrow) { setActiveTaskId(prev => tasks[(tasks.findIndex(t => t.id === prev) + 1) % tasks.length]?.id); return; }
393
+ if (shiftCombo('k') && activeTaskId) {
394
+ handleKillTask(activeTaskId);
395
+ return;
396
+ }
397
+ if (shiftCombo('r') && activeTaskId) { setRenameMode(true); setRenameInput(activeTask.name); setRenameCursor(activeTask.name.length); return; }
357
398
  }
358
399
  if (key.return) { setMainView('navigator'); return; }
359
400
  return;
@@ -392,7 +433,10 @@ function Compass({rootPath, initialView = 'navigator'}) {
392
433
  setViewMode((prev) => (prev === 'detail' ? 'list' : 'detail'));
393
434
  return;
394
435
  }
395
- if (shiftCombo('q')) { exit(); return; }
436
+ if (shiftCombo('q')) {
437
+ if (hasRunningTasks) setQuitConfirm(true); else exit();
438
+ return;
439
+ }
396
440
  if (shiftCombo('c') && viewMode === 'detail' && selectedProject) { setCustomMode(true); setCustomInput(''); setCustomCursor(0); return; }
397
441
 
398
442
  const actionKey = normalizedInput && ACTION_MAP[normalizedInput];
@@ -408,6 +452,10 @@ function Compass({rootPath, initialView = 'navigator'}) {
408
452
 
409
453
  const projectCountLabel = `${projects.length} project${projects.length === 1 ? '' : 's'}`;
410
454
 
455
+ if (quitConfirm) {
456
+ return create(Box, {flexDirection: 'column', borderStyle: 'round', borderColor: 'red', padding: 1}, create(Text, {bold: true, color: 'red'}, '⚠️ Confirm Exit'), create(Text, null, `There are ${tasks.filter(t=>t.status==='running').length} tasks still running in the background.`), create(Text, null, 'Are you sure you want to quit and stop all processes?'), create(Text, {marginTop: 1}, kleur.bold('Y') + ' to Quit, ' + kleur.bold('N') + ' to Cancel'));
457
+ }
458
+
411
459
  if (mainView === 'studio') return create(Studio);
412
460
 
413
461
  if (mainView === 'tasks') {
@@ -415,14 +463,16 @@ function Compass({rootPath, initialView = 'navigator'}) {
415
463
  Box,
416
464
  {flexDirection: 'column', borderStyle: 'round', borderColor: 'yellow', padding: 1},
417
465
  create(Text, {bold: true, color: 'yellow'}, '🛰️ Task Manager | Background Processes'),
418
- create(Text, {dimColor: true, marginBottom: 1}, 'Select a task to view its output logs.'),
466
+ create(Text, {dimColor: true, marginBottom: 1}, 'Up/Down: focus, Shift+K: Kill/Delete, Shift+R: Rename'),
419
467
  ...tasks.map(t => create(
420
468
  Box,
421
- {key: t.id, marginBottom: 0},
422
- create(Text, {color: t.id === activeTaskId ? 'cyan' : 'white', bold: t.id === activeTaskId}, `${t.id === activeTaskId ? '→' : ' '} [${t.status.toUpperCase()}] ${t.name}`)
469
+ {key: t.id, marginBottom: 0, flexDirection: 'column'},
470
+ t.id === activeTaskId && renameMode
471
+ ? create(Box, {flexDirection: 'row'}, create(Text, {color: 'cyan'}, '→ Rename to: '), create(CursorText, {value: renameInput, cursorIndex: renameCursor}))
472
+ : create(Text, {color: t.id === activeTaskId ? 'cyan' : 'white', bold: t.id === activeTaskId}, `${t.id === activeTaskId ? '→' : ' '} [${t.status.toUpperCase()}] ${t.name}`)
423
473
  )),
424
474
  !tasks.length && create(Text, {dimColor: true}, 'No active or background tasks.'),
425
- create(Text, {marginTop: 1, dimColor: true}, 'Press Enter or Shift+T to return to Navigator, Up/Down to switch focus.')
475
+ create(Text, {marginTop: 1, dimColor: true}, 'Press Enter or Shift+T to return to Navigator.')
426
476
  );
427
477
  }
428
478
 
@@ -486,10 +536,6 @@ function Compass({rootPath, initialView = 'navigator'}) {
486
536
  detailContent.push(create(Text, {key: 'e-h', dimColor: true}, 'Press Enter on a project to reveal details.'));
487
537
  }
488
538
 
489
- if (customMode) {
490
- 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})));
491
- }
492
-
493
539
  const artTileNodes = [
494
540
  {label: 'Pulse', detail: projectCountLabel, accent: 'magenta', icon: '●', subtext: `Workspace · ${path.basename(rootPath) || rootPath}`},
495
541
  {label: 'Focus', detail: selectedProject?.name || 'Selection', accent: 'cyan', icon: '◆', subtext: `${selectedProject?.type || 'Stack'}`},
@@ -500,11 +546,11 @@ function Compass({rootPath, initialView = 'navigator'}) {
500
546
  create(Text, {dimColor: true}, tile.subtext)
501
547
  ));
502
548
 
503
- const artBoard = create(Box, {flexDirection: 'column', marginTop: 1, borderStyle: 'round', borderColor: 'gray', padding: 1},
549
+ const artBoard = config.showArtBoard ? create(Box, {flexDirection: 'column', marginTop: 1, borderStyle: 'round', borderColor: 'gray', padding: 1},
504
550
  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')),
505
551
  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)))),
506
552
  create(Box, {flexDirection: 'row', marginTop: 1}, ...artTileNodes)
507
- );
553
+ ) : null;
508
554
 
509
555
  const logs = activeTask?.logs || [];
510
556
  const logWindowStart = Math.max(0, logs.length - OUTPUT_WINDOW_SIZE - logOffset);
@@ -515,7 +561,7 @@ function Compass({rootPath, initialView = 'navigator'}) {
515
561
  const helpCards = [
516
562
  {label: 'Navigation', color: 'magenta', body: ['↑ / ↓ move focus, Enter: details', 'Shift+↑ / ↓ scroll output', 'Shift+H toggle help cards', 'Shift+D detach from task']},
517
563
  {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']},
518
- {label: 'Orbit & Studio', color: 'yellow', body: ['Shift+T task manager', 'Shift+A open Omni-Studio', 'Shift+C save custom action', 'Shift+Q quit application']}
564
+ {label: 'Orbit & Studio', color: 'yellow', body: ['Shift+T task manager', 'Shift+A studio / Shift+B art', 'Shift+C custom / Shift+Q quit']}
519
565
  ];
520
566
 
521
567
  const toggleHint = showHelpCards ? 'Shift+H hide help' : 'Shift+H show help';
@@ -533,11 +579,11 @@ function Compass({rootPath, initialView = 'navigator'}) {
533
579
  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')),
534
580
  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),
535
581
  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`)),
536
- create(Box, {marginTop: 1, flexDirection: 'row', borderStyle: 'round', borderColor: running ? 'green' : 'gray', paddingX: 1}, create(Text, {bold: true, color: 'green'}, running ? ' Stdin buffer ' : ' Input ready '), create(Box, {marginLeft: 1}, create(CursorText, {value: stdinBuffer || (running ? '' : 'Start a command to feed stdin'), cursorIndex: stdinCursor, active: running})))
582
+ 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})))
537
583
  ),
538
584
  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))))),
539
585
  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(', ')}`))),
540
- 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.'))
586
+ 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; Shift+B Toggle Art.'))
541
587
  );
542
588
  }
543
589
 
@@ -574,10 +620,15 @@ async function main() {
574
620
  console.log(' Shift+A Switch to Omni-Studio (Environment Health)');
575
621
  console.log(' Shift+T Open Orbit Task Manager (Manage background processes)');
576
622
  console.log(' Shift+D Detach from active task (Keep it running in background)');
623
+ console.log(' Shift+B Toggle Art Board visibility');
577
624
  console.log(' Shift+X Clear active task output log');
578
625
  console.log(' Shift+E Export current logs to a .txt file');
579
626
  console.log(' Shift+↑ / ↓ Scroll the output logs');
580
- console.log(' Shift+Q Quit application');
627
+ console.log(' Shift+Q Quit application (with confirmation if tasks run)');
628
+ console.log('');
629
+ console.log(kleur.bold('Task Manager (Shift+T):'));
630
+ console.log(' Shift+K Kill active/selected task');
631
+ console.log(' Shift+R Rename selected task');
581
632
  console.log('');
582
633
  console.log(kleur.bold('Execution shortcuts:'));
583
634
  console.log(' B / T / R Quick run: Build / Test / Run');