@krishivpb60/aether-ai-cli 1.3.7 → 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 CHANGED
@@ -1,3 +1,9 @@
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
+
1
7
  # Aether CLI v1.3.7 Highlights
2
8
  - **Readme Updates**:
3
9
  - Updates documentation to display interactive Git TUI, Autopilot debug loop, and Web Telemetry Dashboard companion commands.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krishivpb60/aether-ai-cli",
3
- "version": "1.3.7",
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 query = line.slice(8);
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
- return `/attach ${fullPath}${isDir ? "/" : ""}`;
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, bullet } from "./theme.js";
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
- const art = [
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(art.join("\n"));
45
- }
57
+ console.log(logo);
46
58
 
47
- /**
48
- * Gets a styled label for the given mode.
49
- * @param {string} mode - Mode name
50
- * @returns {string} Styled mode label
51
- */
52
- function modeLabel(mode) {
53
- const labels = {
54
- synthesis: colors.accent3.bold("Synthesis v2.5"),
55
- research: colors.accent2.bold("Research v104"),
56
- architect: colors.magenta.bold("Architect v55"),
57
- titan: colors.accent.bold("Titan Fusion v110"),
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
- return labels[mode?.toLowerCase()] || labels.titan;
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
  }
@@ -76,7 +76,7 @@
76
76
  }
77
77
 
78
78
  .hud-frame::after {
79
- content: "AETHER CLI V1.3.7";
79
+ content: "AETHER CLI V1.3.8";
80
80
  position: absolute;
81
81
  bottom: -12px;
82
82
  right: 20px;