@krishivpb60/aether-ai-cli 1.3.1 → 1.3.3
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 +13 -0
- package/package.json +1 -1
- package/src/chat.js +223 -2
- package/src/config.js +1 -1
- package/src/updater.js +17 -7
- package/test/dx.test.js +10 -0
- package/test/updater.test.js +19 -0
package/HIGHLIGHTS.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
# Aether CLI v1.3.3 Highlights
|
|
2
|
+
- **Codex & Claude Code Slash Commands**: Added 7 new advanced developer experience (DX) commands:
|
|
3
|
+
- `/review`: Analyze staged/unstaged git changes and stream an AI-powered code review.
|
|
4
|
+
- `/diagnose [cmd]`: Run tests/builds and automatically debug any errors.
|
|
5
|
+
- `/explain <file>`: Explains design flow and patterns in code.
|
|
6
|
+
- `/refactor <file>`: Rewrites a target file to optimize it.
|
|
7
|
+
- `/bug <file>`: Scans a file to detect logical edge case failures.
|
|
8
|
+
- `/doc <file>`: Automatically writes documentation, inline comments, or JSDoc.
|
|
9
|
+
- `/translate <file> <lang>`: AI-translates file code into another target language.
|
|
10
|
+
|
|
11
|
+
# Aether CLI v1.3.2 Highlights
|
|
12
|
+
- **Manual Updater `/update`**: Added a new slash command `/update` to manually check the registry and force-upgrade Aether CLI to the latest version immediately, bypassing the 24-hour cache throttle.
|
|
13
|
+
|
|
1
14
|
# Aether CLI v1.3.1 Highlights
|
|
2
15
|
- **Codex & Claude Code Fusion**: The powers of OpenAI Codex and Claude Code are now combined directly inside the default **Titan Fusion** (`titan`) mode.
|
|
3
16
|
- **Streamlined Modes**: Removed the individual `codex` and `cloude-code` modes to reduce clutter, automatically redirecting all lookups of these modes to Titan Fusion.
|
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.3",
|
|
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
|
@@ -47,6 +47,7 @@ import { runMainframeHack } from "./ai/fallback.js";
|
|
|
47
47
|
import { AGENT_INSTRUCTIONS } from "./agent.js";
|
|
48
48
|
import { checkForUpdates } from "./updater.js";
|
|
49
49
|
import { getSessionTokenStats, getBreakdownByModel, resetSessionTokenStats } from "./ai/tokens.js";
|
|
50
|
+
import { getGitDiff } from "./git.js";
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
|
|
@@ -136,7 +137,8 @@ export async function startChat(options = {}) {
|
|
|
136
137
|
"/help", "/mode", "/modes", "/attach", "/files", "/clear",
|
|
137
138
|
"/providers", "/export", "/status", "/copy", "/exit", "/quit",
|
|
138
139
|
"/theme", "/themes", "/history-clear", "/game", "/abort", "/cmd", "/write",
|
|
139
|
-
"/commit", "/run", "/history", "/autopilot", "/tokens"
|
|
140
|
+
"/commit", "/run", "/history", "/autopilot", "/tokens", "/update",
|
|
141
|
+
"/review", "/diagnose", "/explain", "/refactor", "/bug", "/doc", "/translate"
|
|
140
142
|
];
|
|
141
143
|
const customCmds = aiConfig.CUSTOM_COMMANDS || {};
|
|
142
144
|
const commands = [...builtIn, ...Object.keys(customCmds)];
|
|
@@ -423,7 +425,9 @@ export async function startChat(options = {}) {
|
|
|
423
425
|
"/", "/help", "/mode", "/modes", "/attach", "/files", "/clear",
|
|
424
426
|
"/providers", "/export", "/status", "/copy", "/exit", "/quit",
|
|
425
427
|
"/theme", "/themes", "/history-clear", "/game", "/abort", "/cmd",
|
|
426
|
-
"/guess", "/write", "/commit", "/run", "/history", "/autopilot", "/tokens"
|
|
428
|
+
"/guess", "/write", "/commit", "/run", "/history", "/autopilot", "/tokens",
|
|
429
|
+
"/update", "/review", "/diagnose", "/explain", "/refactor", "/bug", "/doc",
|
|
430
|
+
"/translate"
|
|
427
431
|
];
|
|
428
432
|
|
|
429
433
|
const customCmds = aiConfig.CUSTOM_COMMANDS || {};
|
|
@@ -514,6 +518,28 @@ async function handleCommand(input, ctx) {
|
|
|
514
518
|
showActiveProviders(ctx.aiConfig);
|
|
515
519
|
break;
|
|
516
520
|
|
|
521
|
+
case "/update":
|
|
522
|
+
console.log("\n" + label.system + " " + colors.muted("Checking registry for updates..."));
|
|
523
|
+
await checkForUpdates(true);
|
|
524
|
+
console.log("");
|
|
525
|
+
break;
|
|
526
|
+
|
|
527
|
+
case "/review":
|
|
528
|
+
await handleReviewCommand(ctx);
|
|
529
|
+
break;
|
|
530
|
+
|
|
531
|
+
case "/diagnose":
|
|
532
|
+
await handleDiagnoseCommand(args, ctx);
|
|
533
|
+
break;
|
|
534
|
+
|
|
535
|
+
case "/explain":
|
|
536
|
+
case "/refactor":
|
|
537
|
+
case "/bug":
|
|
538
|
+
case "/doc":
|
|
539
|
+
case "/translate":
|
|
540
|
+
await handleFileAICommand(cmd, args, ctx);
|
|
541
|
+
break;
|
|
542
|
+
|
|
517
543
|
case "/theme":
|
|
518
544
|
await handleThemeSwitch(args);
|
|
519
545
|
break;
|
|
@@ -606,12 +632,20 @@ function showHelp(aiConfig) {
|
|
|
606
632
|
console.log(keyValue("/history-clear", "Clear saved persistent chat history"));
|
|
607
633
|
console.log(keyValue("/autopilot <mode>", "View or switch agent autopilot level (off, safe, workspace, machine)"));
|
|
608
634
|
console.log(keyValue("/tokens", "View detailed session token usage and exchanges telemetry"));
|
|
635
|
+
console.log(keyValue("/update", "Force check for updates and update Aether CLI manually"));
|
|
609
636
|
console.log(keyValue("/game", "Start the local mainframe hacking mini-game"));
|
|
610
637
|
console.log(keyValue("/copy", "Copy the last assistant response to clipboard"));
|
|
611
638
|
console.log(keyValue("/cmd <list|add|remove>", "Manage custom command shortcuts"));
|
|
612
639
|
console.log(keyValue("/write <filename>", "Extract last code block and save to file"));
|
|
613
640
|
console.log(keyValue("/commit", "Generate conventional commit message and commit changes"));
|
|
614
641
|
console.log(keyValue("/run <command>", "Execute a shell command interactively"));
|
|
642
|
+
console.log(keyValue("/review", "Run git diff and stream an AI code review"));
|
|
643
|
+
console.log(keyValue("/diagnose [cmd]", "Run build/tests and AI-debug any errors"));
|
|
644
|
+
console.log(keyValue("/explain <file>", "AI-explain the design and logic of a file"));
|
|
645
|
+
console.log(keyValue("/refactor <file>", "AI-refactor the code of a target file"));
|
|
646
|
+
console.log(keyValue("/bug <file>", "AI-audit a file to find potential logic bugs"));
|
|
647
|
+
console.log(keyValue("/doc <file>", "AI-generate documentation/docstrings for a file"));
|
|
648
|
+
console.log(keyValue("/translate <file> <lang>", "AI-translate code of a file to another language"));
|
|
615
649
|
console.log(keyValue("/exit", "End session"));
|
|
616
650
|
|
|
617
651
|
if (aiConfig && aiConfig.CUSTOM_COMMANDS) {
|
|
@@ -1394,3 +1428,190 @@ async function handleTokensDisplay(ctx) {
|
|
|
1394
1428
|
console.log(" " + colors.accent("Total Tokens:") + colors.text(` Prompt: ${stats.prompt.toLocaleString()} | Completion: ${stats.completion.toLocaleString()} | Sum: `) + colors.brand.bold(stats.total.toLocaleString()));
|
|
1395
1429
|
console.log(separator("━") + "\n");
|
|
1396
1430
|
}
|
|
1431
|
+
|
|
1432
|
+
/**
|
|
1433
|
+
* Streams an AI query prompt and prints telemetry details at the end.
|
|
1434
|
+
*/
|
|
1435
|
+
async function executeAISpecialCommand(prompt, specialLabel, ctx) {
|
|
1436
|
+
const systemPrompt = ctx.currentMode.systemPrompt + "\n" + AGENT_INSTRUCTIONS;
|
|
1437
|
+
let hasStarted = false;
|
|
1438
|
+
let responseText = "";
|
|
1439
|
+
const queryStartTime = Date.now();
|
|
1440
|
+
let firstTokenTime = 0;
|
|
1441
|
+
|
|
1442
|
+
const onToken = (token) => {
|
|
1443
|
+
if (!hasStarted) {
|
|
1444
|
+
hasStarted = true;
|
|
1445
|
+
firstTokenTime = Date.now();
|
|
1446
|
+
process.stdout.write("\n" + label.aether + " " + colors.accent(specialLabel) + "\n" + separator("─") + "\n\n");
|
|
1447
|
+
}
|
|
1448
|
+
process.stdout.write(colors.success(token));
|
|
1449
|
+
responseText += token;
|
|
1450
|
+
};
|
|
1451
|
+
|
|
1452
|
+
const result = await routePrompt(prompt, systemPrompt, ctx.aiConfig, onToken);
|
|
1453
|
+
console.log("\n");
|
|
1454
|
+
|
|
1455
|
+
const elapsedSec = ((Date.now() - queryStartTime) / 1000).toFixed(1);
|
|
1456
|
+
let speedText = "";
|
|
1457
|
+
if (firstTokenTime > 0) {
|
|
1458
|
+
const streamElapsed = (Date.now() - firstTokenTime) / 1000;
|
|
1459
|
+
if (streamElapsed > 0.05) {
|
|
1460
|
+
const estimatedTokens = Math.max(1, Math.round(responseText.length / 4));
|
|
1461
|
+
const tps = (estimatedTokens / streamElapsed).toFixed(1);
|
|
1462
|
+
speedText = ` • ${tps} tok/s`;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
const showTokens = ctx.aiConfig.SHOW_TOKENS !== "false";
|
|
1467
|
+
let tokensText = "";
|
|
1468
|
+
if (showTokens && result.usage) {
|
|
1469
|
+
const { promptTokens, completionTokens } = result.usage;
|
|
1470
|
+
tokensText = ` • ${promptTokens.toLocaleString()} in / ${completionTokens.toLocaleString()} out tokens`;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
console.log(separator("─"));
|
|
1474
|
+
console.log(
|
|
1475
|
+
" " + colors.dim(`Node ${result.node} • ${result.provider}`) +
|
|
1476
|
+
(result.model ? colors.dim(` • ${result.model}`) : "") +
|
|
1477
|
+
colors.dim(` • ${elapsedSec}s${speedText}`) +
|
|
1478
|
+
colors.dim(tokensText)
|
|
1479
|
+
);
|
|
1480
|
+
console.log("");
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
/**
|
|
1484
|
+
* Handler for the /review command (git diff analysis).
|
|
1485
|
+
*/
|
|
1486
|
+
async function handleReviewCommand(ctx) {
|
|
1487
|
+
console.log("\n" + label.system + " " + colors.muted("Running git diff to fetch repository changes..."));
|
|
1488
|
+
try {
|
|
1489
|
+
const { diff, isStaged } = await getGitDiff();
|
|
1490
|
+
if (!diff) {
|
|
1491
|
+
console.log(label.system + " " + colors.success("✓ No changes detected in the repository to review.\n"));
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
const specialLabel = `Reviewing ${isStaged ? "staged" : "unstaged"} changes...`;
|
|
1496
|
+
const prompt = `Review the following git diff. Identify potential bugs, logical issues, security concerns, performance problems, and recommend optimization or code cleanup. Keep it concise, practical, and highly technical:\n\n\`\`\`diff\n${diff}\n\`\`\``;
|
|
1497
|
+
|
|
1498
|
+
await executeAISpecialCommand(prompt, specialLabel, ctx);
|
|
1499
|
+
} catch (err) {
|
|
1500
|
+
console.log(label.system + " " + colors.danger(`Error: ${err.message}\n`));
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
/**
|
|
1505
|
+
* Handler for the /diagnose command (build & test diagnostics execution).
|
|
1506
|
+
*/
|
|
1507
|
+
async function handleDiagnoseCommand(args, ctx) {
|
|
1508
|
+
const defaultCmd = ctx.aiConfig.DIAGNOSE_CMD || "npm test";
|
|
1509
|
+
const cmdToRun = args.join(" ").trim() || defaultCmd;
|
|
1510
|
+
|
|
1511
|
+
console.log("\n" + label.system + " " + colors.muted(`Running diagnostics command: "${cmdToRun}"...`));
|
|
1512
|
+
|
|
1513
|
+
const spinner = createSpinner("Executing diagnostics").start();
|
|
1514
|
+
try {
|
|
1515
|
+
const { exec } = await import("node:child_process");
|
|
1516
|
+
const { promisify } = await import("node:util");
|
|
1517
|
+
const execAsync = promisify(exec);
|
|
1518
|
+
await execAsync(cmdToRun);
|
|
1519
|
+
spinner.succeed("Diagnostics complete!");
|
|
1520
|
+
console.log("\n" + label.system + " " + colors.success("✓ Diagnostics clean! Build and tests passed successfully.\n"));
|
|
1521
|
+
} catch (err) {
|
|
1522
|
+
spinner.fail("Diagnostics failed!");
|
|
1523
|
+
|
|
1524
|
+
const output = (err.stdout || "") + "\n" + (err.stderr || "");
|
|
1525
|
+
console.log("\n" + label.system + " " + colors.warning(`Diagnostics returned exit code ${err.code}.`));
|
|
1526
|
+
console.log(colors.muted("Analyzing compiler/test output logs...\n"));
|
|
1527
|
+
|
|
1528
|
+
const prompt = `The diagnostics command "${cmdToRun}" failed with exit code ${err.code}. Analyze the following stdout and stderr logs to determine the root cause, identify the files/lines causing the failure, and provide a step-by-step resolution and debugging plan:\n\n\`\`\`\n${output.slice(0, 15000)}\n\`\`\``;
|
|
1529
|
+
|
|
1530
|
+
await executeAISpecialCommand(prompt, "Analyzing diagnostics logs...", ctx);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
/**
|
|
1535
|
+
* Handler for file analysis commands: /explain, /refactor, /bug, /doc, /translate.
|
|
1536
|
+
*/
|
|
1537
|
+
async function handleFileAICommand(cmdName, args, ctx) {
|
|
1538
|
+
const filePath = args[0];
|
|
1539
|
+
if (!filePath) {
|
|
1540
|
+
console.log("\n" + label.system + " " + colors.warning(`Usage: ${cmdName} <file_path>\n`));
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
// Resolve path
|
|
1545
|
+
const resolvedPath = resolve(process.cwd(), filePath);
|
|
1546
|
+
|
|
1547
|
+
// Verify path is inside the workspace
|
|
1548
|
+
const { isInsideWorkspace } = await import("./agent.js");
|
|
1549
|
+
if (!isInsideWorkspace(resolvedPath)) {
|
|
1550
|
+
console.log("\n" + label.system + " " + colors.danger("Error: Path is outside the current workspace sandbox.\n"));
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
if (!existsSync(resolvedPath)) {
|
|
1555
|
+
console.log("\n" + label.system + " " + colors.danger(`Error: File does not exist at "${filePath}"\n`));
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
const stat = statSync(resolvedPath);
|
|
1560
|
+
if (stat.isDirectory()) {
|
|
1561
|
+
console.log("\n" + label.system + " " + colors.danger(`Error: "${filePath}" is a directory. File path required.\n`));
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
if (stat.size > 150 * 1024) { // 150KB limit
|
|
1566
|
+
console.log("\n" + label.system + " " + colors.warning(`Warning: File "${filePath}" is too large (${Math.round(stat.size / 1024)}KB). Limits are 150KB to protect context limit.\n`));
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
// Read file content
|
|
1571
|
+
let content;
|
|
1572
|
+
try {
|
|
1573
|
+
const { parseFile } = await import("./file-parser.js");
|
|
1574
|
+
const parsed = await parseFile(resolvedPath);
|
|
1575
|
+
content = parsed.content;
|
|
1576
|
+
} catch (err) {
|
|
1577
|
+
console.log("\n" + label.system + " " + colors.danger(`Error parsing file: ${err.message}\n`));
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
let prompt = "";
|
|
1582
|
+
let labelText = "";
|
|
1583
|
+
|
|
1584
|
+
switch (cmdName.toLowerCase()) {
|
|
1585
|
+
case "/explain":
|
|
1586
|
+
labelText = `Explaining ${filePath}...`;
|
|
1587
|
+
prompt = `Explain the architecture, design patterns, logic flow, and purpose of the following code. Be clear, technical, and structured:\n\n\`\`\`\n${content}\n\`\`\``;
|
|
1588
|
+
break;
|
|
1589
|
+
case "/refactor":
|
|
1590
|
+
labelText = `Refactoring ${filePath}...`;
|
|
1591
|
+
prompt = `Suggest refactoring improvements for the following code. Focus on clean code design principles, optimization, readability, reducing complexity, and fixing potential logic bugs. Return both the refactored code block and explanations:\n\n\`\`\`\n${content}\n\`\`\``;
|
|
1592
|
+
break;
|
|
1593
|
+
case "/bug":
|
|
1594
|
+
labelText = `Auditing bugs in ${filePath}...`;
|
|
1595
|
+
prompt = `Perform a thorough static analysis and code review of the following code. Identify potential logical bugs, race conditions, edge case failures, performance bottlenecks, and security hazards. Suggest fixes:\n\n\`\`\`\n${content}\n\`\`\``;
|
|
1596
|
+
break;
|
|
1597
|
+
case "/doc":
|
|
1598
|
+
labelText = `Generating documentation for ${filePath}...`;
|
|
1599
|
+
prompt = `Generate comprehensive API documentation, JSDoc/docstrings, and comments for the following code. Ensure code parameters, return values, and types are documented:\n\n\`\`\`\n${content}\n\`\`\``;
|
|
1600
|
+
break;
|
|
1601
|
+
case "/translate":
|
|
1602
|
+
const targetLang = args[1];
|
|
1603
|
+
if (!targetLang) {
|
|
1604
|
+
console.log("\n" + label.system + " " + colors.warning(`Usage: /translate <file_path> <target_language>\n`));
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
labelText = `Translating ${filePath} to ${targetLang}...`;
|
|
1608
|
+
prompt = `Translate the following code into ${targetLang}. Return a clean, syntactically correct, and beautifully structured code block of the translated code:\n\n\`\`\`\n${content}\n\`\`\``;
|
|
1609
|
+
break;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
try {
|
|
1613
|
+
await executeAISpecialCommand(prompt, labelText, ctx);
|
|
1614
|
+
} catch (err) {
|
|
1615
|
+
console.log("\n" + label.system + " " + colors.danger(`Error: ${err.message}\n`));
|
|
1616
|
+
}
|
|
1617
|
+
}
|
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"
|
|
179
|
+
"SHOW_TOKENS", "DIAGNOSE_CMD"
|
|
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/updater.js
CHANGED
|
@@ -105,16 +105,16 @@ export async function showReleaseHighlights(version) {
|
|
|
105
105
|
/**
|
|
106
106
|
* Checks for updates and runs the automatic updater if configured.
|
|
107
107
|
*/
|
|
108
|
-
export async function checkForUpdates() {
|
|
108
|
+
export async function checkForUpdates(force = false) {
|
|
109
109
|
const autoUpdate = (await getConfigValue("AUTO_UPDATE")) !== "false";
|
|
110
110
|
const showHighlights = (await getConfigValue("SHOW_HIGHLIGHTS")) !== "false";
|
|
111
111
|
const lastCheck = parseInt(await getConfigValue("LAST_UPDATE_CHECK") || "0", 10);
|
|
112
112
|
const now = Date.now();
|
|
113
113
|
const currentVersion = pkg.version;
|
|
114
114
|
|
|
115
|
-
// Run update check at most once every 24 hours (86,400,000 ms)
|
|
115
|
+
// Run update check at most once every 24 hours (86,400,000 ms), unless forced
|
|
116
116
|
const checkInterval = 24 * 60 * 60 * 1000;
|
|
117
|
-
if (now - lastCheck < checkInterval) {
|
|
117
|
+
if (!force && (now - lastCheck < checkInterval)) {
|
|
118
118
|
// Show highlights if we just updated and haven't shown highlights for this version
|
|
119
119
|
const lastNotified = await getConfigValue("LAST_NOTIFIED_VERSION") || "";
|
|
120
120
|
if (showHighlights && lastNotified !== currentVersion) {
|
|
@@ -136,12 +136,17 @@ export async function checkForUpdates() {
|
|
|
136
136
|
});
|
|
137
137
|
clearTimeout(timeoutId);
|
|
138
138
|
|
|
139
|
-
if (!res.ok)
|
|
139
|
+
if (!res.ok) {
|
|
140
|
+
if (force) {
|
|
141
|
+
console.log(label.system + " " + colors.warning(`⚠ Update check failed: server returned status ${res.status}`));
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
140
145
|
const data = await res.json();
|
|
141
146
|
const latestVersion = data.version;
|
|
142
147
|
|
|
143
148
|
if (isNewerVersion(latestVersion, currentVersion)) {
|
|
144
|
-
if (autoUpdate) {
|
|
149
|
+
if (autoUpdate || force) {
|
|
145
150
|
console.log("\n" + label.system + " " + colors.brand(`⚡ New version detected! Auto-updating from v${currentVersion} to v${latestVersion}...`));
|
|
146
151
|
|
|
147
152
|
const isPip = process.env.AETHER_PACKAGER === "pip";
|
|
@@ -173,6 +178,9 @@ export async function checkForUpdates() {
|
|
|
173
178
|
console.log(label.system + " " + colors.muted(`To update, run: ${updateCmd}`));
|
|
174
179
|
}
|
|
175
180
|
} else {
|
|
181
|
+
if (force) {
|
|
182
|
+
console.log(label.system + " " + colors.success(`✓ Aether is already up to date (v${currentVersion}).`));
|
|
183
|
+
}
|
|
176
184
|
// Already on latest version, check if we need to show highlights
|
|
177
185
|
const lastNotified = await getConfigValue("LAST_NOTIFIED_VERSION") || "";
|
|
178
186
|
if (showHighlights && lastNotified !== currentVersion) {
|
|
@@ -180,7 +188,9 @@ export async function checkForUpdates() {
|
|
|
180
188
|
await setConfigValue("LAST_NOTIFIED_VERSION", currentVersion);
|
|
181
189
|
}
|
|
182
190
|
}
|
|
183
|
-
} catch {
|
|
184
|
-
|
|
191
|
+
} catch (err) {
|
|
192
|
+
if (force) {
|
|
193
|
+
console.log(label.system + " " + colors.warning(`⚠ Update check failed: ${err.message}`));
|
|
194
|
+
}
|
|
185
195
|
}
|
|
186
196
|
}
|
package/test/dx.test.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { isValidConfigKey } from "../src/config.js";
|
|
4
|
+
|
|
5
|
+
test("Developer Experience (DX) Commands Suite", async (t) => {
|
|
6
|
+
await t.test("isValidConfigKey whitelists DIAGNOSE_CMD correctly", () => {
|
|
7
|
+
assert.strictEqual(isValidConfigKey("DIAGNOSE_CMD"), true);
|
|
8
|
+
assert.strictEqual(isValidConfigKey("diagnose_cmd"), true);
|
|
9
|
+
});
|
|
10
|
+
});
|
package/test/updater.test.js
CHANGED
|
@@ -87,4 +87,23 @@ test("Auto-Updater & Highlights Suite", async (t) => {
|
|
|
87
87
|
const updatedCheck = parseInt(await getConfigValue("LAST_UPDATE_CHECK") || "0", 10);
|
|
88
88
|
assert.ok(updatedCheck > now - 10000);
|
|
89
89
|
});
|
|
90
|
+
|
|
91
|
+
await t.test("checkForUpdates(true) bypasses 24h throttling when force is true", async () => {
|
|
92
|
+
const now = Date.now();
|
|
93
|
+
// Set last check to 1 hour ago (would normally throttle)
|
|
94
|
+
await setConfigValue("LAST_UPDATE_CHECK", (now - 60 * 60 * 1000).toString());
|
|
95
|
+
|
|
96
|
+
let fetchCalled = false;
|
|
97
|
+
globalThis.fetch = async (url) => {
|
|
98
|
+
fetchCalled = true;
|
|
99
|
+
assert.ok(url.includes("registry.npmjs.org"));
|
|
100
|
+
return {
|
|
101
|
+
ok: true,
|
|
102
|
+
json: async () => ({ version: pkg.version })
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
await checkForUpdates(true); // force = true
|
|
107
|
+
assert.strictEqual(fetchCalled, true);
|
|
108
|
+
});
|
|
90
109
|
});
|