@krishivpb60/aether-ai-cli 1.3.10 → 1.4.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/HIGHLIGHTS.md CHANGED
@@ -1,3 +1,20 @@
1
+ # Aether CLI v1.4.0 Highlights
2
+ - **Microphone Audio Input & Dynamic Nerd Font Glyphs (`/mic`)**:
3
+ - Adds `/mic` voice command to record audio directly from your microphone inside the terminal session.
4
+ - Implements native zero-dependency audio recording on Windows using the WinMM Multimedia Control Interface (MCI) via PowerShell.
5
+ - Automatically transcribes speech using Google Gemini (base64 inlineData), Groq Whisper, or OpenAI Whisper.
6
+ - Fixes readline interface raw mode pausing blockages to ensure Enter keypress resolves transcription correctly.
7
+ - Introduces dynamic `getIcon` helper supporting high-definition vector icons in the terminal.
8
+ - Adds optional `"NERD_FONTS"` configuration parameter (`aether config set NERD_FONTS true`) to automatically switch between standard emojis and Nerd Font glyphs (like FontAwesome microphones, folders, gears, and branch trees) based on your preferences.
9
+
10
+ # Aether CLI v1.3.11 Highlights
11
+ - **Microphone Audio Input Non-TTY Safety & Transcription (`/mic`)**:
12
+ - Adds `/mic` voice command to record audio directly from your microphone inside the terminal session.
13
+ - Implements native zero-dependency audio recording on Windows using the WinMM Multimedia Control Interface (MCI) via PowerShell.
14
+ - Automatically transcribes speech using Google Gemini (base64 inlineData), Groq Whisper, or OpenAI Whisper.
15
+ - Adds safeguards to prevent setRawMode crashes in non-TTY environments (like tests or piped runs).
16
+ - Populates the active readline prompt buffer directly with the transcribed text so you can review, edit, and send it.
17
+
1
18
  # Aether CLI v1.3.10 Highlights
2
19
  - **Microphone Audio Input Fixes & Transcription (`/mic`)**:
3
20
  - Adds `/mic` voice command to record audio directly from your microphone inside the terminal session.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krishivpb60/aether-ai-cli",
3
- "version": "1.3.10",
3
+ "version": "1.4.0",
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
@@ -25,7 +25,8 @@ import {
25
25
  getActiveTheme,
26
26
  setTheme,
27
27
  getThemesList,
28
- interactiveMenu
28
+ interactiveMenu,
29
+ getIcon
29
30
  } from "./ui/theme.js";
30
31
  import { createSpinner } from "./ui/spinner.js";
31
32
  import { showBanner } from "./ui/banner.js";
@@ -2256,7 +2257,7 @@ export async function handleMicInput(ctx) {
2256
2257
  return;
2257
2258
  }
2258
2259
 
2259
- console.log("\n" + label.system + " " + colors.brand("🎤 AUDIO VOICE INPUT"));
2260
+ console.log("\n" + label.system + " " + colors.brand(getIcon("mic", ctx.aiConfig) + "AUDIO VOICE INPUT"));
2260
2261
  console.log(separator("─"));
2261
2262
  console.log(colors.accent(" Recording started..."));
2262
2263
  console.log(" " + colors.muted("Speak into your microphone."));
@@ -2267,13 +2268,24 @@ export async function handleMicInput(ctx) {
2267
2268
 
2268
2269
  const stdin = process.stdin;
2269
2270
  const wasRaw = stdin.isRaw;
2270
- stdin.setRawMode(true);
2271
+ const isTTY = typeof stdin.setRawMode === "function";
2272
+
2273
+ if (isTTY) {
2274
+ stdin.setRawMode(true);
2275
+ }
2271
2276
  stdin.resume();
2272
2277
  stdin.setEncoding("utf8");
2273
2278
 
2274
2279
  let aborted = false;
2275
2280
  await new Promise((resolve) => {
2276
2281
  function onData(chunk) {
2282
+ if (!isTTY) {
2283
+ if (chunk.includes("\n") || chunk.includes("\r")) {
2284
+ stdin.removeListener("data", onData);
2285
+ resolve();
2286
+ }
2287
+ return;
2288
+ }
2277
2289
  if (chunk === "\u0003") {
2278
2290
  aborted = true;
2279
2291
  stdin.removeListener("data", onData);
@@ -2288,7 +2300,9 @@ export async function handleMicInput(ctx) {
2288
2300
  stdin.on("data", onData);
2289
2301
  });
2290
2302
 
2291
- stdin.setRawMode(wasRaw);
2303
+ if (isTTY) {
2304
+ stdin.setRawMode(wasRaw);
2305
+ }
2292
2306
  ctx.rl.resume();
2293
2307
 
2294
2308
  if (aborted) {
package/src/config.js CHANGED
@@ -176,7 +176,7 @@ export function isValidConfigKey(key) {
176
176
  const allowedSpecialKeys = [
177
177
  "THEME", "CUSTOM_COMMANDS", "AUTOPILOT",
178
178
  "AUTO_UPDATE", "SHOW_HIGHLIGHTS", "LAST_UPDATE_CHECK", "LAST_NOTIFIED_VERSION",
179
- "SHOW_TOKENS", "DIAGNOSE_CMD"
179
+ "SHOW_TOKENS", "DIAGNOSE_CMD", "NERD_FONTS"
180
180
  ];
181
181
  if (upper.endsWith("_API_KEY") || upper.endsWith("_API_KEYS") || upper.endsWith("_MODEL") || allowedSpecialKeys.includes(upper)) {
182
182
  return true;
package/src/ui/banner.js CHANGED
@@ -7,7 +7,7 @@ import { readFileSync, existsSync } from "node:fs";
7
7
  import { join } from "node:path";
8
8
  import { homedir } from "node:os";
9
9
  import chalk from "chalk";
10
- import { colors, separator, modeBadge } from "./theme.js";
10
+ import { colors, separator, modeBadge, getIcon } from "./theme.js";
11
11
  import { getActiveProviders } from "../ai/providers.js";
12
12
  import { MODES } from "../modes.js";
13
13
 
@@ -137,11 +137,11 @@ export function showBanner(currentMode = "titan") {
137
137
  : "npm (@krishivpb60/aether-ai-cli)";
138
138
 
139
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)),
140
+ formatRow(` ${colors.muted(getIcon("workspace", config) + "Workspace")}`, colors.text(workspaceValue)),
141
+ formatRow(` ${colors.muted(getIcon("mode", config) + "Mode")}`, modeRowValue),
142
+ formatRow(` ${colors.muted(getIcon("network", config) + "Network")}`, meshStatusText),
143
+ formatRow(` ${colors.muted(getIcon("engine", config) + "Engine")}`, colors.text(engineValue)),
144
+ formatRow(` ${colors.muted(getIcon("package", config) + "Packager")}`, colors.text(packagerText)),
145
145
  ];
146
146
 
147
147
  console.log(`\n ⚡ ${colors.brand("AETHER COMMAND STATION v" + version)} • Welcome back, ${colors.accent(username)}`);
@@ -76,7 +76,7 @@
76
76
  }
77
77
 
78
78
  .hud-frame::after {
79
- content: "AETHER CLI V1.3.10";
79
+ content: "AETHER CLI V1.4.0";
80
80
  position: absolute;
81
81
  bottom: -12px;
82
82
  right: 20px;
package/src/ui/theme.js CHANGED
@@ -486,3 +486,29 @@ export async function interactiveMenu(headerText, items) {
486
486
  stdin.on("data", handleKey);
487
487
  });
488
488
  }
489
+
490
+ /**
491
+ * Returns either a premium Nerd Font glyph or a standard Unicode emoji fallback
492
+ * depending on whether NERD_FONTS is enabled in the configuration.
493
+ * @param {string} name - Icon name
494
+ * @param {object} config - Active configuration object
495
+ * @returns {string}
496
+ */
497
+ export function getIcon(name, config) {
498
+ const useNerd = config?.NERD_FONTS === true || config?.NERD_FONTS === "true";
499
+
500
+ const icons = {
501
+ workspace: useNerd ? "\uf07c " : "📂 ",
502
+ mode: useNerd ? "\uf0e0 " : "🧠 ", // brain / envelope-like modes icon
503
+ network: useNerd ? "\uf6ff " : "🟢 ", // network icon
504
+ engine: useNerd ? "\uf013 " : "⚙️ ", // gear icon
505
+ package: useNerd ? "\uf1b2 " : "📦 ", // package icon
506
+ mic: useNerd ? "\uf130 " : "🎤 ", // microphone icon
507
+ git: useNerd ? "\uf113 " : "🌿 ", // git/leaf icon
508
+ dashboard: useNerd ? "\uf201 " : "📊 ", // chart icon
509
+ bolt: useNerd ? "\uf0e7 " : "⚡ ", // lightning bolt icon
510
+ };
511
+
512
+ return icons[name] || "";
513
+ }
514
+
@@ -148,6 +148,7 @@ test("Configuration Loading Suite", async (t) => {
148
148
  assert.strictEqual(isValidConfigKey("GOOGLE_API_KEYS"), true);
149
149
  assert.strictEqual(isValidConfigKey("THEME"), true);
150
150
  assert.strictEqual(isValidConfigKey("CUSTOM_COMMANDS"), true);
151
+ assert.strictEqual(isValidConfigKey("NERD_FONTS"), true);
151
152
  assert.strictEqual(isValidConfigKey("INVALID_KEY_NAME"), false);
152
153
  });
153
154
 
package/test/ux.test.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { test, beforeEach, afterEach } from "node:test";
2
2
  import assert from "node:assert";
3
3
  import { ReadableStream } from "node:stream/web";
4
- import { separator, clearStreamedText, StreamFilter, stripCodeFences, getActiveTheme, setTheme, getThemesList } from "../src/ui/theme.js";
4
+ import { separator, clearStreamedText, StreamFilter, stripCodeFences, getActiveTheme, setTheme, getThemesList, getIcon } from "../src/ui/theme.js";
5
5
  import { createSpinner } from "../src/ui/spinner.js";
6
6
  import { routePrompt } from "../src/ai/router.js";
7
7
  import { getModeByName, MODES } from "../src/modes.js";
@@ -183,4 +183,20 @@ test("Cyberpunk UX and Streaming Suite", async (t) => {
183
183
  const noFenceBlock = "console.log('hi');";
184
184
  assert.strictEqual(stripCodeFences(noFenceBlock), "console.log('hi');");
185
185
  });
186
+
187
+ await t.test("getIcon returns Nerd Font glyphs if enabled, else defaults to emojis", () => {
188
+ const nerdConfig = { NERD_FONTS: true };
189
+ const emojiConfig = { NERD_FONTS: false };
190
+ const defaultConfig = {};
191
+
192
+ // 1. Nerd Font Enabled
193
+ assert.strictEqual(getIcon("mic", nerdConfig), "\uf130 ");
194
+ assert.strictEqual(getIcon("git", nerdConfig), "\uf113 ");
195
+ assert.strictEqual(getIcon("dashboard", nerdConfig), "\uf201 ");
196
+
197
+ // 2. Nerd Font Disabled / Empty
198
+ assert.strictEqual(getIcon("mic", emojiConfig), "🎤 ");
199
+ assert.strictEqual(getIcon("git", emojiConfig), "🌿 ");
200
+ assert.strictEqual(getIcon("mic", defaultConfig), "🎤 ");
201
+ });
186
202
  });