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.
Files changed (206) hide show
  1. package/README.md +23 -15
  2. package/dist/main.js +549 -552
  3. package/package.json +4 -2
  4. package/src/agent/Agent.ts +903 -0
  5. package/src/agent/SimpleAgent.ts +48 -0
  6. package/src/agent/index.ts +54 -0
  7. package/src/agent/orchestrator/AppBuilder.ts +443 -0
  8. package/src/agent/orchestrator/CouncilAgent.ts +310 -0
  9. package/src/agent/orchestrator/DiscussionRoom.ts +462 -0
  10. package/src/agent/orchestrator/OrchestratorAgent.ts +637 -0
  11. package/src/agent/orchestrator/index.ts +38 -0
  12. package/src/agent/orchestrator/utils.ts +397 -0
  13. package/src/agent/pricing.ts +115 -0
  14. package/src/agent/router.ts +74 -0
  15. package/src/agent/routerStats.ts +121 -0
  16. package/src/agent/types.ts +318 -0
  17. package/src/auth/login.ts +383 -0
  18. package/src/cli/config.ts +189 -0
  19. package/src/cli/index.ts +17 -0
  20. package/src/cli/middleware.ts +119 -0
  21. package/src/cli/types.ts +75 -0
  22. package/src/config/ConfigManager.ts +587 -0
  23. package/src/config/index.ts +7 -0
  24. package/src/config/types.ts +584 -0
  25. package/src/context/CompactionService.ts +300 -0
  26. package/src/context/ContextManager.ts +450 -0
  27. package/src/context/FileAnalyzer.ts +267 -0
  28. package/src/context/TokenCounter.ts +265 -0
  29. package/src/context/index.ts +27 -0
  30. package/src/context/storage/CacheStore.ts +176 -0
  31. package/src/context/storage/JSONLStore.ts +201 -0
  32. package/src/context/storage/MemoryStore.ts +205 -0
  33. package/src/context/storage/PersistentStore.ts +327 -0
  34. package/src/context/storage/index.ts +9 -0
  35. package/src/context/storage/pathUtils.ts +114 -0
  36. package/src/context/test.ts +309 -0
  37. package/src/context/types.ts +268 -0
  38. package/src/hooks/HookExecutor.ts +434 -0
  39. package/src/hooks/HookManager.ts +596 -0
  40. package/src/hooks/HookService.ts +269 -0
  41. package/src/hooks/Matcher.ts +157 -0
  42. package/src/hooks/index.ts +63 -0
  43. package/src/hooks/types.ts +424 -0
  44. package/src/main.tsx +596 -0
  45. package/src/mcp/HealthMonitor.ts +150 -0
  46. package/src/mcp/McpClient.ts +491 -0
  47. package/src/mcp/McpRegistry.ts +321 -0
  48. package/src/mcp/createMcpTool.ts +251 -0
  49. package/src/mcp/index.ts +15 -0
  50. package/src/mcp/server.ts +334 -0
  51. package/src/mcp/test-server.ts +88 -0
  52. package/src/mcp/test.ts +372 -0
  53. package/src/mcp/types.ts +247 -0
  54. package/src/memory/AgentMemoryBus.ts +432 -0
  55. package/src/memory/CloudSync.ts +99 -0
  56. package/src/memory/DriveSync.ts +106 -0
  57. package/src/memory/SharedMemory.ts +951 -0
  58. package/src/memory/index.ts +14 -0
  59. package/src/memory/machineFingerprint.ts +40 -0
  60. package/src/orchestrator/SubAgentMetadata.ts +136 -0
  61. package/src/prompts/builder.ts +213 -0
  62. package/src/prompts/default.ts +144 -0
  63. package/src/prompts/index.ts +16 -0
  64. package/src/prompts/plan.ts +64 -0
  65. package/src/prompts/test.ts +78 -0
  66. package/src/services/AnthropicChatService.ts +341 -0
  67. package/src/services/ChatService.ts +347 -0
  68. package/src/services/ClaudeCliChatService.ts +256 -0
  69. package/src/services/CloudSync.ts +168 -0
  70. package/src/services/CostLedger.ts +211 -0
  71. package/src/services/Heartbeat.ts +135 -0
  72. package/src/services/LearningCollector.ts +291 -0
  73. package/src/services/OllamaInstaller.ts +342 -0
  74. package/src/services/VersionChecker.ts +445 -0
  75. package/src/services/index.ts +57 -0
  76. package/src/services/streaming/OpenAIEventAdapter.ts +126 -0
  77. package/src/services/streaming/RenderingProfile.ts +90 -0
  78. package/src/services/streaming/StreamEventParser.ts +181 -0
  79. package/src/services/streaming/ThrottledRenderer.ts +139 -0
  80. package/src/services/streaming/TranscriptBuffer.ts +574 -0
  81. package/src/services/streaming/eventStatusMap.ts +52 -0
  82. package/src/services/streaming/index.ts +46 -0
  83. package/src/services/streaming/renderFormatting.ts +79 -0
  84. package/src/services/streaming/types.ts +234 -0
  85. package/src/skills/SkillLoader.ts +126 -0
  86. package/src/skills/SkillRegistry.ts +366 -0
  87. package/src/skills/index.ts +48 -0
  88. package/src/skills/types.ts +146 -0
  89. package/src/slash-commands/billing.ts +70 -0
  90. package/src/slash-commands/build.ts +413 -0
  91. package/src/slash-commands/builtinCommands.ts +2733 -0
  92. package/src/slash-commands/clone.ts +242 -0
  93. package/src/slash-commands/council.ts +125 -0
  94. package/src/slash-commands/custom/CustomCommandExecutor.ts +143 -0
  95. package/src/slash-commands/custom/CustomCommandLoader.ts +251 -0
  96. package/src/slash-commands/custom/CustomCommandRegistry.ts +144 -0
  97. package/src/slash-commands/custom/index.ts +7 -0
  98. package/src/slash-commands/debate.ts +254 -0
  99. package/src/slash-commands/gmail.ts +105 -0
  100. package/src/slash-commands/index.ts +388 -0
  101. package/src/slash-commands/mcpCommand.ts +205 -0
  102. package/src/slash-commands/types.ts +201 -0
  103. package/src/store/index.ts +76 -0
  104. package/src/store/selectors.ts +246 -0
  105. package/src/store/slices/appSlice.ts +205 -0
  106. package/src/store/slices/commandSlice.ts +115 -0
  107. package/src/store/slices/configSlice.ts +46 -0
  108. package/src/store/slices/focusSlice.ts +64 -0
  109. package/src/store/slices/index.ts +9 -0
  110. package/src/store/slices/sessionSlice.ts +424 -0
  111. package/src/store/streaming-buffer.ts +425 -0
  112. package/src/store/test.ts +296 -0
  113. package/src/store/types.ts +274 -0
  114. package/src/store/vanilla.ts +186 -0
  115. package/src/tools/builtin/bash.ts +236 -0
  116. package/src/tools/builtin/council.ts +105 -0
  117. package/src/tools/builtin/edit.ts +213 -0
  118. package/src/tools/builtin/glob.ts +136 -0
  119. package/src/tools/builtin/grep.ts +263 -0
  120. package/src/tools/builtin/index.ts +61 -0
  121. package/src/tools/builtin/memory.ts +66 -0
  122. package/src/tools/builtin/read.ts +168 -0
  123. package/src/tools/builtin/skill.ts +97 -0
  124. package/src/tools/builtin/snapshot.ts +40 -0
  125. package/src/tools/builtin/task.ts +106 -0
  126. package/src/tools/builtin/write.ts +134 -0
  127. package/src/tools/createTool.ts +221 -0
  128. package/src/tools/execution/ExecutionPipeline.ts +263 -0
  129. package/src/tools/execution/index.ts +40 -0
  130. package/src/tools/execution/stages/CacheStage.ts +131 -0
  131. package/src/tools/execution/stages/ConfirmationStage.ts +203 -0
  132. package/src/tools/execution/stages/DiscoveryStage.ts +46 -0
  133. package/src/tools/execution/stages/ExecutionStage.ts +48 -0
  134. package/src/tools/execution/stages/FormattingStage.ts +44 -0
  135. package/src/tools/execution/stages/HookStage.ts +72 -0
  136. package/src/tools/execution/stages/PermissionStage.ts +287 -0
  137. package/src/tools/execution/stages/PostHookStage.ts +71 -0
  138. package/src/tools/execution/stages/index.ts +12 -0
  139. package/src/tools/execution/test.ts +266 -0
  140. package/src/tools/execution/types.ts +273 -0
  141. package/src/tools/index.ts +81 -0
  142. package/src/tools/registry.ts +304 -0
  143. package/src/tools/schemas.ts +109 -0
  144. package/src/tools/test.ts +220 -0
  145. package/src/tools/types.ts +175 -0
  146. package/src/tools/validation/PermissionChecker.ts +242 -0
  147. package/src/tools/validation/SensitiveFileDetector.ts +210 -0
  148. package/src/tools/validation/index.ts +11 -0
  149. package/src/ui/App.tsx +166 -0
  150. package/src/ui/components/AegisInterface.tsx +484 -0
  151. package/src/ui/components/common/ChatSearch.tsx +150 -0
  152. package/src/ui/components/common/ErrorBoundary.tsx +82 -0
  153. package/src/ui/components/common/ExitMessage.tsx +120 -0
  154. package/src/ui/components/common/LoadingIndicator.tsx +49 -0
  155. package/src/ui/components/common/index.ts +6 -0
  156. package/src/ui/components/dialog/ConfirmationPrompt.tsx +208 -0
  157. package/src/ui/components/dialog/InteractiveSelector.tsx +149 -0
  158. package/src/ui/components/dialog/SetupWizard.tsx +297 -0
  159. package/src/ui/components/dialog/UpdatePrompt.tsx +155 -0
  160. package/src/ui/components/dialog/index.ts +8 -0
  161. package/src/ui/components/index.ts +28 -0
  162. package/src/ui/components/input/CommandSuggestions.tsx +139 -0
  163. package/src/ui/components/input/CustomTextInput.tsx +220 -0
  164. package/src/ui/components/input/InputArea.tsx +361 -0
  165. package/src/ui/components/input/PromptSuggestions.tsx +66 -0
  166. package/src/ui/components/input/index.ts +6 -0
  167. package/src/ui/components/layout/ChatStatusBar.tsx +90 -0
  168. package/src/ui/components/layout/ContextBar.tsx +79 -0
  169. package/src/ui/components/layout/MessageArea.tsx +96 -0
  170. package/src/ui/components/layout/MessageList.tsx +647 -0
  171. package/src/ui/components/layout/MessageSeparator.tsx +26 -0
  172. package/src/ui/components/layout/WelcomeMessage.tsx +93 -0
  173. package/src/ui/components/layout/index.ts +7 -0
  174. package/src/ui/components/markdown/CodeHighlighter.tsx +292 -0
  175. package/src/ui/components/markdown/MessageRenderer.tsx +1211 -0
  176. package/src/ui/components/markdown/index.ts +8 -0
  177. package/src/ui/components/markdown/parser.ts +336 -0
  178. package/src/ui/components/markdown/types.ts +66 -0
  179. package/src/ui/focus/FocusManager.ts +137 -0
  180. package/src/ui/focus/index.ts +13 -0
  181. package/src/ui/focus/types.ts +54 -0
  182. package/src/ui/focus/useFocus.ts +75 -0
  183. package/src/ui/hooks/index.ts +11 -0
  184. package/src/ui/hooks/useAgent.ts +284 -0
  185. package/src/ui/hooks/useCommandHistory.ts +87 -0
  186. package/src/ui/hooks/useCommandProcessor.ts +443 -0
  187. package/src/ui/hooks/useConfirmation.ts +99 -0
  188. package/src/ui/hooks/useCtrlCHandler.ts +100 -0
  189. package/src/ui/hooks/useInputBuffer.ts +122 -0
  190. package/src/ui/hooks/useTerminalSize.ts +68 -0
  191. package/src/ui/hooks/useTerminalWidth.ts +5 -0
  192. package/src/ui/hooks/useWindowedList.ts +118 -0
  193. package/src/ui/render-debugger.ts +621 -0
  194. package/src/ui/test.ts +189 -0
  195. package/src/ui/themes/ThemeManager.ts +332 -0
  196. package/src/ui/themes/aegisTheme.ts +87 -0
  197. package/src/ui/themes/darkTheme.ts +87 -0
  198. package/src/ui/themes/defaultTheme.ts +85 -0
  199. package/src/ui/themes/index.ts +10 -0
  200. package/src/ui/themes/lightTheme.ts +89 -0
  201. package/src/ui/themes/popularThemes.ts +187 -0
  202. package/src/ui/themes/types.ts +130 -0
  203. package/src/utils/clipboard.ts +48 -0
  204. package/src/utils/debug.ts +43 -0
  205. package/src/utils/environment.ts +68 -0
  206. package/src/utils/index.ts +10 -0
package/src/ui/test.ts ADDED
@@ -0,0 +1,189 @@
1
+ /**
2
+ * UI Component Tests (D)
3
+ *
4
+ * Tests for MessageList, MessageRenderer, ErrorBoundary, ChatSearch,
5
+ * and other UI components.
6
+ *
7
+ * Run with: tsx src/ui/test.ts
8
+ *
9
+ * Rendering debugger:
10
+ * tsx src/ui/test.ts --debug-rendering Enable render debugger during tests
11
+ * tsx src/ui/test.ts --render-report Run tests then print render report
12
+ */
13
+
14
+ import React from 'react';
15
+ import { render } from 'ink';
16
+ import { ErrorBoundary } from './components/common/ErrorBoundary.js';
17
+ import { MessageList } from './components/layout/MessageList.js';
18
+ import { WelcomeMessage } from './components/layout/WelcomeMessage.js';
19
+ import { MessageSeparator } from './components/layout/MessageSeparator.js';
20
+ import { ChatStatusBar } from './components/layout/ChatStatusBar.js';
21
+ import { vanillaStore, getState } from '../store/index.js';
22
+ import { startRenderDebugger, stopRenderDebugger, getRenderReport } from './render-debugger.js';
23
+
24
+ let testsPassed = 0;
25
+ let testsFailed = 0;
26
+
27
+ function pass(msg: string) {
28
+ testsPassed++;
29
+ console.log(` ✓ ${msg}`);
30
+ }
31
+
32
+ function fail(msg: string, error?: any) {
33
+ testsFailed++;
34
+ console.log(` ✗ ${msg}${error ? `: ${error}` : ''}`);
35
+ }
36
+
37
+ async function runTests() {
38
+ console.log('\nUI Component Tests\n');
39
+
40
+ // Auto-start render debugger if --debug-rendering flag is set
41
+ const enableDebug = process.argv.includes('--debug-rendering');
42
+ if (enableDebug) {
43
+ startRenderDebugger({ reportInterval: 5000, verbose: false });
44
+ }
45
+
46
+ // ===== ErrorBoundary =====
47
+ console.log('ErrorBoundary:');
48
+
49
+ // 1. ErrorBoundary renders children normally
50
+ const GoodComponent = () => React.createElement('test-good', null, 'hello');
51
+ try {
52
+ const { unmount } = render(
53
+ React.createElement(ErrorBoundary, null,
54
+ React.createElement(GoodComponent),
55
+ ),
56
+ );
57
+ pass('renders children without error');
58
+ unmount();
59
+ } catch (e) {
60
+ fail('renders children without error', e);
61
+ }
62
+
63
+ // 2. ErrorBoundary catches errors
64
+ const BadComponent = () => { throw new Error('test error'); };
65
+ try {
66
+ const { unmount } = render(
67
+ React.createElement(ErrorBoundary,
68
+ { fallback: React.createElement('div', null, 'fallback'), children: React.createElement(BadComponent) },
69
+ ),
70
+ );
71
+ pass('catches rendering errors');
72
+ unmount();
73
+ } catch (e) {
74
+ // If we get here, ErrorBoundary caught it - that's fine for ink
75
+ pass('catches rendering errors (error propagated)');
76
+ }
77
+
78
+ // ===== MessageList =====
79
+ console.log('\nMessageList:');
80
+
81
+ // 3. MessageList renders welcome when empty
82
+ try {
83
+ const { unmount } = render(
84
+ React.createElement(MessageList, { terminalWidth: 80, terminalHeight: 24 }),
85
+ );
86
+ pass('renders without messages');
87
+ unmount();
88
+ } catch (e) {
89
+ fail('renders without messages', e);
90
+ }
91
+
92
+ // 4. MessageList renders messages
93
+ try {
94
+ // Add a test message directly to store
95
+ getState().session.messages.push({
96
+ id: 'test-1',
97
+ role: 'user',
98
+ content: 'Hello world',
99
+ timestamp: Date.now(),
100
+ });
101
+
102
+ const { unmount, rerender } = render(
103
+ React.createElement(MessageList, { terminalWidth: 80, terminalHeight: 24 }),
104
+ );
105
+
106
+ pass('renders with messages');
107
+ unmount();
108
+
109
+ // Clean up
110
+ getState().session.messages = [];
111
+ } catch (e) {
112
+ fail('renders with messages', e);
113
+ }
114
+
115
+ // ===== ChatStatusBar =====
116
+ console.log('\nChatStatusBar:');
117
+
118
+ // 5. ChatStatusBar renders
119
+ try {
120
+ const { unmount } = render(
121
+ React.createElement(ChatStatusBar, null),
122
+ );
123
+ pass('renders ChatStatusBar');
124
+ unmount();
125
+ } catch (e) {
126
+ fail('renders ChatStatusBar', e);
127
+ }
128
+
129
+ // ===== Store Integration =====
130
+ console.log('\nStore Integration:');
131
+
132
+ // 6. Messages have expected shape
133
+ const msg = { id: 'test-2', role: 'assistant' as const, content: 'test', timestamp: Date.now() };
134
+ getState().session.messages.push(msg);
135
+ const messages = getState().session.messages;
136
+ if (messages.length > 0 && messages[0].id && messages[0].role && messages[0].content) {
137
+ pass('messages have correct shape');
138
+ } else {
139
+ fail('messages missing required fields');
140
+ }
141
+
142
+ // 7. Message streaming state transitions
143
+ const streamId = 'test-stream-1';
144
+ getState().session.messages.push({
145
+ id: streamId,
146
+ role: 'assistant',
147
+ content: '',
148
+ timestamp: Date.now(),
149
+ isStreaming: true,
150
+ });
151
+
152
+ // Simulate streaming
153
+ const msgIndex = getState().session.messages.findIndex(m => m.id === streamId);
154
+ if (msgIndex >= 0) {
155
+ getState().session.messages[msgIndex].content = 'partial';
156
+ pass('streaming content update works');
157
+ } else {
158
+ fail('streaming message not found');
159
+ }
160
+
161
+ // Finish streaming
162
+ getState().session.messages[msgIndex].isStreaming = false;
163
+ pass('streaming completion works');
164
+
165
+ // Clean up
166
+ getState().session.messages = [];
167
+
168
+ // ===== Summary =====
169
+ console.log(`\n${'='.repeat(40)}`);
170
+ console.log(`Tests: ${testsPassed} passed, ${testsFailed} failed`);
171
+
172
+ // Print render report if debug was enabled or --render-report flag set
173
+ if (enableDebug || process.argv.includes('--render-report')) {
174
+ console.log(getRenderReport());
175
+ }
176
+
177
+ if (enableDebug) {
178
+ stopRenderDebugger();
179
+ }
180
+
181
+ if (testsFailed > 0) {
182
+ process.exit(1);
183
+ }
184
+ }
185
+
186
+ runTests().catch((error) => {
187
+ console.error('Test suite error:', error);
188
+ process.exit(1);
189
+ });
@@ -0,0 +1,332 @@
1
+ /**
2
+ *
3
+ *
4
+ *
5
+ *
6
+ */
7
+
8
+ import { execSync } from 'node:child_process';
9
+ import type { Theme, ThemePreset, RoleStyle, ColorMode } from './types.js';
10
+ import { defaultTheme } from './defaultTheme.js';
11
+ import { darkTheme } from './darkTheme.js';
12
+ import { lightTheme } from './lightTheme.js';
13
+ import { aegisTheme } from './aegisTheme.js';
14
+ import { configManager } from '../../config/ConfigManager.js';
15
+
16
+ /**
17
+ *
18
+ *
19
+ *
20
+ * 1. macOS 系统级暗色模式
21
+ * 2. COLORFGBG 环境变量(一些终端会设置)
22
+ * 3. 默认返回 unknown
23
+ */
24
+ function detectColorMode(): ColorMode {
25
+ // 1. macOS 系统级检
26
+ if (process.platform === 'darwin') {
27
+ try {
28
+ const result = execSync('defaults read -g AppleInterfaceStyle 2>/dev/null', {
29
+ encoding: 'utf-8',
30
+ timeout: 1000,
31
+ }).trim();
32
+ if (result === 'Dark') {
33
+ return 'dark';
34
+ }
35
+ } catch {
36
+ // 如果命令失败或返回空,说明是亮色模
37
+ return 'light';
38
+ }
39
+ }
40
+
41
+ // 2. 检查 COLORFGBG 环境变
42
+ // 格式: "foreground;background" 如 "15;0" (白字黑底) 或 "0;15" (黑字白
43
+ const colorFgBg = process.env.COLORFGBG;
44
+ if (colorFgBg) {
45
+ const parts = colorFgBg.split(';');
46
+ if (parts.length >= 2) {
47
+ const bg = parseInt(parts[parts.length - 1], 10);
48
+ // 背景色 0-7 通常是暗色,8-15 通常是亮
49
+ // 0=黑, 7=白(暗), 8=亮黑(灰), 15=亮
50
+ if (!isNaN(bg)) {
51
+ if (bg === 0 || bg <= 6) {
52
+ return 'dark';
53
+ } else if (bg === 7 || bg >= 8) {
54
+ return 'light';
55
+ }
56
+ }
57
+ }
58
+ }
59
+
60
+ // 3. 检查常见终端的默认模
61
+ const termProgram = process.env.TERM_PROGRAM?.toLowerCase();
62
+ if (termProgram) {
63
+ // VS Code 集成终端通常跟随编辑器主
64
+ if (termProgram === 'vscode') {
65
+ // VS Code 的 VSCODE_THEME_KIND 变
66
+ const themeKind = process.env.VSCODE_THEME_KIND;
67
+ if (themeKind === 'vscode-dark' || themeKind === 'vscode-high-contrast') {
68
+ return 'dark';
69
+ } else if (themeKind === 'vscode-light' || themeKind === 'vscode-high-contrast-light') {
70
+ return 'light';
71
+ }
72
+ }
73
+ }
74
+
75
+ return 'unknown';
76
+ }
77
+
78
+ /**
79
+ *
80
+ */
81
+ const presetThemes: ThemePreset[] = [
82
+ {
83
+ id: 'default',
84
+ name: 'Default',
85
+ description: 'Balanced theme (works on most terminals)',
86
+ theme: defaultTheme,
87
+ },
88
+ {
89
+ id: 'aegis',
90
+ name: 'ÆGIS',
91
+ description: 'ÆGIS teal — the signature brand theme',
92
+ theme: aegisTheme,
93
+ },
94
+ {
95
+ id: 'light',
96
+ name: 'Light',
97
+ description: 'Optimized for light/white terminal backgrounds',
98
+ theme: lightTheme,
99
+ },
100
+ {
101
+ id: 'dark',
102
+ name: 'Dark',
103
+ description: 'Dark theme for dark terminal backgrounds',
104
+ theme: darkTheme,
105
+ },
106
+ {
107
+ id: 'ocean',
108
+ name: 'Ocean',
109
+ description: 'Deep blue ocean theme',
110
+ theme: {
111
+ ...darkTheme,
112
+ name: 'ocean',
113
+ colors: {
114
+ ...darkTheme.colors,
115
+ primary: '#0ea5e9', // sky-500
116
+ secondary: '#06b6d4', // cyan-500
117
+ accent: '#14b8a6', // teal-500
118
+ background: {
119
+ primary: '#0c4a6e', // sky-900
120
+ secondary: '#075985', // sky-800
121
+ dark: '#082f49', // sky-950
122
+ },
123
+ },
124
+ },
125
+ },
126
+ {
127
+ id: 'forest',
128
+ name: 'Forest',
129
+ description: 'Natural green forest theme',
130
+ theme: {
131
+ ...darkTheme,
132
+ name: 'forest',
133
+ colors: {
134
+ ...darkTheme.colors,
135
+ primary: '#22c55e', // green-500
136
+ secondary: '#16a34a', // green-600
137
+ accent: '#84cc16', // lime-500
138
+ background: {
139
+ primary: '#14532d', // green-900
140
+ secondary: '#166534', // green-800
141
+ dark: '#052e16', // green-950
142
+ },
143
+ },
144
+ },
145
+ },
146
+ {
147
+ id: 'sunset',
148
+ name: 'Sunset',
149
+ description: 'Warm sunset colors',
150
+ theme: {
151
+ ...darkTheme,
152
+ name: 'sunset',
153
+ colors: {
154
+ ...darkTheme.colors,
155
+ primary: '#f97316', // orange-500
156
+ secondary: '#ea580c', // orange-600
157
+ accent: '#eab308', // yellow-500
158
+ background: {
159
+ primary: '#7c2d12', // orange-900
160
+ secondary: '#9a3412', // orange-800
161
+ dark: '#431407', // orange-950
162
+ },
163
+ },
164
+ },
165
+ },
166
+ ];
167
+
168
+ /**
169
+ *
170
+ */
171
+ export class ThemeManager {
172
+ private currentTheme: Theme = defaultTheme;
173
+ private themes: Map<string, Theme> = new Map();
174
+ private initialized: boolean = false;
175
+
176
+ constructor() {
177
+ // 注册预设主
178
+ for (const preset of presetThemes) {
179
+ this.themes.set(preset.id, preset.theme);
180
+ }
181
+ }
182
+
183
+ /**
184
+ *
185
+ *
186
+ *
187
+ * 1. 用户保存的主题(用户配置文件中)
188
+ * 2. 自动检测终端颜色模式,选择合适的主题
189
+ * 3. 使用默认主题
190
+ */
191
+ initializeFromConfig(): void {
192
+ if (this.initialized) return;
193
+
194
+ try {
195
+ const savedTheme = configManager.getTheme();
196
+
197
+ // 如果用户之前保存过主题,使用保存的主
198
+ if (savedTheme && this.themes.has(savedTheme)) {
199
+ this.currentTheme = this.themes.get(savedTheme)!;
200
+ this.initialized = true;
201
+ return;
202
+ }
203
+
204
+ // 没有用户保存的主题,自动检测终端颜色模
205
+ const colorMode = detectColorMode();
206
+
207
+ if (colorMode === 'dark') {
208
+ this.currentTheme = this.themes.get('dark')!;
209
+ } else if (colorMode === 'light') {
210
+ this.currentTheme = this.themes.get('light')!;
211
+ }
212
+ // colorMode === 'unknown' 时保持默认主
213
+
214
+ this.initialized = true;
215
+ } catch {
216
+ // 配置读取失败时使用默认主
217
+ this.initialized = true;
218
+ }
219
+ }
220
+
221
+ /**
222
+ *
223
+ */
224
+ getDetectedColorMode(): ColorMode {
225
+ return detectColorMode();
226
+ }
227
+
228
+ /**
229
+ *
230
+ */
231
+ setTheme(themeName: string, persist: boolean = true): void {
232
+ const theme = this.themes.get(themeName);
233
+ if (theme) {
234
+ this.currentTheme = theme;
235
+
236
+ // 同步保存到配置文件,确保立即生
237
+ if (persist) {
238
+ try {
239
+ configManager.saveTheme(themeName);
240
+ } catch {
241
+ // 保存失败时静默忽
242
+ }
243
+ }
244
+ } else {
245
+ throw new Error(`Theme '${themeName}' not found`);
246
+ }
247
+ }
248
+
249
+ /**
250
+ *
251
+ */
252
+ getTheme(): Theme {
253
+ return this.currentTheme;
254
+ }
255
+
256
+ /**
257
+ *
258
+ */
259
+ getCurrentThemeName(): string {
260
+ return this.currentTheme.name;
261
+ }
262
+
263
+ /**
264
+ *
265
+ */
266
+ hasTheme(themeName: string): boolean {
267
+ return this.themes.has(themeName);
268
+ }
269
+
270
+ /**
271
+ *
272
+ */
273
+ getAvailableThemes(): string[] {
274
+ return Array.from(this.themes.keys());
275
+ }
276
+
277
+ /**
278
+ *
279
+ */
280
+ getThemePresets(): ThemePreset[] {
281
+ return presetThemes;
282
+ }
283
+
284
+ /**
285
+ *
286
+ */
287
+ registerTheme(id: string, theme: Theme): void {
288
+ this.themes.set(id, theme);
289
+ }
290
+
291
+ /**
292
+ *
293
+ */
294
+ getRoleStyle(role: 'user' | 'assistant' | 'system' | 'tool'): RoleStyle {
295
+ const colors = this.currentTheme.colors;
296
+
297
+ switch (role) {
298
+ case 'user':
299
+ return {
300
+ color: colors.text.muted,
301
+ prefix: '',
302
+ bold: false,
303
+ };
304
+ case 'assistant':
305
+ return {
306
+ color: colors.primary,
307
+ prefix: '',
308
+ bold: false,
309
+ };
310
+ case 'system':
311
+ return {
312
+ color: colors.warning,
313
+ prefix: '!',
314
+ bold: true,
315
+ };
316
+ case 'tool':
317
+ return {
318
+ color: colors.info,
319
+ prefix: '',
320
+ bold: false,
321
+ };
322
+ default:
323
+ return {
324
+ color: colors.text.primary,
325
+ prefix: '',
326
+ };
327
+ }
328
+ }
329
+ }
330
+
331
+ // 导出单
332
+ export const themeManager = new ThemeManager();
@@ -0,0 +1,87 @@
1
+ import type { Theme, BaseColors, Spacing } from './types.js';
2
+
3
+ const aegisColors: BaseColors = {
4
+ primary: '#00e5c0', // teal
5
+ secondary: '#7c6fd4', // purple
6
+ accent: '#00e5c0', // teal
7
+ success: '#22c55e',
8
+ warning: '#f59e0b',
9
+ error: '#ef4444',
10
+ info: '#00e5c0',
11
+ text: {
12
+ primary: '#e8e6f4',
13
+ secondary: '#8882a4',
14
+ muted: '#44405a',
15
+ light: '#e8e6f4',
16
+ },
17
+ background: {
18
+ primary: '#04040c',
19
+ secondary: '#181828',
20
+ dark: '#0c0c16',
21
+ },
22
+ border: {
23
+ light: 'rgba(255,255,255,0.06)',
24
+ dark: '#1a1a2a',
25
+ },
26
+ syntax: {
27
+ keyword: '#00e5c0',
28
+ string: '#7c6fd4',
29
+ number: '#f59e0b',
30
+ comment: '#44405a',
31
+ function: '#f472b6',
32
+ variable: '#e8e6f4',
33
+ operator: '#89DDFF',
34
+ type: '#FFCB6B',
35
+ tag: '#F07178',
36
+ attr: '#C3E88D',
37
+ default: '#A6ACCD',
38
+ },
39
+ };
40
+
41
+ const spacing: Spacing = {
42
+ xs: 0, sm: 1, md: 2, lg: 3, xl: 4
43
+ };
44
+
45
+ const tokens = {
46
+ elevation: {
47
+ flat: 'rgba(0,0,0,0)',
48
+ raised: 'rgba(0, 229, 192, 0.08)',
49
+ overlay: 'rgba(0, 0, 0, 0.5)',
50
+ sticky: 'rgba(0, 229, 192, 0.15)',
51
+ },
52
+ radius: {
53
+ sm: 0,
54
+ md: 1,
55
+ lg: 2,
56
+ full: 0,
57
+ },
58
+ animation: {
59
+ fast: 80,
60
+ normal: 150,
61
+ slow: 300,
62
+ easing: 'ease-out',
63
+ },
64
+ semantic: {
65
+ interactive: {
66
+ idle: aegisColors.primary,
67
+ hover: '#33ffd9',
68
+ active: '#00bfa0',
69
+ disabled: '#44405a',
70
+ },
71
+ focus: {
72
+ ring: '#00e5c0',
73
+ glow: 'rgba(0, 229, 192, 0.3)',
74
+ },
75
+ code: {
76
+ background: '#08080f',
77
+ border: '#1a1a2a',
78
+ },
79
+ },
80
+ };
81
+
82
+ export const aegisTheme: Theme = {
83
+ name: 'aegis',
84
+ colors: aegisColors,
85
+ spacing,
86
+ tokens,
87
+ };
@@ -0,0 +1,87 @@
1
+ /**
2
+ *
3
+ */
4
+
5
+ import type { Theme } from './types.js';
6
+ import { defaultTheme } from './defaultTheme.js';
7
+
8
+ const darkTokens = {
9
+ elevation: {
10
+ flat: 'rgba(0,0,0,0)',
11
+ raised: 'rgba(96, 165, 250, 0.08)',
12
+ overlay: 'rgba(0, 0, 0, 0.4)',
13
+ sticky: 'rgba(96, 165, 250, 0.12)',
14
+ },
15
+ radius: {
16
+ sm: 0,
17
+ md: 1,
18
+ lg: 2,
19
+ full: 0,
20
+ },
21
+ animation: {
22
+ fast: 80,
23
+ normal: 150,
24
+ slow: 300,
25
+ easing: 'ease-out',
26
+ },
27
+ semantic: {
28
+ interactive: {
29
+ idle: '#60a5fa',
30
+ hover: '#93c5fd',
31
+ active: '#3b82f6',
32
+ disabled: '#9ca3af',
33
+ },
34
+ focus: {
35
+ ring: '#60a5fa',
36
+ glow: 'rgba(96, 165, 250, 0.3)',
37
+ },
38
+ code: {
39
+ background: '#1f2937',
40
+ border: '#374151',
41
+ },
42
+ },
43
+ };
44
+
45
+ export const darkTheme: Theme = {
46
+ ...defaultTheme,
47
+ name: 'dark',
48
+ tokens: darkTokens,
49
+ colors: {
50
+ ...defaultTheme.colors,
51
+ primary: '#60a5fa', // blue-400
52
+ secondary: '#818cf8', // indigo-400
53
+ accent: '#a78bfa', // violet-400
54
+ success: '#34d399', // emerald-400
55
+ warning: '#fbbf24', // amber-400
56
+ error: '#f87171', // red-400
57
+ info: '#22d3ee', // cyan-400
58
+ text: {
59
+ primary: '#f9fafb', // gray-50
60
+ secondary: '#e5e7eb', // gray-200
61
+ muted: '#9ca3af', // gray-400
62
+ light: '#111827', // gray-900
63
+ },
64
+ background: {
65
+ primary: '#111827', // gray-900
66
+ secondary: '#374151', // gray-700
67
+ dark: '#030712', // gray-950
68
+ },
69
+ border: {
70
+ light: '#374151', // gray-700
71
+ dark: '#1f2937', // gray-800
72
+ },
73
+ syntax: {
74
+ comment: '#9ca3af', // gray-400
75
+ string: '#34d399', // emerald-400
76
+ number: '#f472b6', // pink-400
77
+ keyword: '#a78bfa', // violet-400
78
+ function: '#60a5fa', // blue-400
79
+ variable: '#e5e7eb', // gray-200
80
+ operator: '#fbbf24', // amber-400
81
+ type: '#22d3ee', // cyan-400
82
+ tag: '#fb7185', // rose-400
83
+ attr: '#facc15', // yellow-400
84
+ default: '#f9fafb', // gray-50
85
+ },
86
+ },
87
+ };