nova-terminal-assistant 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of nova-terminal-assistant might be problematic. Click here for more details.

Files changed (192) hide show
  1. package/README.md +358 -0
  2. package/bin/nova +38 -0
  3. package/bin/nova.js +12 -0
  4. package/package.json +67 -0
  5. package/src/cli/commands/SmartCompletion.ts +458 -0
  6. package/src/cli/index.ts +5 -0
  7. package/src/cli/startup/IFlowRepl.ts +212 -0
  8. package/src/cli/startup/InkBasedRepl.ts +1056 -0
  9. package/src/cli/startup/InteractiveRepl.ts +2833 -0
  10. package/src/cli/startup/NovaApp.ts +1861 -0
  11. package/src/cli/startup/index.ts +4 -0
  12. package/src/cli/startup/parseArgs.ts +293 -0
  13. package/src/cli/test-modules.ts +27 -0
  14. package/src/cli/ui/IFlowDropdown.ts +425 -0
  15. package/src/cli/ui/ModernReplUI.ts +276 -0
  16. package/src/cli/ui/SimpleSelector2.ts +215 -0
  17. package/src/cli/ui/components/ConfirmDialog.ts +176 -0
  18. package/src/cli/ui/components/ErrorPanel.ts +364 -0
  19. package/src/cli/ui/components/InkAppRunner.tsx +67 -0
  20. package/src/cli/ui/components/InkComponents.tsx +613 -0
  21. package/src/cli/ui/components/NovaInkApp.tsx +312 -0
  22. package/src/cli/ui/components/ProgressBar.ts +177 -0
  23. package/src/cli/ui/components/ProgressIndicator.ts +298 -0
  24. package/src/cli/ui/components/QuickActions.ts +396 -0
  25. package/src/cli/ui/components/SimpleErrorPanel.ts +231 -0
  26. package/src/cli/ui/components/StatusBar.ts +194 -0
  27. package/src/cli/ui/components/ThinkingBlockRenderer.ts +401 -0
  28. package/src/cli/ui/components/index.ts +27 -0
  29. package/src/cli/ui/ink-prototype.tsx +347 -0
  30. package/src/cli/utils/CliUI.ts +336 -0
  31. package/src/cli/utils/CompletionHelper.ts +388 -0
  32. package/src/cli/utils/EnhancedCompleter.test.ts +226 -0
  33. package/src/cli/utils/EnhancedCompleter.ts +513 -0
  34. package/src/cli/utils/ErrorEnhancer.ts +429 -0
  35. package/src/cli/utils/OutputFormatter.ts +193 -0
  36. package/src/cli/utils/index.ts +9 -0
  37. package/src/core/agents/AgentOrchestrator.ts +515 -0
  38. package/src/core/agents/index.ts +17 -0
  39. package/src/core/audit/AuditLogger.ts +509 -0
  40. package/src/core/audit/index.ts +11 -0
  41. package/src/core/auth/AuthManager.d.ts.map +1 -0
  42. package/src/core/auth/AuthManager.ts +138 -0
  43. package/src/core/auth/index.d.ts.map +1 -0
  44. package/src/core/auth/index.ts +2 -0
  45. package/src/core/config/ConfigManager.d.ts.map +1 -0
  46. package/src/core/config/ConfigManager.test.ts +183 -0
  47. package/src/core/config/ConfigManager.ts +1219 -0
  48. package/src/core/config/index.d.ts.map +1 -0
  49. package/src/core/config/index.ts +1 -0
  50. package/src/core/context/ContextBuilder.d.ts.map +1 -0
  51. package/src/core/context/ContextBuilder.ts +171 -0
  52. package/src/core/context/ContextCompressor.d.ts.map +1 -0
  53. package/src/core/context/ContextCompressor.ts +642 -0
  54. package/src/core/context/LayeredMemoryManager.ts +657 -0
  55. package/src/core/context/MemoryDiscovery.d.ts.map +1 -0
  56. package/src/core/context/MemoryDiscovery.ts +175 -0
  57. package/src/core/context/defaultSystemPrompt.d.ts.map +1 -0
  58. package/src/core/context/defaultSystemPrompt.ts +35 -0
  59. package/src/core/context/index.d.ts.map +1 -0
  60. package/src/core/context/index.ts +22 -0
  61. package/src/core/extensions/SkillGenerator.ts +421 -0
  62. package/src/core/extensions/SkillInstaller.d.ts.map +1 -0
  63. package/src/core/extensions/SkillInstaller.ts +257 -0
  64. package/src/core/extensions/SkillRegistry.d.ts.map +1 -0
  65. package/src/core/extensions/SkillRegistry.ts +361 -0
  66. package/src/core/extensions/SkillValidator.ts +525 -0
  67. package/src/core/extensions/index.ts +15 -0
  68. package/src/core/index.d.ts.map +1 -0
  69. package/src/core/index.ts +42 -0
  70. package/src/core/mcp/McpManager.d.ts.map +1 -0
  71. package/src/core/mcp/McpManager.ts +632 -0
  72. package/src/core/mcp/index.d.ts.map +1 -0
  73. package/src/core/mcp/index.ts +2 -0
  74. package/src/core/model/ModelClient.d.ts.map +1 -0
  75. package/src/core/model/ModelClient.ts +217 -0
  76. package/src/core/model/ModelConnectionTester.ts +363 -0
  77. package/src/core/model/ModelValidator.ts +348 -0
  78. package/src/core/model/index.d.ts.map +1 -0
  79. package/src/core/model/index.ts +6 -0
  80. package/src/core/model/providers/AnthropicProvider.d.ts.map +1 -0
  81. package/src/core/model/providers/AnthropicProvider.ts +279 -0
  82. package/src/core/model/providers/CodingPlanProvider.d.ts.map +1 -0
  83. package/src/core/model/providers/CodingPlanProvider.ts +210 -0
  84. package/src/core/model/providers/OllamaCloudProvider.d.ts.map +1 -0
  85. package/src/core/model/providers/OllamaCloudProvider.ts +405 -0
  86. package/src/core/model/providers/OllamaManager.d.ts.map +1 -0
  87. package/src/core/model/providers/OllamaManager.ts +201 -0
  88. package/src/core/model/providers/OllamaProvider.d.ts.map +1 -0
  89. package/src/core/model/providers/OllamaProvider.ts +73 -0
  90. package/src/core/model/providers/OpenAICompatibleProvider.d.ts.map +1 -0
  91. package/src/core/model/providers/OpenAICompatibleProvider.ts +327 -0
  92. package/src/core/model/providers/OpenAIProvider.d.ts.map +1 -0
  93. package/src/core/model/providers/OpenAIProvider.ts +29 -0
  94. package/src/core/model/providers/index.d.ts.map +1 -0
  95. package/src/core/model/providers/index.ts +12 -0
  96. package/src/core/model/types.d.ts.map +1 -0
  97. package/src/core/model/types.ts +77 -0
  98. package/src/core/security/ApprovalManager.d.ts.map +1 -0
  99. package/src/core/security/ApprovalManager.ts +174 -0
  100. package/src/core/security/FileFilter.d.ts.map +1 -0
  101. package/src/core/security/FileFilter.ts +141 -0
  102. package/src/core/security/HookExecutor.d.ts.map +1 -0
  103. package/src/core/security/HookExecutor.ts +178 -0
  104. package/src/core/security/SandboxExecutor.ts +447 -0
  105. package/src/core/security/index.d.ts.map +1 -0
  106. package/src/core/security/index.ts +8 -0
  107. package/src/core/session/AgentLoop.d.ts.map +1 -0
  108. package/src/core/session/AgentLoop.ts +501 -0
  109. package/src/core/session/SessionManager.d.ts.map +1 -0
  110. package/src/core/session/SessionManager.test.ts +183 -0
  111. package/src/core/session/SessionManager.ts +460 -0
  112. package/src/core/session/index.d.ts.map +1 -0
  113. package/src/core/session/index.ts +3 -0
  114. package/src/core/telemetry/Telemetry.d.ts.map +1 -0
  115. package/src/core/telemetry/Telemetry.ts +90 -0
  116. package/src/core/telemetry/TelemetryService.ts +531 -0
  117. package/src/core/telemetry/index.d.ts.map +1 -0
  118. package/src/core/telemetry/index.ts +12 -0
  119. package/src/core/testing/AutoFixer.ts +385 -0
  120. package/src/core/testing/ErrorAnalyzer.ts +499 -0
  121. package/src/core/testing/TestRunner.ts +265 -0
  122. package/src/core/testing/agent-cli-tests.ts +538 -0
  123. package/src/core/testing/index.ts +11 -0
  124. package/src/core/tools/ToolRegistry.d.ts.map +1 -0
  125. package/src/core/tools/ToolRegistry.test.ts +206 -0
  126. package/src/core/tools/ToolRegistry.ts +260 -0
  127. package/src/core/tools/impl/EditFileTool.d.ts.map +1 -0
  128. package/src/core/tools/impl/EditFileTool.ts +97 -0
  129. package/src/core/tools/impl/ListDirectoryTool.d.ts.map +1 -0
  130. package/src/core/tools/impl/ListDirectoryTool.ts +142 -0
  131. package/src/core/tools/impl/MemoryTool.d.ts.map +1 -0
  132. package/src/core/tools/impl/MemoryTool.ts +102 -0
  133. package/src/core/tools/impl/ReadFileTool.d.ts.map +1 -0
  134. package/src/core/tools/impl/ReadFileTool.ts +58 -0
  135. package/src/core/tools/impl/SearchContentTool.d.ts.map +1 -0
  136. package/src/core/tools/impl/SearchContentTool.ts +94 -0
  137. package/src/core/tools/impl/SearchFileTool.d.ts.map +1 -0
  138. package/src/core/tools/impl/SearchFileTool.ts +61 -0
  139. package/src/core/tools/impl/ShellTool.d.ts.map +1 -0
  140. package/src/core/tools/impl/ShellTool.ts +118 -0
  141. package/src/core/tools/impl/TaskTool.d.ts.map +1 -0
  142. package/src/core/tools/impl/TaskTool.ts +207 -0
  143. package/src/core/tools/impl/TodoTool.d.ts.map +1 -0
  144. package/src/core/tools/impl/TodoTool.ts +122 -0
  145. package/src/core/tools/impl/WebFetchTool.d.ts.map +1 -0
  146. package/src/core/tools/impl/WebFetchTool.ts +103 -0
  147. package/src/core/tools/impl/WebSearchTool.d.ts.map +1 -0
  148. package/src/core/tools/impl/WebSearchTool.ts +89 -0
  149. package/src/core/tools/impl/WriteFileTool.d.ts.map +1 -0
  150. package/src/core/tools/impl/WriteFileTool.ts +49 -0
  151. package/src/core/tools/impl/index.d.ts.map +1 -0
  152. package/src/core/tools/impl/index.ts +16 -0
  153. package/src/core/tools/index.d.ts.map +1 -0
  154. package/src/core/tools/index.ts +7 -0
  155. package/src/core/tools/schemas/execution.d.ts.map +1 -0
  156. package/src/core/tools/schemas/execution.ts +42 -0
  157. package/src/core/tools/schemas/file.d.ts.map +1 -0
  158. package/src/core/tools/schemas/file.ts +119 -0
  159. package/src/core/tools/schemas/index.d.ts.map +1 -0
  160. package/src/core/tools/schemas/index.ts +11 -0
  161. package/src/core/tools/schemas/memory.d.ts.map +1 -0
  162. package/src/core/tools/schemas/memory.ts +52 -0
  163. package/src/core/tools/schemas/orchestration.d.ts.map +1 -0
  164. package/src/core/tools/schemas/orchestration.ts +44 -0
  165. package/src/core/tools/schemas/search.d.ts.map +1 -0
  166. package/src/core/tools/schemas/search.ts +112 -0
  167. package/src/core/tools/schemas/todo.d.ts.map +1 -0
  168. package/src/core/tools/schemas/todo.ts +32 -0
  169. package/src/core/tools/schemas/web.d.ts.map +1 -0
  170. package/src/core/tools/schemas/web.ts +86 -0
  171. package/src/core/types/config.d.ts.map +1 -0
  172. package/src/core/types/config.ts +200 -0
  173. package/src/core/types/errors.d.ts.map +1 -0
  174. package/src/core/types/errors.ts +204 -0
  175. package/src/core/types/index.d.ts.map +1 -0
  176. package/src/core/types/index.ts +8 -0
  177. package/src/core/types/session.d.ts.map +1 -0
  178. package/src/core/types/session.ts +216 -0
  179. package/src/core/types/tools.d.ts.map +1 -0
  180. package/src/core/types/tools.ts +157 -0
  181. package/src/core/utils/CheckpointManager.d.ts.map +1 -0
  182. package/src/core/utils/CheckpointManager.ts +327 -0
  183. package/src/core/utils/Logger.d.ts.map +1 -0
  184. package/src/core/utils/Logger.ts +98 -0
  185. package/src/core/utils/RetryManager.ts +471 -0
  186. package/src/core/utils/TokenCounter.d.ts.map +1 -0
  187. package/src/core/utils/TokenCounter.ts +414 -0
  188. package/src/core/utils/VectorMemoryStore.ts +440 -0
  189. package/src/core/utils/helpers.d.ts.map +1 -0
  190. package/src/core/utils/helpers.ts +89 -0
  191. package/src/core/utils/index.d.ts.map +1 -0
  192. package/src/core/utils/index.ts +19 -0
@@ -0,0 +1,509 @@
1
+ // ============================================================================
2
+ // AuditLogger - Comprehensive audit logging system
3
+ // ============================================================================
4
+
5
+ import * as fs from 'node:fs';
6
+ import * as path from 'node:path';
7
+ import { createLogger } from '../utils/Logger.js';
8
+ import { generateId } from '../utils/helpers.js';
9
+
10
+ const logger = createLogger('AuditLogger');
11
+
12
+ // ============================================================================
13
+ // Types
14
+ // ============================================================================
15
+
16
+ /**
17
+ * Audit action types
18
+ */
19
+ export type AuditAction =
20
+ | 'tool_use'
21
+ | 'file_read'
22
+ | 'file_write'
23
+ | 'file_delete'
24
+ | 'command_exec'
25
+ | 'model_call'
26
+ | 'session_start'
27
+ | 'session_end'
28
+ | 'approval_granted'
29
+ | 'approval_denied'
30
+ | 'config_change'
31
+ | 'auth_change';
32
+
33
+ /**
34
+ * Audit log entry
35
+ */
36
+ export interface AuditEntry {
37
+ id: string;
38
+ timestamp: Date;
39
+ action: AuditAction;
40
+ actor: 'user' | 'agent' | 'system';
41
+ resource: string;
42
+ result: 'success' | 'denied' | 'error' | 'timeout';
43
+ sessionId?: string;
44
+ metadata: {
45
+ model?: string;
46
+ provider?: string;
47
+ tokens?: { input: number; output: number };
48
+ duration?: number;
49
+ reason?: string;
50
+ error?: string;
51
+ input?: Record<string, unknown>;
52
+ output?: string;
53
+ diff?: string;
54
+ ip?: string;
55
+ userAgent?: string;
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Audit query filter
61
+ */
62
+ export interface AuditFilter {
63
+ startTime?: Date;
64
+ endTime?: Date;
65
+ actions?: AuditAction[];
66
+ actors?: ('user' | 'agent' | 'system')[];
67
+ results?: AuditEntry['result'][];
68
+ sessionId?: string;
69
+ resourcePattern?: string;
70
+ }
71
+
72
+ /**
73
+ * Audit configuration
74
+ */
75
+ export interface AuditConfig {
76
+ /** Log file path */
77
+ logFile: string;
78
+ /** Maximum log file size in bytes (default 10MB) */
79
+ maxFileSize?: number;
80
+ /** Maximum number of log files to keep (default 10) */
81
+ maxFiles?: number;
82
+ /** Whether to log to console (default false) */
83
+ console?: boolean;
84
+ /** Minimum log level */
85
+ minLevel?: 'debug' | 'info' | 'warn' | 'error';
86
+ }
87
+
88
+ // ============================================================================
89
+ // AuditLogger
90
+ // ============================================================================
91
+
92
+ /**
93
+ * Comprehensive audit logging system
94
+ */
95
+ export class AuditLogger {
96
+ private config: Required<AuditConfig>;
97
+ private writeQueue: AuditEntry[] = [];
98
+ private isWriting = false;
99
+ private flushInterval: NodeJS.Timeout | null = null;
100
+
101
+ constructor(config: AuditConfig) {
102
+ this.config = {
103
+ maxFileSize: config.maxFileSize ?? 10 * 1024 * 1024, // 10MB
104
+ maxFiles: config.maxFiles ?? 10,
105
+ console: config.console ?? false,
106
+ minLevel: config.minLevel ?? 'info',
107
+ ...config,
108
+ };
109
+
110
+ // Ensure log directory exists
111
+ const logDir = path.dirname(this.config.logFile);
112
+ if (!fs.existsSync(logDir)) {
113
+ fs.mkdirSync(logDir, { recursive: true });
114
+ }
115
+
116
+ // Start periodic flush
117
+ this.flushInterval = setInterval(() => this.flush(), 5000);
118
+
119
+ // Flush on exit
120
+ process.on('beforeExit', () => this.flush());
121
+ }
122
+
123
+ // -----------------------------------------------------------------------
124
+ // Core Logging
125
+ // -----------------------------------------------------------------------
126
+
127
+ /**
128
+ * Log an audit entry
129
+ */
130
+ async log(entry: Omit<AuditEntry, 'id' | 'timestamp'>): Promise<AuditEntry> {
131
+ const fullEntry: AuditEntry = {
132
+ ...entry,
133
+ id: generateId(),
134
+ timestamp: new Date(),
135
+ };
136
+
137
+ // Add to write queue
138
+ this.writeQueue.push(fullEntry);
139
+
140
+ // Console output
141
+ if (this.config.console) {
142
+ this.logToConsole(fullEntry);
143
+ }
144
+
145
+ // Flush if queue is getting large
146
+ if (this.writeQueue.length >= 100) {
147
+ await this.flush();
148
+ }
149
+
150
+ return fullEntry;
151
+ }
152
+
153
+ /**
154
+ * Log a tool use event
155
+ */
156
+ async logToolUse(
157
+ toolName: string,
158
+ input: Record<string, unknown>,
159
+ result: AuditEntry['result'],
160
+ metadata: Partial<AuditEntry['metadata']> = {}
161
+ ): Promise<AuditEntry> {
162
+ return this.log({
163
+ action: 'tool_use',
164
+ actor: 'agent',
165
+ resource: toolName,
166
+ result,
167
+ metadata: {
168
+ input,
169
+ ...metadata,
170
+ },
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Log a file operation
176
+ */
177
+ async logFileOperation(
178
+ operation: 'file_read' | 'file_write' | 'file_delete',
179
+ filePath: string,
180
+ result: AuditEntry['result'],
181
+ metadata: Partial<AuditEntry['metadata']> = {}
182
+ ): Promise<AuditEntry> {
183
+ return this.log({
184
+ action: operation,
185
+ actor: 'agent',
186
+ resource: filePath,
187
+ result,
188
+ metadata,
189
+ });
190
+ }
191
+
192
+ /**
193
+ * Log a command execution
194
+ */
195
+ async logCommand(
196
+ command: string,
197
+ result: AuditEntry['result'],
198
+ metadata: Partial<AuditEntry['metadata']> = {}
199
+ ): Promise<AuditEntry> {
200
+ return this.log({
201
+ action: 'command_exec',
202
+ actor: 'agent',
203
+ resource: command,
204
+ result,
205
+ metadata,
206
+ });
207
+ }
208
+
209
+ /**
210
+ * Log a model API call
211
+ */
212
+ async logModelCall(
213
+ model: string,
214
+ provider: string,
215
+ result: AuditEntry['result'],
216
+ metadata: Partial<AuditEntry['metadata']> = {}
217
+ ): Promise<AuditEntry> {
218
+ return this.log({
219
+ action: 'model_call',
220
+ actor: 'agent',
221
+ resource: `${provider}/${model}`,
222
+ result,
223
+ metadata: {
224
+ model,
225
+ provider,
226
+ ...metadata,
227
+ },
228
+ });
229
+ }
230
+
231
+ /**
232
+ * Log an approval decision
233
+ */
234
+ async logApproval(
235
+ resource: string,
236
+ granted: boolean,
237
+ reason?: string
238
+ ): Promise<AuditEntry> {
239
+ return this.log({
240
+ action: granted ? 'approval_granted' : 'approval_denied',
241
+ actor: 'user',
242
+ resource,
243
+ result: granted ? 'success' : 'denied',
244
+ metadata: { reason },
245
+ });
246
+ }
247
+
248
+ /**
249
+ * Log a session event
250
+ */
251
+ async logSessionEvent(
252
+ event: 'session_start' | 'session_end',
253
+ sessionId: string,
254
+ metadata: Partial<AuditEntry['metadata']> = {}
255
+ ): Promise<AuditEntry> {
256
+ return this.log({
257
+ action: event,
258
+ actor: 'system',
259
+ resource: sessionId,
260
+ result: 'success',
261
+ sessionId,
262
+ metadata,
263
+ });
264
+ }
265
+
266
+ // -----------------------------------------------------------------------
267
+ // Querying
268
+ // -----------------------------------------------------------------------
269
+
270
+ /**
271
+ * Query audit logs
272
+ */
273
+ async query(filter: AuditFilter = {}): Promise<AuditEntry[]> {
274
+ await this.flush(); // Ensure all entries are written
275
+
276
+ const entries: AuditEntry[] = [];
277
+
278
+ try {
279
+ // Read all log files
280
+ const logDir = path.dirname(this.config.logFile);
281
+ const baseName = path.basename(this.config.logFile, '.jsonl');
282
+ const files = fs.readdirSync(logDir)
283
+ .filter(f => f.startsWith(baseName))
284
+ .sort()
285
+ .reverse(); // Most recent first
286
+
287
+ for (const file of files) {
288
+ const filePath = path.join(logDir, file);
289
+ const content = fs.readFileSync(filePath, 'utf-8');
290
+ const lines = content.split('\n').filter(l => l.trim());
291
+
292
+ for (const line of lines) {
293
+ try {
294
+ const entry = JSON.parse(line) as AuditEntry;
295
+ entry.timestamp = new Date(entry.timestamp);
296
+
297
+ if (this.matchesFilter(entry, filter)) {
298
+ entries.push(entry);
299
+ }
300
+ } catch {
301
+ // Skip malformed lines
302
+ }
303
+ }
304
+ }
305
+ } catch (error) {
306
+ logger.error('Failed to query audit logs', { error });
307
+ }
308
+
309
+ return entries;
310
+ }
311
+
312
+ /**
313
+ * Check if entry matches filter
314
+ */
315
+ private matchesFilter(entry: AuditEntry, filter: AuditFilter): boolean {
316
+ if (filter.startTime && entry.timestamp < filter.startTime) return false;
317
+ if (filter.endTime && entry.timestamp > filter.endTime) return false;
318
+ if (filter.actions && !filter.actions.includes(entry.action)) return false;
319
+ if (filter.actors && !filter.actors.includes(entry.actor)) return false;
320
+ if (filter.results && !filter.results.includes(entry.result)) return false;
321
+ if (filter.sessionId && entry.sessionId !== filter.sessionId) return false;
322
+ if (filter.resourcePattern && !entry.resource.match(new RegExp(filter.resourcePattern, 'i'))) return false;
323
+
324
+ return true;
325
+ }
326
+
327
+ // -----------------------------------------------------------------------
328
+ // Export
329
+ // -----------------------------------------------------------------------
330
+
331
+ /**
332
+ * Export logs in various formats
333
+ */
334
+ async export(format: 'json' | 'csv' | 'txt', filter: AuditFilter = {}): Promise<string> {
335
+ const entries = await this.query(filter);
336
+
337
+ if (format === 'json') {
338
+ return JSON.stringify(entries, null, 2);
339
+ }
340
+
341
+ if (format === 'csv') {
342
+ const headers = ['id', 'timestamp', 'action', 'actor', 'resource', 'result', 'sessionId'];
343
+ const rows = entries.map(e => [
344
+ e.id,
345
+ e.timestamp.toISOString(),
346
+ e.action,
347
+ e.actor,
348
+ `"${e.resource.replace(/"/g, '""')}"`,
349
+ e.result,
350
+ e.sessionId ?? '',
351
+ ].join(','));
352
+ return [headers.join(','), ...rows].join('\n');
353
+ }
354
+
355
+ // format === 'txt'
356
+ return entries.map(e => {
357
+ const time = e.timestamp.toISOString();
358
+ return `[${time}] ${e.action.toUpperCase()} ${e.actor} ${e.resource} (${e.result})`;
359
+ }).join('\n');
360
+ }
361
+
362
+ // -----------------------------------------------------------------------
363
+ // Maintenance
364
+ // -----------------------------------------------------------------------
365
+
366
+ /**
367
+ * Flush write queue to disk
368
+ */
369
+ async flush(): Promise<void> {
370
+ if (this.isWriting || this.writeQueue.length === 0) return;
371
+
372
+ this.isWriting = true;
373
+ const entries = [...this.writeQueue];
374
+ this.writeQueue = [];
375
+
376
+ try {
377
+ // Check if log rotation is needed
378
+ await this.rotateIfNeeded();
379
+
380
+ // Append entries
381
+ const lines = entries.map(e => JSON.stringify(e)).join('\n') + '\n';
382
+ fs.appendFileSync(this.config.logFile, lines, 'utf-8');
383
+
384
+ logger.debug(`Flushed ${entries.length} audit entries`);
385
+ } catch (error) {
386
+ logger.error('Failed to flush audit entries', { error });
387
+ // Put entries back in queue
388
+ this.writeQueue.unshift(...entries);
389
+ } finally {
390
+ this.isWriting = false;
391
+ }
392
+ }
393
+
394
+ /**
395
+ * Rotate log file if needed
396
+ */
397
+ private async rotateIfNeeded(): Promise<void> {
398
+ try {
399
+ if (!fs.existsSync(this.config.logFile)) return;
400
+
401
+ const stats = fs.statSync(this.config.logFile);
402
+
403
+ if (stats.size >= this.config.maxFileSize) {
404
+ const logDir = path.dirname(this.config.logFile);
405
+ const baseName = path.basename(this.config.logFile, '.jsonl');
406
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
407
+ const newName = `${baseName}.${timestamp}.jsonl`;
408
+
409
+ fs.renameSync(this.config.logFile, path.join(logDir, newName));
410
+
411
+ // Clean up old files
412
+ const files = fs.readdirSync(logDir)
413
+ .filter(f => f.startsWith(baseName) && f.endsWith('.jsonl'))
414
+ .sort();
415
+
416
+ while (files.length > this.config.maxFiles) {
417
+ const oldFile = files.shift()!;
418
+ fs.unlinkSync(path.join(logDir, oldFile));
419
+ logger.debug(`Deleted old audit log: ${oldFile}`);
420
+ }
421
+
422
+ logger.info(`Rotated audit log to ${newName}`);
423
+ }
424
+ } catch (error) {
425
+ logger.error('Failed to rotate audit log', { error });
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Get statistics about the audit log
431
+ */
432
+ async getStats(): Promise<{
433
+ totalEntries: number;
434
+ oldestEntry: Date | null;
435
+ newestEntry: Date | null;
436
+ byAction: Record<string, number>;
437
+ byResult: Record<string, number>;
438
+ }> {
439
+ const entries = await this.query();
440
+
441
+ const byAction: Record<string, number> = {};
442
+ const byResult: Record<string, number> = {};
443
+ let oldest: Date | null = null;
444
+ let newest: Date | null = null;
445
+
446
+ for (const entry of entries) {
447
+ byAction[entry.action] = (byAction[entry.action] ?? 0) + 1;
448
+ byResult[entry.result] = (byResult[entry.result] ?? 0) + 1;
449
+
450
+ if (!oldest || entry.timestamp < oldest) oldest = entry.timestamp;
451
+ if (!newest || entry.timestamp > newest) newest = entry.timestamp;
452
+ }
453
+
454
+ return {
455
+ totalEntries: entries.length,
456
+ oldestEntry: oldest,
457
+ newestEntry: newest,
458
+ byAction,
459
+ byResult,
460
+ };
461
+ }
462
+
463
+ /**
464
+ * Log to console
465
+ */
466
+ private logToConsole(entry: AuditEntry): void {
467
+ const time = entry.timestamp.toISOString();
468
+ const level = entry.result === 'error' ? 'error' : entry.result === 'denied' ? 'warn' : 'info';
469
+ const message = `[AUDIT] ${time} ${entry.action} ${entry.actor} ${entry.resource} (${entry.result})`;
470
+
471
+ switch (level) {
472
+ case 'error':
473
+ console.error(message);
474
+ break;
475
+ case 'warn':
476
+ console.warn(message);
477
+ break;
478
+ default:
479
+ console.log(message);
480
+ }
481
+ }
482
+
483
+ /**
484
+ * Shutdown the logger
485
+ */
486
+ shutdown(): void {
487
+ if (this.flushInterval) {
488
+ clearInterval(this.flushInterval);
489
+ this.flushInterval = null;
490
+ }
491
+ this.flush();
492
+ }
493
+ }
494
+
495
+ // ============================================================================
496
+ // Factory function
497
+ // ============================================================================
498
+
499
+ /**
500
+ * Create an audit logger with default configuration
501
+ */
502
+ export function createAuditLogger(
503
+ storageDir?: string
504
+ ): AuditLogger {
505
+ const defaultDir = storageDir ?? path.join(process.env.HOME ?? '~', '.nova', 'logs');
506
+ return new AuditLogger({
507
+ logFile: path.join(defaultDir, 'audit.jsonl'),
508
+ });
509
+ }
@@ -0,0 +1,11 @@
1
+ // ============================================================================
2
+ // Audit Module - Audit logging system
3
+ // ============================================================================
4
+
5
+ export { AuditLogger, createAuditLogger } from './AuditLogger.js';
6
+ export type {
7
+ AuditAction,
8
+ AuditEntry,
9
+ AuditFilter,
10
+ AuditConfig,
11
+ } from './AuditLogger.js';
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthManager.d.ts","sourceRoot":"","sources":["AuthManager.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,WAAW,CAAsC;;IAMzD,oCAAoC;IAC9B,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAYtC,qCAAqC;IACrC,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAiBtG,gDAAgD;IAChD,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAKzC,qCAAqC;IAC/B,cAAc,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAe3H,wCAAwC;IAClC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAM3D,oCAAoC;IACpC,aAAa,IAAI,MAAM,EAAE;IAIzB,kCAAkC;YACpB,eAAe;IAiB7B,OAAO,CAAC,aAAa;CA6BtB"}
@@ -0,0 +1,138 @@
1
+ // ============================================================================
2
+ // AuthManager - Manages API credentials
3
+ // ============================================================================
4
+
5
+ import fs from 'node:fs/promises';
6
+ import path from 'node:path';
7
+ import os from 'node:os';
8
+
9
+ export interface CredentialEntry {
10
+ provider: string;
11
+ apiKey: string;
12
+ baseUrl?: string;
13
+ organizationId?: string;
14
+ createdAt: number;
15
+ updatedAt: number;
16
+ }
17
+
18
+ export class AuthManager {
19
+ private credentialsPath: string;
20
+ private credentials = new Map<string, CredentialEntry>();
21
+
22
+ constructor() {
23
+ this.credentialsPath = path.join(os.homedir(), '.nova', 'credentials.json');
24
+ }
25
+
26
+ /** Load credentials from storage */
27
+ async loadCredentials(): Promise<void> {
28
+ try {
29
+ const content = await fs.readFile(this.credentialsPath, 'utf-8');
30
+ const data = JSON.parse(content) as Record<string, CredentialEntry>;
31
+ for (const [provider, entry] of Object.entries(data)) {
32
+ this.credentials.set(provider, entry);
33
+ }
34
+ } catch {
35
+ // No credentials file yet
36
+ }
37
+ }
38
+
39
+ /** Get credentials for a provider */
40
+ getCredentials(provider: string): { apiKey: string; baseUrl?: string; organizationId?: string } | null {
41
+ const entry = this.credentials.get(provider);
42
+ if (!entry) {
43
+ const envKey = this.getEnvKeyName(provider);
44
+ const envValue = process.env[envKey];
45
+ if (envValue) {
46
+ return { apiKey: envValue };
47
+ }
48
+ return null;
49
+ }
50
+ return {
51
+ apiKey: entry.apiKey,
52
+ baseUrl: entry.baseUrl,
53
+ organizationId: entry.organizationId,
54
+ };
55
+ }
56
+
57
+ /** Check if credentials exist for a provider */
58
+ hasCredentials(provider: string): boolean {
59
+ if (this.credentials.has(provider)) return true;
60
+ return !!process.env[this.getEnvKeyName(provider)];
61
+ }
62
+
63
+ /** Set credentials for a provider */
64
+ async setCredentials(entry: { provider: string; apiKey: string; baseUrl?: string; organizationId?: string }): Promise<void> {
65
+ const existing = this.credentials.get(entry.provider);
66
+ const credential: CredentialEntry = {
67
+ provider: entry.provider,
68
+ apiKey: entry.apiKey,
69
+ baseUrl: entry.baseUrl,
70
+ organizationId: entry.organizationId,
71
+ createdAt: existing?.createdAt || Date.now(),
72
+ updatedAt: Date.now(),
73
+ };
74
+
75
+ this.credentials.set(entry.provider, credential);
76
+ await this.saveCredentials();
77
+ }
78
+
79
+ /** Remove credentials for a provider */
80
+ async removeCredentials(provider: string): Promise<boolean> {
81
+ const deleted = this.credentials.delete(provider);
82
+ if (deleted) await this.saveCredentials();
83
+ return deleted;
84
+ }
85
+
86
+ /** List all configured providers */
87
+ listProviders(): string[] {
88
+ return Array.from(this.credentials.keys());
89
+ }
90
+
91
+ /** Save credentials to storage */
92
+ private async saveCredentials(): Promise<void> {
93
+ const dir = path.dirname(this.credentialsPath);
94
+ await fs.mkdir(dir, { recursive: true });
95
+
96
+ const data: Record<string, CredentialEntry> = {};
97
+ for (const [provider, entry] of this.credentials) {
98
+ data[provider] = entry;
99
+ }
100
+
101
+ await fs.writeFile(this.credentialsPath, JSON.stringify(data, null, 2), 'utf-8');
102
+ try {
103
+ await fs.chmod(this.credentialsPath, 0o600);
104
+ } catch {
105
+ // chmod may fail on some systems
106
+ }
107
+ }
108
+
109
+ private getEnvKeyName(provider: string): string {
110
+ const envMap: Record<string, string> = {
111
+ // Built-in
112
+ anthropic: 'ANTHROPIC_API_KEY',
113
+ openai: 'OPENAI_API_KEY',
114
+ azure: 'AZURE_OPENAI_API_KEY',
115
+ ollama: 'OLLAMA_HOST',
116
+ 'ollama-cloud': 'OLLAMA_API_KEY',
117
+ // Major cloud providers
118
+ google: 'GOOGLE_API_KEY',
119
+ deepseek: 'DEEPSEEK_API_KEY',
120
+ // Chinese providers
121
+ qwen: 'DASHSCOPE_API_KEY',
122
+ glm: 'ZHIPU_API_KEY',
123
+ zhipu: 'ZHIPU_API_KEY',
124
+ moonshot: 'MOONSHOT_API_KEY',
125
+ baichuan: 'BAICHUAN_API_KEY',
126
+ minimax: 'MINIMAX_API_KEY',
127
+ yi: 'YI_API_KEY',
128
+ siliconflow: 'SILICONFLOW_API_KEY',
129
+ // International providers
130
+ groq: 'GROQ_API_KEY',
131
+ mistral: 'MISTRAL_API_KEY',
132
+ together: 'TOGETHER_API_KEY',
133
+ cohere: 'COHERE_API_KEY',
134
+ perplexity: 'PERPLEXITY_API_KEY',
135
+ };
136
+ return envMap[provider] || `${provider.toUpperCase().replace(/-/g, '_')}_API_KEY`;
137
+ }
138
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { AuthManager } from './AuthManager.js';
2
+ export type { CredentialEntry } from './AuthManager.js';
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfigManager.d.ts","sourceRoot":"","sources":["ConfigManager.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AA+7BnG,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,iBAAiB,CAAuB;;IAMhD,0DAA0D;IACpD,IAAI,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAqCpD,mCAAmC;IACnC,SAAS,IAAI,UAAU;IAOvB,sBAAsB;IACtB,aAAa,IAAI,UAAU;IAI3B,8BAA8B;IAC9B,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG;QAAE,QAAQ,EAAE,mBAAmB,CAAC;QAAC,KAAK,EAAE,WAAW,CAAA;KAAE,GAAG,IAAI;IA6C7F;;;;OAIG;IACH,aAAa,CACX,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,WAAW,EACxB,iBAAiB,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,GAC/C,OAAO;IAuBV;;OAEG;IACH,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,mBAAmB,GAAG,OAAO;IAMpF;;OAEG;IACH,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI;IAQzD,2BAA2B;IAC3B,aAAa,IAAI,MAAM;IAIvB,4BAA4B;IAC5B,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKtC,yBAAyB;IACnB,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAOtD,oCAAoC;IACpC,OAAO,CAAC,WAAW;IAYnB,2CAA2C;IAC3C,OAAO,CAAC,iBAAiB;IAkBzB,+CAA+C;IAC/C,OAAO,CAAC,WAAW;IA6BnB,2BAA2B;IAC3B,OAAO,CAAC,UAAU;CAWnB"}