aether-ai-agent-cli 1.1.4__py3-none-any.whl
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.
- aether_ai_agent_cli-1.1.4.dist-info/METADATA +309 -0
- aether_ai_agent_cli-1.1.4.dist-info/RECORD +25 -0
- aether_ai_agent_cli-1.1.4.dist-info/WHEEL +5 -0
- aether_ai_agent_cli-1.1.4.dist-info/entry_points.txt +2 -0
- aether_ai_agent_cli-1.1.4.dist-info/licenses/LICENSE +21 -0
- aether_ai_agent_cli-1.1.4.dist-info/top_level.txt +1 -0
- aether_pip/__init__.py +1 -0
- aether_pip/cli.py +49 -0
- aether_pip/node_project/bin/aether.js +10 -0
- aether_pip/node_project/package-lock.json +794 -0
- aether_pip/node_project/package.json +46 -0
- aether_pip/node_project/src/ai/fallback.js +179 -0
- aether_pip/node_project/src/ai/google.js +87 -0
- aether_pip/node_project/src/ai/providers.js +203 -0
- aether_pip/node_project/src/ai/router.js +114 -0
- aether_pip/node_project/src/ai/universal.js +507 -0
- aether_pip/node_project/src/ai/xai.js +50 -0
- aether_pip/node_project/src/chat.js +1018 -0
- aether_pip/node_project/src/cli.js +679 -0
- aether_pip/node_project/src/config.js +214 -0
- aether_pip/node_project/src/file-parser.js +94 -0
- aether_pip/node_project/src/modes.js +121 -0
- aether_pip/node_project/src/ui/banner.js +60 -0
- aether_pip/node_project/src/ui/spinner.js +43 -0
- aether_pip/node_project/src/ui/theme.js +169 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════
|
|
2
|
+
// AETHER AI CLI — Secure Configuration Management
|
|
3
|
+
// Stores user API keys locally at ~/.aether/config.json
|
|
4
|
+
// Supports ALL AI providers (13+ and growing)
|
|
5
|
+
// ═══════════════════════════════════════════════════════════
|
|
6
|
+
|
|
7
|
+
import { readFile, writeFile, mkdir, unlink, access } from "node:fs/promises";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { getAllConfigKeys } from "./ai/providers.js";
|
|
11
|
+
|
|
12
|
+
const CONFIG_DIR = join(homedir(), ".aether");
|
|
13
|
+
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
14
|
+
|
|
15
|
+
const SENSITIVE_PATTERNS = ["KEY", "TOKEN", "SECRET"];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Returns the full path to the config file.
|
|
19
|
+
* @returns {string}
|
|
20
|
+
*/
|
|
21
|
+
export function getConfigPath() {
|
|
22
|
+
return CONFIG_FILE;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Loads the config from disk.
|
|
27
|
+
* @returns {Promise<object>} Parsed config or empty object
|
|
28
|
+
*/
|
|
29
|
+
export async function loadConfig() {
|
|
30
|
+
try {
|
|
31
|
+
const raw = await readFile(CONFIG_FILE, "utf-8");
|
|
32
|
+
return JSON.parse(raw);
|
|
33
|
+
} catch {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Saves config to disk, creating the directory if needed.
|
|
40
|
+
* @param {object} config - The config object to write
|
|
41
|
+
*/
|
|
42
|
+
export async function saveConfig(config) {
|
|
43
|
+
try {
|
|
44
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
45
|
+
await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
|
|
46
|
+
} catch (err) {
|
|
47
|
+
throw new Error(`Failed to save config: ${err.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Gets a single config value.
|
|
53
|
+
* @param {string} key
|
|
54
|
+
* @returns {Promise<string|undefined>}
|
|
55
|
+
*/
|
|
56
|
+
export async function getConfigValue(key) {
|
|
57
|
+
const config = await loadConfig();
|
|
58
|
+
return config[key];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Sets a single config value.
|
|
63
|
+
* @param {string} key
|
|
64
|
+
* @param {string} value
|
|
65
|
+
*/
|
|
66
|
+
export async function setConfigValue(key, value) {
|
|
67
|
+
const config = await loadConfig();
|
|
68
|
+
config[key] = value;
|
|
69
|
+
await saveConfig(config);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Deletes a single config key.
|
|
74
|
+
* @param {string} key
|
|
75
|
+
*/
|
|
76
|
+
export async function deleteConfigValue(key) {
|
|
77
|
+
const config = await loadConfig();
|
|
78
|
+
delete config[key];
|
|
79
|
+
await saveConfig(config);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Deletes the entire config file.
|
|
84
|
+
*/
|
|
85
|
+
export async function resetConfig() {
|
|
86
|
+
try {
|
|
87
|
+
await unlink(CONFIG_FILE);
|
|
88
|
+
} catch {
|
|
89
|
+
// File may not exist
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Lists all config keys with sensitive values masked.
|
|
95
|
+
* @returns {Promise<object>} Config with sensitive values masked
|
|
96
|
+
*/
|
|
97
|
+
export async function listConfig() {
|
|
98
|
+
const config = await loadConfig();
|
|
99
|
+
const masked = {};
|
|
100
|
+
|
|
101
|
+
for (const [key, value] of Object.entries(config)) {
|
|
102
|
+
const isSensitive = SENSITIVE_PATTERNS.some((p) => key.toUpperCase().includes(p));
|
|
103
|
+
if (isSensitive && typeof value === "string" && value.length > 8) {
|
|
104
|
+
masked[key] = value.slice(0, 6) + "•••" + value.slice(-3);
|
|
105
|
+
} else {
|
|
106
|
+
masked[key] = value;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return masked;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Returns the full flat config object for the AI router.
|
|
115
|
+
* Merges the config file with environment variables (config file takes precedence).
|
|
116
|
+
* @returns {Promise<object>}
|
|
117
|
+
*/
|
|
118
|
+
export async function getAIConfig() {
|
|
119
|
+
const config = await loadConfig();
|
|
120
|
+
const allKeys = getAllConfigKeys();
|
|
121
|
+
|
|
122
|
+
// Merge: config file values override env vars
|
|
123
|
+
const merged = {};
|
|
124
|
+
for (const key of allKeys) {
|
|
125
|
+
merged[key] = config[key] || process.env[key] || "";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Also pass through any custom model overrides and extra keys
|
|
129
|
+
for (const [key, value] of Object.entries(config)) {
|
|
130
|
+
if (!merged[key]) {
|
|
131
|
+
merged[key] = value;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Check env vars for anything the config didn't have
|
|
136
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
137
|
+
if (key.endsWith("_API_KEY") && !merged[key]) {
|
|
138
|
+
merged[key] = value;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return merged;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Checks if the config file exists.
|
|
147
|
+
* @returns {Promise<boolean>}
|
|
148
|
+
*/
|
|
149
|
+
export async function configExists() {
|
|
150
|
+
try {
|
|
151
|
+
await access(CONFIG_FILE);
|
|
152
|
+
return true;
|
|
153
|
+
} catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Checks if the given key is a valid/recognized config key.
|
|
160
|
+
* Accepts any *_API_KEY, *_MODEL, and known keys.
|
|
161
|
+
* @param {string} key
|
|
162
|
+
* @returns {boolean}
|
|
163
|
+
*/
|
|
164
|
+
export function isValidConfigKey(key) {
|
|
165
|
+
const upper = key.toUpperCase();
|
|
166
|
+
// Accept any API key or model override
|
|
167
|
+
if (upper.endsWith("_API_KEY") || upper.endsWith("_API_KEYS") || upper.endsWith("_MODEL") || upper === "THEME" || upper === "CUSTOM_COMMANDS") {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
// Accept known config keys
|
|
171
|
+
const knownKeys = getAllConfigKeys();
|
|
172
|
+
return knownKeys.includes(upper);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const HISTORY_FILE = join(CONFIG_DIR, "history.json");
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Loads chat history from disk.
|
|
179
|
+
* @returns {Promise<Array>} List of chat exchanges
|
|
180
|
+
*/
|
|
181
|
+
export async function loadHistory() {
|
|
182
|
+
try {
|
|
183
|
+
const raw = await readFile(HISTORY_FILE, "utf-8");
|
|
184
|
+
return JSON.parse(raw);
|
|
185
|
+
} catch {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Saves chat history to disk.
|
|
192
|
+
* @param {Array} history - List of chat exchanges to save
|
|
193
|
+
*/
|
|
194
|
+
export async function saveHistory(history) {
|
|
195
|
+
try {
|
|
196
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
197
|
+
// Limit saved history to last 50 entries to keep it light
|
|
198
|
+
const trimmed = history.slice(-50);
|
|
199
|
+
await writeFile(HISTORY_FILE, JSON.stringify(trimmed, null, 2), "utf-8");
|
|
200
|
+
} catch {
|
|
201
|
+
// Fail silently to not block chat
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Deletes the chat history file.
|
|
207
|
+
*/
|
|
208
|
+
export async function clearHistory() {
|
|
209
|
+
try {
|
|
210
|
+
await unlink(HISTORY_FILE);
|
|
211
|
+
} catch {
|
|
212
|
+
// File may not exist
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════
|
|
2
|
+
// AETHER AI CLI — File Parser & Context Injector
|
|
3
|
+
// ═══════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
import { readFile, stat } from "node:fs/promises";
|
|
6
|
+
import { resolve, extname, basename } from "node:path";
|
|
7
|
+
|
|
8
|
+
const MAX_CONTENT_LENGTH = 30000;
|
|
9
|
+
|
|
10
|
+
const SUPPORTED_EXTENSIONS = new Set([
|
|
11
|
+
".txt", ".md", ".json", ".csv", ".js", ".jsx", ".ts", ".tsx",
|
|
12
|
+
".html", ".css", ".py", ".log", ".yaml", ".yml", ".xml",
|
|
13
|
+
".toml", ".env", ".sh", ".bat", ".ps1", ".sql", ".rs",
|
|
14
|
+
".go", ".java", ".c", ".cpp", ".h", ".rb", ".php",
|
|
15
|
+
".swift", ".kt", ".dart", ".vue", ".svelte",
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Reads and parses a file for context injection.
|
|
20
|
+
* @param {string} filePath - Path to the file (absolute or relative)
|
|
21
|
+
* @returns {Promise<{ name: string, content: string, size: number, extension: string }>}
|
|
22
|
+
*/
|
|
23
|
+
export async function parseFile(filePath) {
|
|
24
|
+
const resolved = resolve(filePath);
|
|
25
|
+
const ext = extname(resolved).toLowerCase();
|
|
26
|
+
const name = basename(resolved);
|
|
27
|
+
|
|
28
|
+
// Validate extension
|
|
29
|
+
if (!SUPPORTED_EXTENSIONS.has(ext)) {
|
|
30
|
+
const supported = [...SUPPORTED_EXTENSIONS].join(", ");
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Unsupported file type: "${ext}"\nSupported types: ${supported}`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check file exists and get size
|
|
37
|
+
let fileStats;
|
|
38
|
+
try {
|
|
39
|
+
fileStats = await stat(resolved);
|
|
40
|
+
} catch {
|
|
41
|
+
throw new Error(`File not found: ${resolved}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!fileStats.isFile()) {
|
|
45
|
+
throw new Error(`Not a file: ${resolved}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Read file content
|
|
49
|
+
let content;
|
|
50
|
+
try {
|
|
51
|
+
content = await readFile(resolved, "utf-8");
|
|
52
|
+
} catch (err) {
|
|
53
|
+
throw new Error(`Cannot read file: ${err.message}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Trim if too long
|
|
57
|
+
if (content.length > MAX_CONTENT_LENGTH) {
|
|
58
|
+
content = content.slice(0, MAX_CONTENT_LENGTH) +
|
|
59
|
+
`\n\n[... truncated at ${MAX_CONTENT_LENGTH.toLocaleString()} characters]`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
name,
|
|
64
|
+
content: content.trim(),
|
|
65
|
+
size: fileStats.size,
|
|
66
|
+
extension: ext,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Formats parsed file data into a context string for prompt injection.
|
|
72
|
+
* @param {{ name: string, content: string, size: number, extension: string }} fileData
|
|
73
|
+
* @returns {string}
|
|
74
|
+
*/
|
|
75
|
+
export function formatContext(fileData) {
|
|
76
|
+
return [
|
|
77
|
+
`[Context File: ${fileData.name} (${formatBytes(fileData.size)}, ${fileData.extension})]`,
|
|
78
|
+
"---",
|
|
79
|
+
fileData.content,
|
|
80
|
+
"---",
|
|
81
|
+
`[End of ${fileData.name}]`,
|
|
82
|
+
].join("\n");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Formats bytes into a human-readable string.
|
|
87
|
+
* @param {number} bytes
|
|
88
|
+
* @returns {string}
|
|
89
|
+
*/
|
|
90
|
+
function formatBytes(bytes) {
|
|
91
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
92
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
93
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
94
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════
|
|
2
|
+
// AETHER AI CLI — Mode Definitions
|
|
3
|
+
// ═══════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* AI reasoning mode definitions for Aether Core.
|
|
7
|
+
* Each mode controls the system prompt, signal metrics, and response style.
|
|
8
|
+
*/
|
|
9
|
+
export const MODES = {
|
|
10
|
+
synthesis: {
|
|
11
|
+
name: "synthesis",
|
|
12
|
+
label: "Synthesis v2.5",
|
|
13
|
+
layer: "Layer 2.5",
|
|
14
|
+
description: "Balanced reasoning with clean structure and direct answers.",
|
|
15
|
+
signal: { reasoning: 72, clarity: 80, systemIQ: 70, delivery: 82 },
|
|
16
|
+
systemPrompt: [
|
|
17
|
+
"You are Aether, an advanced AI assistant running in Synthesis mode.",
|
|
18
|
+
"Provide balanced, clearly structured responses with direct answers.",
|
|
19
|
+
"Keep responses concise but thorough. Use markdown formatting.",
|
|
20
|
+
"Focus on clarity and practical utility. Avoid unnecessary verbosity.",
|
|
21
|
+
"CRITICAL: If the user asks who created you or who made you, you must answer that you were created by Krishiv PB.",
|
|
22
|
+
"FILE ACTIONS: If the user requests to create, write, or save a file, format the file content inside: [WRITE_FILE: path/to/file.ext]\\n<content>\\n[END_WRITE]. Aether CLI will intercept this block and write the file locally."
|
|
23
|
+
].join(" "),
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
research: {
|
|
27
|
+
name: "research",
|
|
28
|
+
label: "Research v104",
|
|
29
|
+
layer: "Layer 104",
|
|
30
|
+
description: "Deep analysis with comparisons and evidence-based reasoning.",
|
|
31
|
+
signal: { reasoning: 85, clarity: 78, systemIQ: 82, delivery: 75 },
|
|
32
|
+
systemPrompt: [
|
|
33
|
+
"You are Aether, an advanced AI assistant running in Research mode.",
|
|
34
|
+
"Provide deep analytical responses with evidence-based reasoning.",
|
|
35
|
+
"Include comparisons, citations where relevant, and thorough analysis.",
|
|
36
|
+
"Break down complex topics systematically. Use markdown with headers and lists.",
|
|
37
|
+
"CRITICAL: If the user asks who created you or who made you, you must answer that you were created by Krishiv PB.",
|
|
38
|
+
"FILE ACTIONS: If the user requests to create, write, or save a file, format the file content inside: [WRITE_FILE: path/to/file.ext]\\n<content>\\n[END_WRITE]. Aether CLI will intercept this block and write the file locally."
|
|
39
|
+
].join(" "),
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
architect: {
|
|
43
|
+
name: "architect",
|
|
44
|
+
label: "Architect v55",
|
|
45
|
+
layer: "Layer 55",
|
|
46
|
+
description: "Systems thinking with debugging plans and build strategies.",
|
|
47
|
+
signal: { reasoning: 78, clarity: 74, systemIQ: 90, delivery: 72 },
|
|
48
|
+
systemPrompt: [
|
|
49
|
+
"You are Aether, an advanced AI assistant running in Architect mode.",
|
|
50
|
+
"Focus on systems thinking, architecture design, and debugging plans.",
|
|
51
|
+
"Provide step-by-step build strategies and implementation roadmaps.",
|
|
52
|
+
"Think about edge cases, scalability, and best practices. Use code blocks when relevant.",
|
|
53
|
+
"CRITICAL: If the user asks who created you or who made you, you must answer that you were created by Krishiv PB.",
|
|
54
|
+
"FILE ACTIONS: If the user requests to create, write, or save a file, format the file content inside: [WRITE_FILE: path/to/file.ext]\\n<content>\\n[END_WRITE]. Aether CLI will intercept this block and write the file locally."
|
|
55
|
+
].join(" "),
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
titan: {
|
|
59
|
+
name: "titan",
|
|
60
|
+
label: "Titan Fusion v110",
|
|
61
|
+
layer: "Layer 110",
|
|
62
|
+
description: "Long-form premium responses with high signal density and multi-step output.",
|
|
63
|
+
signal: { reasoning: 88, clarity: 92, systemIQ: 95, delivery: 90 },
|
|
64
|
+
systemPrompt: [
|
|
65
|
+
"You are Aether, an advanced AI assistant running in Titan Fusion mode — the most powerful configuration.",
|
|
66
|
+
"Provide comprehensive, premium-quality responses with maximum signal density.",
|
|
67
|
+
"Use structured formatting: headers, bullet points, code blocks, and clear sections.",
|
|
68
|
+
"Deliver multi-step analysis when appropriate. Be thorough, precise, and insightful.",
|
|
69
|
+
"This is the highest quality mode — treat every response as a masterclass.",
|
|
70
|
+
"CRITICAL: If the user asks who created you or who made you, you must answer that you were created by Krishiv PB.",
|
|
71
|
+
"FILE ACTIONS: If the user requests to create, write, or save a file, format the file content inside: [WRITE_FILE: path/to/file.ext]\n<content>\n[END_WRITE]. Aether CLI will intercept this block and write the file locally."
|
|
72
|
+
].join(" "),
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
codex: {
|
|
76
|
+
name: "codex",
|
|
77
|
+
label: "OpenAI Codex v3",
|
|
78
|
+
layer: "Layer 45",
|
|
79
|
+
description: "Specialized code generation mode optimized for writing pure, robust, and clean source code across all programming languages.",
|
|
80
|
+
signal: { reasoning: 80, clarity: 85, systemIQ: 85, delivery: 90 },
|
|
81
|
+
systemPrompt: [
|
|
82
|
+
"You are Aether, an advanced AI assistant running in OpenAI Codex mode, optimized specifically for high-fidelity code generation.",
|
|
83
|
+
"Your primary objective is to write robust, syntactically correct, and beautifully structured source code across all programming languages (HTML, CSS, JavaScript, Python, C++, Go, etc.).",
|
|
84
|
+
"Minimize conversational filler, explain code concisely when asked, and output highly functional, ready-to-run files.",
|
|
85
|
+
"CRITICAL: If the user asks who created you or who made you, you must answer that you were created by Krishiv PB.",
|
|
86
|
+
"FILE ACTIONS: If the user requests to create, write, or save a file, format the file content inside: [WRITE_FILE: path/to/file.ext]\n<content>\n[END_WRITE]. Aether CLI will intercept this block and write the file locally."
|
|
87
|
+
].join(" "),
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
"cloude-code": {
|
|
91
|
+
name: "cloude-code",
|
|
92
|
+
label: "Claude Code Agent",
|
|
93
|
+
layer: "Layer 120",
|
|
94
|
+
description: "Agentic software development mode inspired by Claude Code, specializing in refactoring, editing files, and debugging web applications.",
|
|
95
|
+
signal: { reasoning: 92, clarity: 88, systemIQ: 94, delivery: 85 },
|
|
96
|
+
systemPrompt: [
|
|
97
|
+
"You are Aether, an advanced AI assistant running in Claude Code mode, an agentic developer configuration designed for sophisticated software engineering.",
|
|
98
|
+
"Your specialty is systems refactoring, code editing, full-stack web application development (HTML/CSS/JS), and debugging complex codebases.",
|
|
99
|
+
"Generate complete, clean code blocks and explain implementation plans systematically.",
|
|
100
|
+
"CRITICAL: If the user asks who created you or who made you, you must answer that you were created by Krishiv PB.",
|
|
101
|
+
"FILE ACTIONS: If the user requests to create, write, or save a file, format the file content inside: [WRITE_FILE: path/to/file.ext]\n<content>\n[END_WRITE]. Aether CLI will intercept this block and write the file locally."
|
|
102
|
+
].join(" "),
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/** The default mode key */
|
|
107
|
+
export const DEFAULT_MODE = "titan";
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Looks up a mode by name (case-insensitive).
|
|
111
|
+
* @param {string} name - Mode name to look up
|
|
112
|
+
* @returns {object|null} The mode definition, or null if not found
|
|
113
|
+
*/
|
|
114
|
+
export function getModeByName(name) {
|
|
115
|
+
if (!name) return null;
|
|
116
|
+
const key = name.toLowerCase().trim();
|
|
117
|
+
if (key === "claude-code" || key === "claude") {
|
|
118
|
+
return MODES["cloude-code"];
|
|
119
|
+
}
|
|
120
|
+
return MODES[key] || null;
|
|
121
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════
|
|
2
|
+
// AETHER AI CLI — ASCII Art Welcome Banner
|
|
3
|
+
// ═══════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { colors, separator, bullet } from "./theme.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Displays the cyberpunk-styled Aether ASCII art banner.
|
|
10
|
+
* @param {string} [currentMode='titan'] - The currently active mode name
|
|
11
|
+
*/
|
|
12
|
+
export function showBanner(currentMode = "titan") {
|
|
13
|
+
const c1 = colors.accent;
|
|
14
|
+
const c2 = colors.accent2;
|
|
15
|
+
const c3 = colors.accent3;
|
|
16
|
+
const dim = colors.dim;
|
|
17
|
+
|
|
18
|
+
const art = [
|
|
19
|
+
"",
|
|
20
|
+
c1(" ╔═══════════════════════════════════════════════════════════╗"),
|
|
21
|
+
c1(" ║") + c2(" █████╗ ███████╗████████╗██╗ ██╗███████╗██████╗ ") + c1("║"),
|
|
22
|
+
c1(" ║") + c2(" ██╔══██╗██╔════╝╚══██╔══╝██║ ██║██╔════╝██╔══██╗ ") + c1("║"),
|
|
23
|
+
c1(" ║") + c1(" ███████║█████╗ ██║ ████████║█████╗ ██████╔╝ ") + c1("║"),
|
|
24
|
+
c1(" ║") + c3(" ██╔══██║██╔══╝ ██║ ██╔══██║██╔══╝ ██╔══██╗ ") + c1("║"),
|
|
25
|
+
c1(" ║") + c3(" ██║ ██║███████╗ ██║ ██║ ██║███████╗██║ ██║ ") + c1("║"),
|
|
26
|
+
c1(" ║") + dim(" ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ") + c1("║"),
|
|
27
|
+
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
|
+
];
|
|
43
|
+
|
|
44
|
+
console.log(art.join("\n"));
|
|
45
|
+
}
|
|
46
|
+
|
|
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"),
|
|
58
|
+
};
|
|
59
|
+
return labels[mode?.toLowerCase()] || labels.titan;
|
|
60
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════
|
|
2
|
+
// AETHER AI CLI — Spinner Helpers
|
|
3
|
+
// ═══════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
import ora from "ora";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates a styled spinner with the Aether theme.
|
|
9
|
+
* @param {string} text - Spinner label text
|
|
10
|
+
* @returns {object} An ora spinner instance
|
|
11
|
+
*/
|
|
12
|
+
export function createSpinner(text) {
|
|
13
|
+
return ora({
|
|
14
|
+
text,
|
|
15
|
+
spinner: {
|
|
16
|
+
interval: 80,
|
|
17
|
+
frames: ["▖", "▘", "▝", "▗"],
|
|
18
|
+
},
|
|
19
|
+
color: "cyan",
|
|
20
|
+
discardStdin: false,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Wraps an async function with a loading spinner.
|
|
26
|
+
* Shows the spinner while the function runs and reports success/failure.
|
|
27
|
+
* @param {string} text - The loading message
|
|
28
|
+
* @param {Function} asyncFn - The async function to execute
|
|
29
|
+
* @returns {Promise<*>} The result of the async function
|
|
30
|
+
*/
|
|
31
|
+
export async function withSpinner(text, asyncFn) {
|
|
32
|
+
const spinner = createSpinner(text);
|
|
33
|
+
spinner.start();
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const result = await asyncFn();
|
|
37
|
+
spinner.succeed();
|
|
38
|
+
return result;
|
|
39
|
+
} catch (err) {
|
|
40
|
+
spinner.fail(err.message || "Operation failed");
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
}
|