@krishivpb60/aether-ai-cli 1.3.6 → 1.3.8
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/HIGHLIGHTS.md +10 -0
- package/README.md +17 -2
- package/package.json +1 -1
- package/src/chat.js +51 -7
- package/src/ui/banner.js +175 -32
- package/src/ui/dashboard.html +1 -1
package/HIGHLIGHTS.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
# Aether CLI v1.3.8 Highlights
|
|
2
|
+
- **OpenCode TUI Welcome & Navigation**:
|
|
3
|
+
- Implements a stunning, responsive OpenCode-style TUI System State dashboard.
|
|
4
|
+
- Adds `/cd <path>` workspace directory navigation command with directory-only Tab autocomplete.
|
|
5
|
+
- Automatically displays packaging environment info (`npm` vs `pip`).
|
|
6
|
+
|
|
7
|
+
# Aether CLI v1.3.7 Highlights
|
|
8
|
+
- **Readme Updates**:
|
|
9
|
+
- Updates documentation to display interactive Git TUI, Autopilot debug loop, and Web Telemetry Dashboard companion commands.
|
|
10
|
+
|
|
1
11
|
# Aether CLI v1.3.6 Highlights
|
|
2
12
|
- **DX Fixes & Upgrades (Git TUI, Autopilot, Dashboard)**:
|
|
3
13
|
- Fixes non-interactive Git TUI test hangs in git-initialized home directories.
|
package/README.md
CHANGED
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
- ⚡ **Single-Shot Queries** — Quick one-off questions directly from the CLI
|
|
30
30
|
- 🧠 **4 Reasoning Modes** — Synthesis, Research, Architect, Titan Fusion — each with unique system prompts
|
|
31
31
|
- 📎 **File Context Injection** — Attach code files, logs, or documents for context-aware AI responses
|
|
32
|
+
- 🤖 **Autopilot Debug Loop** — Automatically correct build/test failures using AI self-correcting feedback loop
|
|
33
|
+
- 🌿 **Interactive Git TUI** — Beautiful cyberpunk ASCII branch tree commit history & interactive file staging checkbox menu
|
|
34
|
+
- 📊 **Web HUD Dashboard** — Companion local zero-dependency telemetry dashboard displaying real-time latencies & provider status
|
|
32
35
|
- 🔄 **Failover Mesh** — Automatic failback across all configured providers
|
|
33
36
|
- 🔢 **Local Math Solver** — Evaluates mathematical expressions without an API call
|
|
34
37
|
- 🤖 **Krylo Companion** — Offline cyberpunk companion bot when no API keys are configured
|
|
@@ -189,8 +192,20 @@ Inside interactive chat mode, use these slash commands:
|
|
|
189
192
|
| `/cmd add <name> <template>` | Create a custom command shortcut |
|
|
190
193
|
| `/cmd remove <name>` | Delete a custom command |
|
|
191
194
|
| `/game` | Start the mainframe hacking mini-game |
|
|
192
|
-
| `/
|
|
193
|
-
| `/history-clear` | Clear saved chat history |
|
|
195
|
+
| `/history` | List, switch, and resume past interactive chat sessions |
|
|
196
|
+
| `/history-clear` | Clear saved persistent chat history |
|
|
197
|
+
| `/autopilot <mode\|debug [cmd]>` | View/switch autopilot safety level or run autonomous debug loop |
|
|
198
|
+
| `/git` | Launch interactive cyberpunk Git TUI and file stager checkbox menu |
|
|
199
|
+
| `/dashboard` | Spawn zero-dependency local web server and launch telemetry dashboard HUD |
|
|
200
|
+
| `/tokens` | View detailed session token usage and exchanges telemetry |
|
|
201
|
+
| `/update` | Force check for updates and update Aether CLI manually |
|
|
202
|
+
| `/review` | Run git diff and stream an AI code review |
|
|
203
|
+
| `/diagnose [cmd]` | Run build/tests and AI-debug any errors |
|
|
204
|
+
| `/explain <file>` | AI-explain the design and logic of a file |
|
|
205
|
+
| `/refactor <file>` | AI-refactor the code of a target file |
|
|
206
|
+
| `/bug <file>` | Scan a file to detect logical edge case failures |
|
|
207
|
+
| `/doc <file>` | Write documentation, inline comments, or JSDoc |
|
|
208
|
+
| `/translate <file> <lang>` | AI-translate file code into another target language |
|
|
194
209
|
| `/exit` | End session |
|
|
195
210
|
|
|
196
211
|
---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krishivpb60/aether-ai-cli",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.8",
|
|
4
4
|
"description": "Aether Core AI — A cyberpunk command-line AI assistant with multi-mode reasoning, 12-node failover mesh, file context injection, and offline fallbacks.",
|
|
5
5
|
"main": "src/cli.js",
|
|
6
6
|
"bin": {
|
package/src/chat.js
CHANGED
|
@@ -139,14 +139,16 @@ export async function startChat(options = {}) {
|
|
|
139
139
|
"/theme", "/themes", "/history-clear", "/game", "/abort", "/cmd", "/write",
|
|
140
140
|
"/commit", "/run", "/history", "/autopilot", "/tokens", "/update",
|
|
141
141
|
"/review", "/diagnose", "/explain", "/refactor", "/bug", "/doc", "/translate",
|
|
142
|
-
"/search", "/git", "/dashboard"
|
|
142
|
+
"/search", "/git", "/dashboard", "/cd"
|
|
143
143
|
];
|
|
144
144
|
const customCmds = aiConfig.CUSTOM_COMMANDS || {};
|
|
145
145
|
const commands = [...builtIn, ...Object.keys(customCmds)];
|
|
146
146
|
|
|
147
|
-
// File path autocompletion on /attach
|
|
148
|
-
if (line.startsWith("/attach ")) {
|
|
149
|
-
const
|
|
147
|
+
// File path autocompletion on /attach or /cd
|
|
148
|
+
if (line.startsWith("/attach ") || line.startsWith("/cd ")) {
|
|
149
|
+
const isCd = line.startsWith("/cd ");
|
|
150
|
+
const prefix = isCd ? "/cd " : "/attach ";
|
|
151
|
+
const query = line.slice(prefix.length);
|
|
150
152
|
const lastSlash = Math.max(query.lastIndexOf("/"), query.lastIndexOf("\\"));
|
|
151
153
|
let searchDir = ".";
|
|
152
154
|
let searchPrefix = query;
|
|
@@ -169,8 +171,10 @@ export async function startChat(options = {}) {
|
|
|
169
171
|
const fullPath = searchDir === "." || searchDir === sep ? f : join(searchDir, f);
|
|
170
172
|
const fullResolved = resolve(fullPath);
|
|
171
173
|
const isDir = statSync(fullResolved).isDirectory();
|
|
172
|
-
|
|
173
|
-
|
|
174
|
+
if (isCd && !isDir) return null;
|
|
175
|
+
return `${prefix}${fullPath}${isDir ? "/" : ""}`;
|
|
176
|
+
})
|
|
177
|
+
.filter(Boolean);
|
|
174
178
|
return [hits.length ? hits : [], line];
|
|
175
179
|
}
|
|
176
180
|
} catch (e) {
|
|
@@ -428,7 +432,7 @@ export async function startChat(options = {}) {
|
|
|
428
432
|
"/theme", "/themes", "/history-clear", "/game", "/abort", "/cmd",
|
|
429
433
|
"/guess", "/write", "/commit", "/run", "/history", "/autopilot", "/tokens",
|
|
430
434
|
"/update", "/review", "/diagnose", "/explain", "/refactor", "/bug", "/doc",
|
|
431
|
-
"/translate", "/search", "/git", "/dashboard"
|
|
435
|
+
"/translate", "/search", "/git", "/dashboard", "/cd"
|
|
432
436
|
];
|
|
433
437
|
|
|
434
438
|
const customCmds = aiConfig.CUSTOM_COMMANDS || {};
|
|
@@ -507,6 +511,10 @@ async function handleCommand(input, ctx) {
|
|
|
507
511
|
showBanner(ctx.currentMode.name);
|
|
508
512
|
break;
|
|
509
513
|
|
|
514
|
+
case "/cd":
|
|
515
|
+
await handleCd(args, ctx);
|
|
516
|
+
break;
|
|
517
|
+
|
|
510
518
|
case "/export":
|
|
511
519
|
await handleExport(ctx.history);
|
|
512
520
|
break;
|
|
@@ -638,6 +646,7 @@ function showHelp(aiConfig) {
|
|
|
638
646
|
console.log(keyValue("/themes", "List available visual themes"));
|
|
639
647
|
console.log(keyValue("/attach <path>", "Attach a file for context (supports Tab path autocomplete!)"));
|
|
640
648
|
console.log(keyValue("/files", "List attached files"));
|
|
649
|
+
console.log(keyValue("/cd <path>", "Change current working directory of this session (supports Tab path autocomplete!)"));
|
|
641
650
|
console.log(keyValue("/clear", "Clear terminal screen and reprint banner"));
|
|
642
651
|
console.log(keyValue("/providers", "Show active AI providers"));
|
|
643
652
|
console.log(keyValue("/export", "Export conversation to file"));
|
|
@@ -719,6 +728,41 @@ function showModes() {
|
|
|
719
728
|
}
|
|
720
729
|
}
|
|
721
730
|
|
|
731
|
+
async function handleCd(args, ctx) {
|
|
732
|
+
const { homedir } = await import("node:os");
|
|
733
|
+
if (args.length === 0) {
|
|
734
|
+
try {
|
|
735
|
+
const home = homedir();
|
|
736
|
+
process.chdir(home);
|
|
737
|
+
console.log("\n" + label.system + " " + colors.success(`Changed directory to: `) + colors.text(home) + "\n");
|
|
738
|
+
} catch (err) {
|
|
739
|
+
console.log("\n" + label.error + " " + colors.danger(`Failed to change directory: ${err.message}`) + "\n");
|
|
740
|
+
}
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const targetPath = args.join(" ").trim();
|
|
745
|
+
const resolvedPath = resolve(targetPath);
|
|
746
|
+
|
|
747
|
+
try {
|
|
748
|
+
if (!existsSync(resolvedPath)) {
|
|
749
|
+
console.log("\n" + label.error + " " + colors.danger(`Directory does not exist: ${targetPath}`) + "\n");
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const stat = statSync(resolvedPath);
|
|
754
|
+
if (!stat.isDirectory()) {
|
|
755
|
+
console.log("\n" + label.error + " " + colors.danger(`Path is not a directory: ${targetPath}`) + "\n");
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
process.chdir(resolvedPath);
|
|
760
|
+
console.log("\n" + label.system + " " + colors.success(`Changed directory to: `) + colors.text(resolvedPath) + "\n");
|
|
761
|
+
} catch (err) {
|
|
762
|
+
console.log("\n" + label.error + " " + colors.danger(`Failed to change directory: ${err.message}`) + "\n");
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
722
766
|
async function handleAttach(args, ctx) {
|
|
723
767
|
const filePath = args.join(" ").trim();
|
|
724
768
|
if (!filePath) {
|
package/src/ui/banner.js
CHANGED
|
@@ -2,11 +2,37 @@
|
|
|
2
2
|
// AETHER AI CLI — ASCII Art Welcome Banner
|
|
3
3
|
// ═══════════════════════════════════════════════════════════
|
|
4
4
|
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { homedir } from "node:os";
|
|
5
9
|
import chalk from "chalk";
|
|
6
|
-
import { colors, separator,
|
|
10
|
+
import { colors, separator, modeBadge } from "./theme.js";
|
|
11
|
+
import { getActiveProviders } from "../ai/providers.js";
|
|
12
|
+
import { MODES } from "../modes.js";
|
|
13
|
+
|
|
14
|
+
// ANSI escape code strip regex
|
|
15
|
+
const ANSI_REGEX = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
|
|
16
|
+
|
|
17
|
+
function getVisibleLength(str) {
|
|
18
|
+
return str.replace(ANSI_REGEX, "").length;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function loadConfigSync() {
|
|
22
|
+
try {
|
|
23
|
+
const configPath = join(homedir(), ".aether", "config.json");
|
|
24
|
+
if (existsSync(configPath)) {
|
|
25
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
26
|
+
return JSON.parse(raw);
|
|
27
|
+
}
|
|
28
|
+
} catch (e) {
|
|
29
|
+
// Ignore
|
|
30
|
+
}
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
7
33
|
|
|
8
34
|
/**
|
|
9
|
-
* Displays the cyberpunk-styled Aether ASCII art banner.
|
|
35
|
+
* Displays the cyberpunk-styled Aether ASCII art banner and OpenCode-style system info.
|
|
10
36
|
* @param {string} [currentMode='titan'] - The currently active mode name
|
|
11
37
|
*/
|
|
12
38
|
export function showBanner(currentMode = "titan") {
|
|
@@ -15,7 +41,8 @@ export function showBanner(currentMode = "titan") {
|
|
|
15
41
|
const c3 = colors.accent3;
|
|
16
42
|
const dim = colors.dim;
|
|
17
43
|
|
|
18
|
-
|
|
44
|
+
// 1. ASCII Art Logo
|
|
45
|
+
const logo = [
|
|
19
46
|
"",
|
|
20
47
|
c1(" ╔═══════════════════════════════════════════════════════════╗"),
|
|
21
48
|
c1(" ║") + c2(" █████╗ ███████╗████████╗██╗ ██╗███████╗██████╗ ") + c1("║"),
|
|
@@ -25,36 +52,152 @@ export function showBanner(currentMode = "titan") {
|
|
|
25
52
|
c1(" ║") + c3(" ██║ ██║███████╗ ██║ ██║ ██║███████╗██║ ██║ ") + c1("║"),
|
|
26
53
|
c1(" ║") + dim(" ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ") + c1("║"),
|
|
27
54
|
c1(" ╚═══════════════════════════════════════════════════════════╝"),
|
|
28
|
-
|
|
29
|
-
c1(" ⚡ ") + colors.text.bold("Aether Core AI v110") + colors.dim(" — Fusion Command Station"),
|
|
30
|
-
c2(" ◈ ") + colors.muted(`Active Mode: `) + modeLabel(currentMode),
|
|
31
|
-
"",
|
|
32
|
-
separator("─"),
|
|
33
|
-
"",
|
|
34
|
-
bullet("Type your prompt and press " + colors.accent("Enter") + " to query."),
|
|
35
|
-
bullet("Use " + colors.accent("/help") + " for all commands."),
|
|
36
|
-
bullet("Use " + colors.accent("/mode <name>") + " to switch reasoning mode."),
|
|
37
|
-
bullet("Use " + colors.accent("/attach <file>") + " to add file context."),
|
|
38
|
-
bullet("Use " + colors.accent("/exit") + " or " + colors.accent("Ctrl+C") + " to quit."),
|
|
39
|
-
"",
|
|
40
|
-
separator("─"),
|
|
41
|
-
"",
|
|
42
|
-
];
|
|
55
|
+
].join("\n");
|
|
43
56
|
|
|
44
|
-
console.log(
|
|
45
|
-
}
|
|
57
|
+
console.log(logo);
|
|
46
58
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
// 2. Fetch User & System Context
|
|
60
|
+
let username = "Explorer";
|
|
61
|
+
try {
|
|
62
|
+
username = os.userInfo().username;
|
|
63
|
+
} catch (e) {
|
|
64
|
+
username = process.env.USER || process.env.USERNAME || "Explorer";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const cwd = process.cwd();
|
|
68
|
+
const home = homedir();
|
|
69
|
+
let displayCwd = cwd;
|
|
70
|
+
if (cwd.startsWith(home)) {
|
|
71
|
+
displayCwd = "~" + cwd.slice(home.length);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const config = loadConfigSync();
|
|
75
|
+
const active = getActiveProviders(config);
|
|
76
|
+
|
|
77
|
+
const activeNames = active.length > 0
|
|
78
|
+
? [...new Set(active.map(a => a.provider.name))].join(", ")
|
|
79
|
+
: "Local math & offline logic only";
|
|
80
|
+
|
|
81
|
+
const meshStatusText = active.length > 0
|
|
82
|
+
? colors.success(`● Online (${active.length} active node${active.length === 1 ? "" : "s"})`)
|
|
83
|
+
: colors.warning(`○ Offline (Local fallbacks active)`);
|
|
84
|
+
|
|
85
|
+
const modeDetails = MODES[currentMode.toLowerCase()] || MODES.titan;
|
|
86
|
+
const modeText = modeBadge(currentMode) || colors.accent.bold(modeDetails.label);
|
|
87
|
+
|
|
88
|
+
const shortDescriptions = {
|
|
89
|
+
synthesis: "Balanced reasoning with clean structure.",
|
|
90
|
+
research: "Deep analysis with evidence-based reasoning.",
|
|
91
|
+
architect: "Systems thinking with build plans.",
|
|
92
|
+
titan: "Ultimate reasoning fusion of Codex & Claude Code.",
|
|
58
93
|
};
|
|
59
|
-
|
|
94
|
+
const desc = shortDescriptions[currentMode.toLowerCase()] || shortDescriptions.titan;
|
|
95
|
+
|
|
96
|
+
let version = "1.3.8";
|
|
97
|
+
try {
|
|
98
|
+
const pkgUrl = new URL("../../package.json", import.meta.url);
|
|
99
|
+
const pkg = JSON.parse(readFileSync(pkgUrl, "utf-8"));
|
|
100
|
+
version = pkg.version || "1.3.8";
|
|
101
|
+
} catch (e) {
|
|
102
|
+
// Fallback
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 3. Render System Status Box
|
|
106
|
+
const columns = process.stdout.columns || 80;
|
|
107
|
+
const boxWidth = Math.max(76, Math.min(90, columns - 4));
|
|
108
|
+
const maxValueWidth = boxWidth - 20;
|
|
109
|
+
|
|
110
|
+
// Truncate values to fit the box cleanly
|
|
111
|
+
let workspaceValue = displayCwd;
|
|
112
|
+
if (workspaceValue.length > maxValueWidth) {
|
|
113
|
+
workspaceValue = "..." + workspaceValue.slice(-(maxValueWidth - 3));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let engineValue = activeNames;
|
|
117
|
+
if (engineValue.length > maxValueWidth) {
|
|
118
|
+
engineValue = engineValue.slice(0, maxValueWidth - 3) + "...";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Truncate description to ensure total mode value (badge + sep + desc) fits within maxValueWidth
|
|
122
|
+
const modeBadgeText = getVisibleLength(modeText);
|
|
123
|
+
const maxDescWidth = maxValueWidth - modeBadgeText - 3; // 3 for " — "
|
|
124
|
+
let descValue = desc;
|
|
125
|
+
if (descValue.length > maxDescWidth) {
|
|
126
|
+
descValue = descValue.slice(0, Math.max(10, maxDescWidth - 3)) + "...";
|
|
127
|
+
}
|
|
128
|
+
const modeRowValue = `${modeText} ${colors.dim(`— ${descValue}`)}`;
|
|
129
|
+
|
|
130
|
+
function formatRow(label, value) {
|
|
131
|
+
const spaces = " ".repeat(Math.max(0, 14 - getVisibleLength(label)));
|
|
132
|
+
return `${label}${spaces}${value}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const packagerText = process.env.AETHER_PACKAGER === "pip"
|
|
136
|
+
? "pip (aether-ai-agent-cli)"
|
|
137
|
+
: "npm (@krishivpb60/aether-ai-cli)";
|
|
138
|
+
|
|
139
|
+
const rows = [
|
|
140
|
+
formatRow(` ${colors.muted("📂 Workspace")}`, colors.text(workspaceValue)),
|
|
141
|
+
formatRow(` ${colors.muted("🧠 Mode")}`, modeRowValue),
|
|
142
|
+
formatRow(` ${colors.muted("🟢 Network")}`, meshStatusText),
|
|
143
|
+
formatRow(` ${colors.muted("⚙️ Engine")}`, colors.text(engineValue)),
|
|
144
|
+
formatRow(` ${colors.muted("📦 Packager")}`, colors.text(packagerText)),
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
console.log(`\n ⚡ ${colors.brand("AETHER COMMAND STATION v" + version)} • Welcome back, ${colors.accent(username)}`);
|
|
148
|
+
|
|
149
|
+
// Draw Box
|
|
150
|
+
const leftLine = "═".repeat(2);
|
|
151
|
+
const rightLine = "═".repeat(boxWidth - 21);
|
|
152
|
+
const top = dim(` ╔${leftLine} `) + colors.brand("SYSTEM STATE") + dim(` ${rightLine}╗`);
|
|
153
|
+
console.log(top);
|
|
154
|
+
for (const row of rows) {
|
|
155
|
+
const visibleLen = getVisibleLength(row);
|
|
156
|
+
const padding = " ".repeat(Math.max(0, boxWidth - visibleLen - 2));
|
|
157
|
+
console.log(dim(" ║ ") + row + padding + dim(" ║"));
|
|
158
|
+
}
|
|
159
|
+
console.log(dim(` ╚${"═".repeat(boxWidth - 2)}╝`));
|
|
160
|
+
|
|
161
|
+
// 4. Quick Starter Cards (two columns)
|
|
162
|
+
const starters = [
|
|
163
|
+
{ cmd: "/explain", desc: "Analyze files in workspace" },
|
|
164
|
+
{ cmd: "/review", desc: "Review git changes (diffs)" },
|
|
165
|
+
{ cmd: "/diagnose",desc: "Auto-heal failing tests" },
|
|
166
|
+
{ cmd: "/dashboard",desc: "Launch visual telemetry HUD" },
|
|
167
|
+
{ cmd: "/autopilot",desc: "Start autonomous debug loop" },
|
|
168
|
+
{ cmd: "/game", desc: "Play mainframe hacking game" },
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
console.log(`\n ⚡ ${colors.brand("QUICK COMMAND STARTERS")}`);
|
|
172
|
+
if (boxWidth >= 80) {
|
|
173
|
+
// Print 2 columns
|
|
174
|
+
for (let i = 0; i < starters.length; i += 2) {
|
|
175
|
+
const s1 = starters[i];
|
|
176
|
+
const s2 = starters[i + 1];
|
|
177
|
+
|
|
178
|
+
const col1 = ` ${colors.accent(s1.cmd.padEnd(11))} ${colors.text(s1.desc.padEnd(28))}`;
|
|
179
|
+
const col2 = s2 ? ` ${colors.accent(s2.cmd.padEnd(11))} ${colors.text(s2.desc)}` : "";
|
|
180
|
+
|
|
181
|
+
console.log(col1 + col2);
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
// Print 1 column
|
|
185
|
+
for (const s of starters) {
|
|
186
|
+
console.log(` ${colors.accent(s.cmd.padEnd(11))} ${colors.text(s.desc)}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 5. Help / Footer info
|
|
191
|
+
console.log("\n" + separator("─"));
|
|
192
|
+
const footer = [
|
|
193
|
+
colors.dim(" Press "),
|
|
194
|
+
colors.accent("Tab"),
|
|
195
|
+
colors.dim(" to autocomplete file paths • Type "),
|
|
196
|
+
colors.accent("/help"),
|
|
197
|
+
colors.dim(" for all commands • "),
|
|
198
|
+
colors.accent("Ctrl+C"),
|
|
199
|
+
colors.dim(" to exit session")
|
|
200
|
+
].join("");
|
|
201
|
+
console.log(footer);
|
|
202
|
+
console.log(separator("─") + "\n");
|
|
60
203
|
}
|