aegiscode 3.1.8 → 3.1.10
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/README.md +23 -15
- package/dist/main.js +549 -552
- package/package.json +4 -2
- package/src/agent/Agent.ts +903 -0
- package/src/agent/SimpleAgent.ts +48 -0
- package/src/agent/index.ts +54 -0
- package/src/agent/orchestrator/AppBuilder.ts +443 -0
- package/src/agent/orchestrator/CouncilAgent.ts +310 -0
- package/src/agent/orchestrator/DiscussionRoom.ts +462 -0
- package/src/agent/orchestrator/OrchestratorAgent.ts +637 -0
- package/src/agent/orchestrator/index.ts +38 -0
- package/src/agent/orchestrator/utils.ts +397 -0
- package/src/agent/pricing.ts +115 -0
- package/src/agent/router.ts +74 -0
- package/src/agent/routerStats.ts +121 -0
- package/src/agent/types.ts +318 -0
- package/src/auth/login.ts +383 -0
- package/src/cli/config.ts +189 -0
- package/src/cli/index.ts +17 -0
- package/src/cli/middleware.ts +119 -0
- package/src/cli/types.ts +75 -0
- package/src/config/ConfigManager.ts +587 -0
- package/src/config/index.ts +7 -0
- package/src/config/types.ts +584 -0
- package/src/context/CompactionService.ts +300 -0
- package/src/context/ContextManager.ts +450 -0
- package/src/context/FileAnalyzer.ts +267 -0
- package/src/context/TokenCounter.ts +265 -0
- package/src/context/index.ts +27 -0
- package/src/context/storage/CacheStore.ts +176 -0
- package/src/context/storage/JSONLStore.ts +201 -0
- package/src/context/storage/MemoryStore.ts +205 -0
- package/src/context/storage/PersistentStore.ts +327 -0
- package/src/context/storage/index.ts +9 -0
- package/src/context/storage/pathUtils.ts +114 -0
- package/src/context/test.ts +309 -0
- package/src/context/types.ts +268 -0
- package/src/hooks/HookExecutor.ts +434 -0
- package/src/hooks/HookManager.ts +596 -0
- package/src/hooks/HookService.ts +269 -0
- package/src/hooks/Matcher.ts +157 -0
- package/src/hooks/index.ts +63 -0
- package/src/hooks/types.ts +424 -0
- package/src/main.tsx +596 -0
- package/src/mcp/HealthMonitor.ts +150 -0
- package/src/mcp/McpClient.ts +491 -0
- package/src/mcp/McpRegistry.ts +321 -0
- package/src/mcp/createMcpTool.ts +251 -0
- package/src/mcp/index.ts +15 -0
- package/src/mcp/server.ts +334 -0
- package/src/mcp/test-server.ts +88 -0
- package/src/mcp/test.ts +372 -0
- package/src/mcp/types.ts +247 -0
- package/src/memory/AgentMemoryBus.ts +432 -0
- package/src/memory/CloudSync.ts +99 -0
- package/src/memory/DriveSync.ts +106 -0
- package/src/memory/SharedMemory.ts +951 -0
- package/src/memory/index.ts +14 -0
- package/src/memory/machineFingerprint.ts +40 -0
- package/src/orchestrator/SubAgentMetadata.ts +136 -0
- package/src/prompts/builder.ts +213 -0
- package/src/prompts/default.ts +144 -0
- package/src/prompts/index.ts +16 -0
- package/src/prompts/plan.ts +64 -0
- package/src/prompts/test.ts +78 -0
- package/src/services/AnthropicChatService.ts +341 -0
- package/src/services/ChatService.ts +347 -0
- package/src/services/ClaudeCliChatService.ts +256 -0
- package/src/services/CloudSync.ts +168 -0
- package/src/services/CostLedger.ts +211 -0
- package/src/services/Heartbeat.ts +135 -0
- package/src/services/LearningCollector.ts +291 -0
- package/src/services/OllamaInstaller.ts +342 -0
- package/src/services/VersionChecker.ts +445 -0
- package/src/services/index.ts +57 -0
- package/src/services/streaming/OpenAIEventAdapter.ts +126 -0
- package/src/services/streaming/RenderingProfile.ts +90 -0
- package/src/services/streaming/StreamEventParser.ts +181 -0
- package/src/services/streaming/ThrottledRenderer.ts +139 -0
- package/src/services/streaming/TranscriptBuffer.ts +574 -0
- package/src/services/streaming/eventStatusMap.ts +52 -0
- package/src/services/streaming/index.ts +46 -0
- package/src/services/streaming/renderFormatting.ts +79 -0
- package/src/services/streaming/types.ts +234 -0
- package/src/skills/SkillLoader.ts +126 -0
- package/src/skills/SkillRegistry.ts +366 -0
- package/src/skills/index.ts +48 -0
- package/src/skills/types.ts +146 -0
- package/src/slash-commands/billing.ts +70 -0
- package/src/slash-commands/build.ts +413 -0
- package/src/slash-commands/builtinCommands.ts +2733 -0
- package/src/slash-commands/clone.ts +242 -0
- package/src/slash-commands/council.ts +125 -0
- package/src/slash-commands/custom/CustomCommandExecutor.ts +143 -0
- package/src/slash-commands/custom/CustomCommandLoader.ts +251 -0
- package/src/slash-commands/custom/CustomCommandRegistry.ts +144 -0
- package/src/slash-commands/custom/index.ts +7 -0
- package/src/slash-commands/debate.ts +254 -0
- package/src/slash-commands/gmail.ts +105 -0
- package/src/slash-commands/index.ts +388 -0
- package/src/slash-commands/mcpCommand.ts +205 -0
- package/src/slash-commands/types.ts +201 -0
- package/src/store/index.ts +76 -0
- package/src/store/selectors.ts +246 -0
- package/src/store/slices/appSlice.ts +205 -0
- package/src/store/slices/commandSlice.ts +115 -0
- package/src/store/slices/configSlice.ts +46 -0
- package/src/store/slices/focusSlice.ts +64 -0
- package/src/store/slices/index.ts +9 -0
- package/src/store/slices/sessionSlice.ts +424 -0
- package/src/store/streaming-buffer.ts +425 -0
- package/src/store/test.ts +296 -0
- package/src/store/types.ts +274 -0
- package/src/store/vanilla.ts +186 -0
- package/src/tools/builtin/bash.ts +236 -0
- package/src/tools/builtin/council.ts +105 -0
- package/src/tools/builtin/edit.ts +213 -0
- package/src/tools/builtin/glob.ts +136 -0
- package/src/tools/builtin/grep.ts +263 -0
- package/src/tools/builtin/index.ts +61 -0
- package/src/tools/builtin/memory.ts +66 -0
- package/src/tools/builtin/read.ts +168 -0
- package/src/tools/builtin/skill.ts +97 -0
- package/src/tools/builtin/snapshot.ts +40 -0
- package/src/tools/builtin/task.ts +106 -0
- package/src/tools/builtin/write.ts +134 -0
- package/src/tools/createTool.ts +221 -0
- package/src/tools/execution/ExecutionPipeline.ts +263 -0
- package/src/tools/execution/index.ts +40 -0
- package/src/tools/execution/stages/CacheStage.ts +131 -0
- package/src/tools/execution/stages/ConfirmationStage.ts +203 -0
- package/src/tools/execution/stages/DiscoveryStage.ts +46 -0
- package/src/tools/execution/stages/ExecutionStage.ts +48 -0
- package/src/tools/execution/stages/FormattingStage.ts +44 -0
- package/src/tools/execution/stages/HookStage.ts +72 -0
- package/src/tools/execution/stages/PermissionStage.ts +287 -0
- package/src/tools/execution/stages/PostHookStage.ts +71 -0
- package/src/tools/execution/stages/index.ts +12 -0
- package/src/tools/execution/test.ts +266 -0
- package/src/tools/execution/types.ts +273 -0
- package/src/tools/index.ts +81 -0
- package/src/tools/registry.ts +304 -0
- package/src/tools/schemas.ts +109 -0
- package/src/tools/test.ts +220 -0
- package/src/tools/types.ts +175 -0
- package/src/tools/validation/PermissionChecker.ts +242 -0
- package/src/tools/validation/SensitiveFileDetector.ts +210 -0
- package/src/tools/validation/index.ts +11 -0
- package/src/ui/App.tsx +166 -0
- package/src/ui/components/AegisInterface.tsx +484 -0
- package/src/ui/components/common/ChatSearch.tsx +150 -0
- package/src/ui/components/common/ErrorBoundary.tsx +82 -0
- package/src/ui/components/common/ExitMessage.tsx +120 -0
- package/src/ui/components/common/LoadingIndicator.tsx +49 -0
- package/src/ui/components/common/index.ts +6 -0
- package/src/ui/components/dialog/ConfirmationPrompt.tsx +208 -0
- package/src/ui/components/dialog/InteractiveSelector.tsx +149 -0
- package/src/ui/components/dialog/SetupWizard.tsx +297 -0
- package/src/ui/components/dialog/UpdatePrompt.tsx +155 -0
- package/src/ui/components/dialog/index.ts +8 -0
- package/src/ui/components/index.ts +28 -0
- package/src/ui/components/input/CommandSuggestions.tsx +139 -0
- package/src/ui/components/input/CustomTextInput.tsx +220 -0
- package/src/ui/components/input/InputArea.tsx +361 -0
- package/src/ui/components/input/PromptSuggestions.tsx +66 -0
- package/src/ui/components/input/index.ts +6 -0
- package/src/ui/components/layout/ChatStatusBar.tsx +90 -0
- package/src/ui/components/layout/ContextBar.tsx +79 -0
- package/src/ui/components/layout/MessageArea.tsx +96 -0
- package/src/ui/components/layout/MessageList.tsx +647 -0
- package/src/ui/components/layout/MessageSeparator.tsx +26 -0
- package/src/ui/components/layout/WelcomeMessage.tsx +93 -0
- package/src/ui/components/layout/index.ts +7 -0
- package/src/ui/components/markdown/CodeHighlighter.tsx +292 -0
- package/src/ui/components/markdown/MessageRenderer.tsx +1211 -0
- package/src/ui/components/markdown/index.ts +8 -0
- package/src/ui/components/markdown/parser.ts +336 -0
- package/src/ui/components/markdown/types.ts +66 -0
- package/src/ui/focus/FocusManager.ts +137 -0
- package/src/ui/focus/index.ts +13 -0
- package/src/ui/focus/types.ts +54 -0
- package/src/ui/focus/useFocus.ts +75 -0
- package/src/ui/hooks/index.ts +11 -0
- package/src/ui/hooks/useAgent.ts +284 -0
- package/src/ui/hooks/useCommandHistory.ts +87 -0
- package/src/ui/hooks/useCommandProcessor.ts +443 -0
- package/src/ui/hooks/useConfirmation.ts +99 -0
- package/src/ui/hooks/useCtrlCHandler.ts +100 -0
- package/src/ui/hooks/useInputBuffer.ts +122 -0
- package/src/ui/hooks/useTerminalSize.ts +68 -0
- package/src/ui/hooks/useTerminalWidth.ts +5 -0
- package/src/ui/hooks/useWindowedList.ts +118 -0
- package/src/ui/render-debugger.ts +621 -0
- package/src/ui/test.ts +189 -0
- package/src/ui/themes/ThemeManager.ts +332 -0
- package/src/ui/themes/aegisTheme.ts +87 -0
- package/src/ui/themes/darkTheme.ts +87 -0
- package/src/ui/themes/defaultTheme.ts +85 -0
- package/src/ui/themes/index.ts +10 -0
- package/src/ui/themes/lightTheme.ts +89 -0
- package/src/ui/themes/popularThemes.ts +187 -0
- package/src/ui/themes/types.ts +130 -0
- package/src/utils/clipboard.ts +48 -0
- package/src/utils/debug.ts +43 -0
- package/src/utils/environment.ts +68 -0
- package/src/utils/index.ts +10 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* aegis login — browser-based OAuth flow
|
|
3
|
+
*
|
|
4
|
+
* 1. Start a one-shot HTTP server on a random local port
|
|
5
|
+
* 2. Open the browser to aegiscloud.org/login?redirect_uri=...
|
|
6
|
+
* 3. Wait for the callback carrying ?token=...
|
|
7
|
+
* 4. Persist the token in ~/.aegiscode/config.json
|
|
8
|
+
* 5. Resolve (caller prints success) or reject with a readable error
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as http from 'node:http';
|
|
12
|
+
import * as fs from 'node:fs';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import * as os from 'node:os';
|
|
15
|
+
import { spawn } from 'node:child_process';
|
|
16
|
+
|
|
17
|
+
const CONFIG_FILE = path.join(os.homedir(), '.aegiscode', 'config.json');
|
|
18
|
+
const AEGISCLOUD_BASE = 'https://aegiscloud.org';
|
|
19
|
+
const LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
|
|
20
|
+
|
|
21
|
+
// ── Colors ──────────────────────────────────────────────────────────────────
|
|
22
|
+
const C = {
|
|
23
|
+
primary: '\x1b[38;2;0;229;192m',
|
|
24
|
+
muted: '\x1b[2m',
|
|
25
|
+
reset: '\x1b[0m',
|
|
26
|
+
bold: '\x1b[1m',
|
|
27
|
+
green: '\x1b[32m',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// ── Open browser ─────────────────────────────────────────────────────────────
|
|
31
|
+
function openBrowser(url: string): void {
|
|
32
|
+
// Linux: xdg-open; macOS: open; Windows: start
|
|
33
|
+
const cmd =
|
|
34
|
+
process.platform === 'darwin' ? 'open' :
|
|
35
|
+
process.platform === 'win32' ? 'cmd' : 'xdg-open';
|
|
36
|
+
const args = process.platform === 'win32' ? ['/c', 'start', url] : [url];
|
|
37
|
+
spawn(cmd, args, { detached: true, stdio: 'ignore' }).unref();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── Persist token ────────────────────────────────────────────────────────────
|
|
41
|
+
function saveToken(token: string): void {
|
|
42
|
+
let cfg: Record<string, any> = {};
|
|
43
|
+
try { cfg = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } catch {}
|
|
44
|
+
|
|
45
|
+
cfg.aegiscloud = { ...(cfg.aegiscloud ?? {}), api_key: token };
|
|
46
|
+
|
|
47
|
+
const dir = path.dirname(CONFIG_FILE);
|
|
48
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
49
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ── Success page served to the browser ──────────────────────────────────────
|
|
53
|
+
const SUCCESS_HTML = `<!DOCTYPE html>
|
|
54
|
+
<html>
|
|
55
|
+
<head>
|
|
56
|
+
<meta charset="utf-8">
|
|
57
|
+
<title>ÆGIS</title>
|
|
58
|
+
<style>
|
|
59
|
+
body { font-family: monospace; background: #0d1117; color: #00e5c0;
|
|
60
|
+
display: flex; align-items: center; justify-content: center;
|
|
61
|
+
height: 100vh; margin: 0; }
|
|
62
|
+
.card { text-align: center; }
|
|
63
|
+
h2 { font-size: 1.6rem; margin-bottom: .5rem; }
|
|
64
|
+
p { color: #888; }
|
|
65
|
+
</style>
|
|
66
|
+
</head>
|
|
67
|
+
<body>
|
|
68
|
+
<div class="card">
|
|
69
|
+
<h2>◆ Logged in</h2>
|
|
70
|
+
<p>You can close this tab and return to the terminal.</p>
|
|
71
|
+
</div>
|
|
72
|
+
</body>
|
|
73
|
+
</html>`;
|
|
74
|
+
|
|
75
|
+
const ERROR_HTML = (msg: string) => `<!DOCTYPE html>
|
|
76
|
+
<html>
|
|
77
|
+
<head><meta charset="utf-8"><title>ÆGIS</title>
|
|
78
|
+
<style>body{font-family:monospace;background:#0d1117;color:#ff5555;
|
|
79
|
+
display:flex;align-items:center;justify-content:center;height:100vh;margin:0;}</style>
|
|
80
|
+
</head>
|
|
81
|
+
<body><h2>✗ ${msg}</h2></body>
|
|
82
|
+
</html>`;
|
|
83
|
+
|
|
84
|
+
// ── Main export ──────────────────────────────────────────────────────────────
|
|
85
|
+
export async function runLogin(): Promise<{ token: string }> {
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
const server = http.createServer();
|
|
88
|
+
|
|
89
|
+
server.listen(0, '127.0.0.1', () => {
|
|
90
|
+
const { port } = server.address() as { port: number };
|
|
91
|
+
const callbackUrl = `http://127.0.0.1:${port}`;
|
|
92
|
+
const loginUrl = `${AEGISCLOUD_BASE}/auth/google?callback=${encodeURIComponent(callbackUrl)}`;
|
|
93
|
+
|
|
94
|
+
let settled = false;
|
|
95
|
+
|
|
96
|
+
const done = (err?: Error) => {
|
|
97
|
+
if (settled) return;
|
|
98
|
+
settled = true;
|
|
99
|
+
server.close();
|
|
100
|
+
if (err) reject(err);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const timer = setTimeout(
|
|
104
|
+
() => done(new Error('Login timed out after 5 minutes. Please try again.')),
|
|
105
|
+
LOGIN_TIMEOUT_MS,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
server.on('request', (req, res) => {
|
|
109
|
+
try {
|
|
110
|
+
const url = new URL(req.url ?? '/', `http://127.0.0.1:${port}`);
|
|
111
|
+
|
|
112
|
+
const token =
|
|
113
|
+
url.searchParams.get('token') ??
|
|
114
|
+
url.searchParams.get('api_key') ??
|
|
115
|
+
url.searchParams.get('access_token');
|
|
116
|
+
|
|
117
|
+
if (!token) {
|
|
118
|
+
res.writeHead(400, { 'Content-Type': 'text/html' })
|
|
119
|
+
.end(ERROR_HTML('No token received — please try again.'));
|
|
120
|
+
done(new Error('No token in callback URL'));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
res.writeHead(200, { 'Content-Type': 'text/html' }).end(SUCCESS_HTML);
|
|
125
|
+
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
try {
|
|
128
|
+
saveToken(token);
|
|
129
|
+
done();
|
|
130
|
+
resolve({ token });
|
|
131
|
+
} catch (e) {
|
|
132
|
+
done(e as Error);
|
|
133
|
+
}
|
|
134
|
+
} catch (e) {
|
|
135
|
+
res.writeHead(500).end();
|
|
136
|
+
done(e as Error);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// ── Print prompt ───────────────────────────────────────────────────────
|
|
141
|
+
process.stdout.write(
|
|
142
|
+
`\n${C.primary}◆ ÆGIS — Login with Google${C.reset}\n\n` +
|
|
143
|
+
` Opening browser...\n\n` +
|
|
144
|
+
` ${C.muted}${loginUrl}${C.reset}\n\n` +
|
|
145
|
+
` ${C.muted}Paste this URL manually if the browser doesn't open.${C.reset}\n\n` +
|
|
146
|
+
` ${C.muted}Waiting for Google authentication… (Ctrl+Z to cancel)${C.reset}\n`,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
openBrowser(loginUrl);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
server.on('error', (err) => {
|
|
153
|
+
reject(new Error(`Could not start local server: ${err.message}`));
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ── Username / password login ────────────────────────────────────────────────
|
|
159
|
+
export async function runLoginPassword(): Promise<void> {
|
|
160
|
+
const { createInterface } = await import('node:readline');
|
|
161
|
+
|
|
162
|
+
const ask = (prompt: string): Promise<string> =>
|
|
163
|
+
new Promise(resolve => {
|
|
164
|
+
process.stdout.write(prompt);
|
|
165
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: false });
|
|
166
|
+
rl.once('line', line => { rl.close(); resolve(line.trim()); });
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const askHidden = (prompt: string): Promise<string> =>
|
|
170
|
+
new Promise(resolve => {
|
|
171
|
+
process.stdout.write(prompt);
|
|
172
|
+
// Hide input by disabling echo
|
|
173
|
+
if (process.stdin.isTTY) (process.stdin as any).setRawMode?.(true);
|
|
174
|
+
let buf = '';
|
|
175
|
+
const onData = (chunk: Buffer) => {
|
|
176
|
+
for (const b of chunk) {
|
|
177
|
+
if (b === 13 || b === 10) { // Enter
|
|
178
|
+
process.stdout.write('\n');
|
|
179
|
+
if (process.stdin.isTTY) (process.stdin as any).setRawMode?.(false);
|
|
180
|
+
process.stdin.off('data', onData);
|
|
181
|
+
process.stdin.pause();
|
|
182
|
+
resolve(buf);
|
|
183
|
+
return;
|
|
184
|
+
} else if (b === 127 || b === 8) { // Backspace
|
|
185
|
+
buf = buf.slice(0, -1);
|
|
186
|
+
} else if (b >= 32) {
|
|
187
|
+
buf += String.fromCharCode(b);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
process.stdin.resume();
|
|
192
|
+
process.stdin.on('data', onData);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
process.stdout.write(`\n${C.primary}◆ ÆGIS — Login${C.reset}\n\n`);
|
|
196
|
+
|
|
197
|
+
const username = await ask(' Username: ');
|
|
198
|
+
if (!username) throw new Error('Username cannot be empty');
|
|
199
|
+
|
|
200
|
+
const password = await askHidden(' Password: ');
|
|
201
|
+
if (!password) throw new Error('Password cannot be empty');
|
|
202
|
+
|
|
203
|
+
process.stdout.write(`\n ${C.muted}Signing in…${C.reset}\n`);
|
|
204
|
+
|
|
205
|
+
const controller = new AbortController();
|
|
206
|
+
const timer = setTimeout(() => controller.abort(), 10_000);
|
|
207
|
+
try {
|
|
208
|
+
const res = await fetch(`${AEGISCLOUD_BASE}/login`, {
|
|
209
|
+
method: 'POST',
|
|
210
|
+
headers: { 'Content-Type': 'application/json' },
|
|
211
|
+
body: JSON.stringify({ username, password }),
|
|
212
|
+
signal: controller.signal,
|
|
213
|
+
});
|
|
214
|
+
clearTimeout(timer);
|
|
215
|
+
const data = await res.json() as any;
|
|
216
|
+
if (!data.success) throw new Error(data.error || 'Invalid credentials');
|
|
217
|
+
if (data.api_key) saveToken(data.api_key);
|
|
218
|
+
} catch (e: any) {
|
|
219
|
+
clearTimeout(timer);
|
|
220
|
+
if (e.name === 'AbortError') throw new Error('Request timed out — check your connection');
|
|
221
|
+
throw e;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ── Claude Code Pro/Max subscription login ──────────────────────────────────
|
|
226
|
+
// Stores the OAuth token from `claude setup-token` (or pasted manually) into
|
|
227
|
+
// ~/.aegiscode/.env as CLAUDE_CODE_OAUTH_TOKEN, which ConfigManager picks up
|
|
228
|
+
// as an alternative credential for the currently configured Anthropic model —
|
|
229
|
+
// it does not change which model is selected.
|
|
230
|
+
const ENV_FILE = path.join(os.homedir(), '.aegiscode', '.env');
|
|
231
|
+
|
|
232
|
+
function saveClaudeCodeOAuthToken(token: string): void {
|
|
233
|
+
let lines: string[] = [];
|
|
234
|
+
try { lines = fs.readFileSync(ENV_FILE, 'utf8').split('\n'); } catch {}
|
|
235
|
+
|
|
236
|
+
const filtered = lines.filter(l => !l.startsWith('CLAUDE_CODE_OAUTH_TOKEN='));
|
|
237
|
+
filtered.push(`CLAUDE_CODE_OAUTH_TOKEN=${token}`);
|
|
238
|
+
|
|
239
|
+
const dir = path.dirname(ENV_FILE);
|
|
240
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
241
|
+
fs.writeFileSync(ENV_FILE, filtered.filter(l => l.trim()).join('\n') + '\n');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// spawn('claude', ...)/`which claude` relies on PATH — same check ClaudeCliChatService
|
|
245
|
+
// uses, duplicated here (rather than imported) to keep this auth module's only
|
|
246
|
+
// dependency on node:child_process for the existing OAuth browser flow above.
|
|
247
|
+
function isClaudeCliInstalled(): boolean {
|
|
248
|
+
const home = os.homedir();
|
|
249
|
+
const candidates = [
|
|
250
|
+
...(process.env.PATH || '').split(':').map(dir => path.join(dir, 'claude')),
|
|
251
|
+
path.join(home, '.local', 'bin', 'claude'),
|
|
252
|
+
path.join(home, '.npm-global', 'bin', 'claude'),
|
|
253
|
+
'/usr/local/bin/claude',
|
|
254
|
+
'/opt/homebrew/bin/claude',
|
|
255
|
+
];
|
|
256
|
+
return candidates.some(p => p && fs.existsSync(p));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export async function runLoginClaudePro(): Promise<void> {
|
|
260
|
+
if (!isClaudeCliInstalled()) {
|
|
261
|
+
process.stdout.write(
|
|
262
|
+
`\n${C.primary}◆ ÆGIS — Login with Claude Code Pro / Max${C.reset}\n\n` +
|
|
263
|
+
` The claude CLI isn't installed yet — it's required for this login\n` +
|
|
264
|
+
` method (your subscription auth lives in its credential store).\n\n` +
|
|
265
|
+
` Install it, then run this command again:\n` +
|
|
266
|
+
` ${C.bold}npm install -g @anthropic-ai/claude-code${C.reset}\n\n`,
|
|
267
|
+
);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
process.stdout.write(
|
|
272
|
+
`\n${C.primary}◆ ÆGIS — Login with Claude Code Pro / Max${C.reset}\n\n` +
|
|
273
|
+
` This uses your claude.ai subscription instead of a pay-per-token API key.\n` +
|
|
274
|
+
` Generate a token with: ${C.bold}claude setup-token${C.reset}\n\n`,
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const { createInterface } = await import('node:readline');
|
|
278
|
+
const token = await new Promise<string>(resolve => {
|
|
279
|
+
process.stdout.write(' Paste the token here: ');
|
|
280
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: false });
|
|
281
|
+
rl.once('line', line => { rl.close(); resolve(line.trim()); });
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
if (!token) throw new Error('No token provided');
|
|
285
|
+
if (!token.startsWith('sk-ant-oat')) {
|
|
286
|
+
throw new Error('That doesn\'t look like a Claude Code OAuth token (expected it to start with "sk-ant-oat").');
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
saveClaudeCodeOAuthToken(token);
|
|
290
|
+
process.stdout.write(`\n ${C.green}✓ Saved.${C.reset} aegiscode will use your Claude Pro/Max subscription.\n\n`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ── Periodic account re-verification ─────────────────────────────────────────
|
|
294
|
+
// A stored api_key never expires on the client, so a revoked/deleted account
|
|
295
|
+
// would otherwise stay "logged in" forever. Re-check against the server at
|
|
296
|
+
// most once per cache window — frequent enough to catch revocation, rare
|
|
297
|
+
// enough that it doesn't add a network round trip to every single run.
|
|
298
|
+
const ACCOUNT_VERIFY_CACHE_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
299
|
+
|
|
300
|
+
/** Calls /api/me with the stored api_key. Clears the key and returns invalid on a 401. */
|
|
301
|
+
export async function verifyAccount(): Promise<{ valid: boolean; username?: string }> {
|
|
302
|
+
let cfg: Record<string, any> = {};
|
|
303
|
+
try { cfg = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } catch {}
|
|
304
|
+
|
|
305
|
+
const apiKey = cfg?.aegiscloud?.api_key;
|
|
306
|
+
if (!apiKey) return { valid: false };
|
|
307
|
+
|
|
308
|
+
const controller = new AbortController();
|
|
309
|
+
const timer = setTimeout(() => controller.abort(), 8000);
|
|
310
|
+
try {
|
|
311
|
+
const res = await fetch(`${AEGISCLOUD_BASE}/api/me`, {
|
|
312
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
313
|
+
signal: controller.signal,
|
|
314
|
+
});
|
|
315
|
+
clearTimeout(timer);
|
|
316
|
+
|
|
317
|
+
if (res.status === 401) {
|
|
318
|
+
delete cfg.aegiscloud.api_key;
|
|
319
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2));
|
|
320
|
+
return { valid: false };
|
|
321
|
+
}
|
|
322
|
+
if (!res.ok) return { valid: true }; // server hiccup — fail open, don't lock the user out
|
|
323
|
+
|
|
324
|
+
const data = await res.json() as { username?: string };
|
|
325
|
+
cfg.aegiscloud = { ...cfg.aegiscloud, lastVerified: new Date().toISOString() };
|
|
326
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2));
|
|
327
|
+
return { valid: true, username: data.username };
|
|
328
|
+
} catch {
|
|
329
|
+
clearTimeout(timer);
|
|
330
|
+
// Network error — fail open so a flaky/offline connection doesn't force a re-login.
|
|
331
|
+
return { valid: true };
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* True if there's a stored api_key and it's either freshly verified (within
|
|
337
|
+
* the cache window) or just passed a live re-check. False only when the
|
|
338
|
+
* server explicitly rejected the key (revoked/deleted account).
|
|
339
|
+
*/
|
|
340
|
+
export async function ensureAccountValid(): Promise<boolean> {
|
|
341
|
+
let cfg: Record<string, any> = {};
|
|
342
|
+
try { cfg = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } catch {}
|
|
343
|
+
if (!cfg?.aegiscloud?.api_key) return false;
|
|
344
|
+
|
|
345
|
+
const lv = cfg?.aegiscloud?.lastVerified;
|
|
346
|
+
if (lv && Date.now() - new Date(lv).getTime() < ACCOUNT_VERIFY_CACHE_MS) return true;
|
|
347
|
+
|
|
348
|
+
const { valid } = await verifyAccount();
|
|
349
|
+
return valid;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ── Logout helper ─────────────────────────────────────────────────────────────
|
|
353
|
+
export function runLogout(): void {
|
|
354
|
+
let cfg: Record<string, any> = {};
|
|
355
|
+
try { cfg = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } catch {}
|
|
356
|
+
|
|
357
|
+
let hadOAuthToken = false;
|
|
358
|
+
try {
|
|
359
|
+
const lines = fs.readFileSync(ENV_FILE, 'utf8').split('\n');
|
|
360
|
+
hadOAuthToken = lines.some(l => l.startsWith('CLAUDE_CODE_OAUTH_TOKEN='));
|
|
361
|
+
const filtered = lines.filter(l => !l.startsWith('CLAUDE_CODE_OAUTH_TOKEN='));
|
|
362
|
+
fs.writeFileSync(ENV_FILE, filtered.filter(l => l.trim()).join('\n') + (filtered.some(l => l.trim()) ? '\n' : ''));
|
|
363
|
+
} catch {}
|
|
364
|
+
|
|
365
|
+
const hadKey = !!(cfg.aegiscloud?.api_key) || hadOAuthToken;
|
|
366
|
+
|
|
367
|
+
if (cfg.aegiscloud) {
|
|
368
|
+
delete cfg.aegiscloud.api_key;
|
|
369
|
+
cfg.aegiscloud.syncConversations = false;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2));
|
|
374
|
+
process.stdout.write(
|
|
375
|
+
hadKey
|
|
376
|
+
? `\n${C.green}✓ Logged out.${C.reset}\n\n`
|
|
377
|
+
: `\n${C.muted}(no active session)${C.reset}\n\n`,
|
|
378
|
+
);
|
|
379
|
+
} catch (e) {
|
|
380
|
+
process.stderr.write(`Failed to update config: ${(e as Error).message}\n`);
|
|
381
|
+
process.exit(1);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 配置 - yargs 选项定义
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Options } from 'yargs';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
*
|
|
13
|
+
*
|
|
14
|
+
*
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
function readVersionSync(): string {
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = path.dirname(__filename);
|
|
20
|
+
|
|
21
|
+
const possiblePaths = [
|
|
22
|
+
path.resolve(__dirname, '../package.json'), // 打包
|
|
23
|
+
path.resolve(__dirname, '../../package.json'), // 开发环
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
for (const pkgPath of possiblePaths) {
|
|
27
|
+
try {
|
|
28
|
+
const content = fs.readFileSync(pkgPath, 'utf-8');
|
|
29
|
+
const pkg = JSON.parse(content) as { name?: string; version?: string };
|
|
30
|
+
if (pkg.version && (pkg.name === 'aegis' || pkg.name === 'aegis-cli' || pkg.name === 'aegiscode')) {
|
|
31
|
+
return pkg.version;
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
// 继续尝试下一个路
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return '0.1.0';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const version = readVersionSync();
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* CLI 基础配置
|
|
45
|
+
*/
|
|
46
|
+
export const cliConfig = {
|
|
47
|
+
scriptName: 'aegis',
|
|
48
|
+
usage: '$0 [message] [options]',
|
|
49
|
+
version,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
*
|
|
54
|
+
*
|
|
55
|
+
*
|
|
56
|
+
* - Debug Options: 调试相关
|
|
57
|
+
* - AI Options: 模型和 AI 相关
|
|
58
|
+
* - Security Options: 权限和安全相关
|
|
59
|
+
* - Session Options: 会话管理相关
|
|
60
|
+
* - Output Options: 输出格式相关
|
|
61
|
+
*/
|
|
62
|
+
export const globalOptions = {
|
|
63
|
+
// ========== Debug Options ==========
|
|
64
|
+
debug: {
|
|
65
|
+
alias: 'd',
|
|
66
|
+
type: 'boolean',
|
|
67
|
+
describe: 'Enable debug mode',
|
|
68
|
+
default: false,
|
|
69
|
+
group: 'Debug Options:',
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
// ========== AI Options ==========
|
|
73
|
+
'api-key': {
|
|
74
|
+
type: 'string',
|
|
75
|
+
describe: 'API key for the LLM service',
|
|
76
|
+
group: 'AI Options:',
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
'base-url': {
|
|
80
|
+
type: 'string',
|
|
81
|
+
describe: 'Base URL for the API (for OpenAI-compatible services)',
|
|
82
|
+
group: 'AI Options:',
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
model: {
|
|
86
|
+
alias: 'm',
|
|
87
|
+
type: 'string',
|
|
88
|
+
describe: 'Model to use for the current session',
|
|
89
|
+
group: 'AI Options:',
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
'max-turns': {
|
|
93
|
+
type: 'number',
|
|
94
|
+
describe: 'Maximum conversation turns (-1 = unlimited, default: -1)',
|
|
95
|
+
group: 'AI Options:',
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
router: {
|
|
99
|
+
type: 'boolean',
|
|
100
|
+
describe: 'Start with the auto-router on — picks a model per message based on task complexity',
|
|
101
|
+
default: false,
|
|
102
|
+
group: 'AI Options:',
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
// ========== Security Options ==========
|
|
106
|
+
'permission-mode': {
|
|
107
|
+
type: 'string',
|
|
108
|
+
choices: ['default', 'autoEdit', 'yolo'] as const,
|
|
109
|
+
describe: 'Permission mode for tool execution',
|
|
110
|
+
group: 'Security Options:',
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
yolo: {
|
|
114
|
+
type: 'boolean',
|
|
115
|
+
describe: 'Auto-approve all tool executions (alias for --permission-mode=yolo)',
|
|
116
|
+
default: false,
|
|
117
|
+
group: 'Security Options:',
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
'allowed-tools': {
|
|
121
|
+
type: 'array',
|
|
122
|
+
string: true,
|
|
123
|
+
describe: 'List of tool names to allow',
|
|
124
|
+
group: 'Security Options:',
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
'disallowed-tools': {
|
|
128
|
+
type: 'array',
|
|
129
|
+
string: true,
|
|
130
|
+
describe: 'List of tool names to disallow',
|
|
131
|
+
group: 'Security Options:',
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
// ========== Session Options ==========
|
|
135
|
+
continue: {
|
|
136
|
+
alias: 'c',
|
|
137
|
+
type: 'boolean',
|
|
138
|
+
describe: 'Continue the most recent conversation',
|
|
139
|
+
default: false,
|
|
140
|
+
group: 'Session Options:',
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
resume: {
|
|
144
|
+
alias: 'r',
|
|
145
|
+
type: 'string',
|
|
146
|
+
describe: 'Resume a specific conversation by ID',
|
|
147
|
+
group: 'Session Options:',
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
// ========== Output Options ==========
|
|
151
|
+
print: {
|
|
152
|
+
alias: 'p',
|
|
153
|
+
type: 'boolean',
|
|
154
|
+
describe: 'Print response and exit (non-interactive mode)',
|
|
155
|
+
default: false,
|
|
156
|
+
group: 'Output Options:',
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
'output-format': {
|
|
160
|
+
type: 'string',
|
|
161
|
+
choices: ['text', 'json'] as const,
|
|
162
|
+
describe: 'Output format (only with --print)',
|
|
163
|
+
default: 'text',
|
|
164
|
+
group: 'Output Options:',
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
// ========== UI Options ==========
|
|
168
|
+
theme: {
|
|
169
|
+
alias: 't',
|
|
170
|
+
type: 'string',
|
|
171
|
+
choices: ['default', 'light', 'dark', 'ocean', 'forest', 'sunset'] as const,
|
|
172
|
+
describe: 'Color theme for the UI (overrides saved preference)',
|
|
173
|
+
group: 'UI Options:',
|
|
174
|
+
},
|
|
175
|
+
'plain': {
|
|
176
|
+
type: 'boolean',
|
|
177
|
+
describe: 'Plain text mode (disable Ink rendering, for non-TTY terminals)',
|
|
178
|
+
default: false,
|
|
179
|
+
group: 'UI Options:',
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
// ========== Config Options ==========
|
|
183
|
+
init: {
|
|
184
|
+
type: 'boolean',
|
|
185
|
+
describe: 'Create default configuration file',
|
|
186
|
+
default: false,
|
|
187
|
+
group: 'Config Options:',
|
|
188
|
+
},
|
|
189
|
+
} satisfies Record<string, Options>;
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 模块导出
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { cliConfig, globalOptions } from './config.js';
|
|
6
|
+
export {
|
|
7
|
+
validatePermissions,
|
|
8
|
+
loadConfiguration,
|
|
9
|
+
validateOutput,
|
|
10
|
+
middlewareChain,
|
|
11
|
+
} from './middleware.js';
|
|
12
|
+
export type {
|
|
13
|
+
CliArguments,
|
|
14
|
+
MiddlewareFunction,
|
|
15
|
+
PermissionMode,
|
|
16
|
+
AppProps,
|
|
17
|
+
} from './types.js';
|