@codedir/mimir-code 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.
package/dist/index.js ADDED
@@ -0,0 +1,1656 @@
1
+ // src/types/index.ts
2
+ var createOk = (value) => ({ ok: true, value });
3
+ var createErr = (error) => ({ ok: false, error });
4
+
5
+ // src/core/Agent.ts
6
+ var Agent = class {
7
+ // private provider: ILLMProvider; // TODO: Will be used when LLM integration is implemented
8
+ toolRegistry;
9
+ config;
10
+ conversationHistory = [];
11
+ currentIteration = 0;
12
+ constructor(_provider, toolRegistry, config) {
13
+ this.toolRegistry = toolRegistry;
14
+ this.config = config;
15
+ }
16
+ async run(task) {
17
+ this.conversationHistory = [
18
+ {
19
+ role: "system",
20
+ content: "You are a helpful coding assistant."
21
+ },
22
+ {
23
+ role: "user",
24
+ content: task
25
+ }
26
+ ];
27
+ while (this.currentIteration < this.config.maxIterations) {
28
+ const action = await this.reason();
29
+ if (action.type === "finish") {
30
+ return createOk(action.result);
31
+ }
32
+ const observation = await this.act(action);
33
+ await this.observe(observation);
34
+ this.currentIteration++;
35
+ }
36
+ return createErr(new Error("Max iterations reached"));
37
+ }
38
+ async reason() {
39
+ return {
40
+ type: "finish",
41
+ result: "Task completed"
42
+ };
43
+ }
44
+ async act(action) {
45
+ if (!action.toolName || !action.arguments) {
46
+ return {
47
+ type: "error",
48
+ error: "Invalid action: missing toolName or arguments"
49
+ };
50
+ }
51
+ const result = await this.toolRegistry.execute(action.toolName, action.arguments);
52
+ return {
53
+ type: "tool_result",
54
+ data: result
55
+ };
56
+ }
57
+ async observe(observation) {
58
+ this.conversationHistory.push({
59
+ role: "assistant",
60
+ content: JSON.stringify(observation)
61
+ });
62
+ }
63
+ };
64
+
65
+ // src/core/Tool.ts
66
+ var ToolRegistry = class {
67
+ tools = /* @__PURE__ */ new Map();
68
+ register(tool) {
69
+ this.tools.set(tool.name, tool);
70
+ }
71
+ unregister(toolName) {
72
+ this.tools.delete(toolName);
73
+ }
74
+ get(toolName) {
75
+ return this.tools.get(toolName);
76
+ }
77
+ getAll() {
78
+ return Array.from(this.tools.values());
79
+ }
80
+ has(toolName) {
81
+ return this.tools.has(toolName);
82
+ }
83
+ async execute(toolName, args) {
84
+ const tool = this.get(toolName);
85
+ if (!tool) {
86
+ return {
87
+ success: false,
88
+ error: `Tool not found: ${toolName}`
89
+ };
90
+ }
91
+ try {
92
+ const validatedArgs = tool.schema.parse(args);
93
+ return await tool.execute(validatedArgs);
94
+ } catch (error) {
95
+ return {
96
+ success: false,
97
+ error: error instanceof Error ? error.message : String(error)
98
+ };
99
+ }
100
+ }
101
+ };
102
+
103
+ // src/core/RiskAssessor.ts
104
+ var RiskAssessor = class {
105
+ // Critical patterns - system-destroying commands
106
+ criticalPatterns = [
107
+ { pattern: /rm\s+-rf\s+\/(?!tmp|var\/tmp)/, reason: "Deletes root filesystem" },
108
+ { pattern: /format\s+[a-z]:/i, reason: "Formats entire drive" },
109
+ { pattern: /del\s+\/[sf]/i, reason: "Deletes system files (Windows)" },
110
+ { pattern: /shutdown|reboot|poweroff/, reason: "System shutdown/reboot" },
111
+ { pattern: /dd\s+.*of=\/dev\/(sda|hda|nvme)/, reason: "Direct disk write (can destroy data)" },
112
+ { pattern: /mkfs/, reason: "Formats filesystem" },
113
+ {
114
+ pattern: /(>|vim|vi|nano|emacs|edit).*\/etc\/(passwd|shadow|sudoers)/,
115
+ reason: "Modifies critical system files"
116
+ },
117
+ { pattern: /curl.*\|\s*(bash|sh|python)/, reason: "Executes remote script without inspection" },
118
+ { pattern: /wget.*\|\s*(bash|sh|python)/, reason: "Executes remote script without inspection" }
119
+ ];
120
+ // High risk patterns - destructive but recoverable
121
+ highPatterns = [
122
+ { pattern: /rm\s+-rf\s+(?!\/($|\s))/, reason: "Recursive force delete" },
123
+ { pattern: /sudo\s+rm/, reason: "Elevated permissions file deletion" },
124
+ { pattern: /git\s+push\s+--force/, reason: "Force pushes can overwrite history" },
125
+ { pattern: /npm\s+publish/, reason: "Publishes package to registry" },
126
+ { pattern: /docker\s+rmi.*-f/, reason: "Force removes Docker images" },
127
+ { pattern: /docker\s+system\s+prune\s+-a/, reason: "Removes all unused Docker data" },
128
+ { pattern: /git\s+reset\s+--hard\s+HEAD~/, reason: "Permanently deletes commits" },
129
+ { pattern: /git\s+clean\s+-fd/, reason: "Deletes untracked files" },
130
+ { pattern: /chmod\s+777/, reason: "Makes files world-writable (security risk)" },
131
+ { pattern: /chown\s+-R/, reason: "Recursive ownership change" }
132
+ ];
133
+ // Medium risk patterns - potentially problematic
134
+ mediumPatterns = [
135
+ { pattern: /npm\s+install/, reason: "Installs dependencies (can include malicious packages)" },
136
+ { pattern: /yarn\s+add/, reason: "Installs dependencies (can include malicious packages)" },
137
+ { pattern: /pip\s+install/, reason: "Installs Python packages" },
138
+ { pattern: /git\s+push/, reason: "Pushes changes to remote" },
139
+ { pattern: /docker\s+run/, reason: "Runs Docker container" },
140
+ { pattern: /docker\s+exec/, reason: "Executes command in container" },
141
+ { pattern: /ssh\s+/, reason: "Remote connection" },
142
+ { pattern: /scp\s+/, reason: "Remote file transfer" },
143
+ { pattern: /rsync\s+/, reason: "File synchronization" },
144
+ { pattern: /npm\s+run\s+build/, reason: "Runs build scripts" }
145
+ ];
146
+ /**
147
+ * Assess risk level and provide detailed reasons
148
+ */
149
+ assess(command) {
150
+ const reasons = [];
151
+ let maxScore = 0;
152
+ for (const { pattern, reason } of this.criticalPatterns) {
153
+ if (pattern.test(command)) {
154
+ reasons.push(`\u{1F534} CRITICAL: ${reason}`);
155
+ maxScore = Math.max(maxScore, 100);
156
+ }
157
+ }
158
+ for (const { pattern, reason } of this.highPatterns) {
159
+ if (pattern.test(command)) {
160
+ reasons.push(`\u{1F7E0} HIGH: ${reason}`);
161
+ maxScore = Math.max(maxScore, 75);
162
+ }
163
+ }
164
+ for (const { pattern, reason } of this.mediumPatterns) {
165
+ if (pattern.test(command)) {
166
+ reasons.push(`\u{1F7E1} MEDIUM: ${reason}`);
167
+ maxScore = Math.max(maxScore, 50);
168
+ }
169
+ }
170
+ const additionalRisks = this.assessAdditionalRisks(command);
171
+ reasons.push(...additionalRisks.reasons);
172
+ maxScore = Math.max(maxScore, additionalRisks.score);
173
+ const level = this.scoreToLevel(maxScore);
174
+ if (reasons.length === 0) {
175
+ reasons.push("No specific risks detected");
176
+ }
177
+ return {
178
+ level,
179
+ reasons,
180
+ score: maxScore
181
+ };
182
+ }
183
+ /**
184
+ * Additional risk checks (file size, complexity, etc.)
185
+ */
186
+ assessAdditionalRisks(command) {
187
+ const reasons = [];
188
+ let score = 0;
189
+ if (command.length > 500) {
190
+ reasons.push("\u26A0\uFE0F Command is unusually long (possible obfuscation)");
191
+ score = Math.max(score, 30);
192
+ }
193
+ const chainCount = (command.match(/[;&|]+/g) || []).length;
194
+ if (chainCount > 3) {
195
+ reasons.push(`\u26A0\uFE0F Multiple chained commands (${chainCount} chains)`);
196
+ score = Math.max(score, 40);
197
+ }
198
+ if (/>\/dev\/null|2>&1/.test(command)) {
199
+ reasons.push("\u26A0\uFE0F Output redirected (hiding results)");
200
+ score = Math.max(score, 20);
201
+ }
202
+ if (/sudo\s*$/.test(command)) {
203
+ reasons.push("\u{1F7E0} Elevated permissions without specific command");
204
+ score = Math.max(score, 60);
205
+ }
206
+ if (/export\s+|setenv\s+/.test(command) && /PATH/.test(command)) {
207
+ reasons.push("\u{1F7E1} Modifies PATH environment variable");
208
+ score = Math.max(score, 45);
209
+ }
210
+ if (/base64\s+--decode|echo\s+.*\|\s*base64/.test(command)) {
211
+ reasons.push("\u26A0\uFE0F Uses Base64 encoding (possible obfuscation)");
212
+ score = Math.max(score, 35);
213
+ }
214
+ if (/eval\s+/.test(command)) {
215
+ reasons.push("\u{1F7E0} Uses eval (dynamic code execution)");
216
+ score = Math.max(score, 65);
217
+ }
218
+ return { reasons, score };
219
+ }
220
+ /**
221
+ * Convert numeric score to risk level
222
+ */
223
+ scoreToLevel(score) {
224
+ if (score >= 80) return "critical";
225
+ if (score >= 60) return "high";
226
+ if (score >= 30) return "medium";
227
+ return "low";
228
+ }
229
+ /**
230
+ * Check if command matches any allowed patterns
231
+ */
232
+ isAllowed(command, allowlist) {
233
+ return allowlist.some((pattern) => {
234
+ try {
235
+ const regex = new RegExp(pattern);
236
+ return regex.test(command);
237
+ } catch {
238
+ return command.includes(pattern);
239
+ }
240
+ });
241
+ }
242
+ /**
243
+ * Check if command matches any blocked patterns
244
+ */
245
+ isBlocked(command, blocklist) {
246
+ return blocklist.some((pattern) => {
247
+ try {
248
+ const regex = new RegExp(pattern);
249
+ return regex.test(command);
250
+ } catch {
251
+ return command.includes(pattern);
252
+ }
253
+ });
254
+ }
255
+ /**
256
+ * Get a human-readable summary of the risk assessment
257
+ */
258
+ getSummary(assessment) {
259
+ const levelEmoji = {
260
+ low: "\u{1F7E2}",
261
+ medium: "\u{1F7E1}",
262
+ high: "\u{1F7E0}",
263
+ critical: "\u{1F534}"
264
+ };
265
+ const lines = [
266
+ `${levelEmoji[assessment.level]} Risk Level: ${assessment.level.toUpperCase()} (score: ${assessment.score}/100)`,
267
+ "",
268
+ "Reasons:",
269
+ ...assessment.reasons.map((r) => ` \u2022 ${r}`)
270
+ ];
271
+ return lines.join("\n");
272
+ }
273
+ };
274
+
275
+ // src/core/PermissionManager.ts
276
+ var PermissionManager = class {
277
+ riskAssessor;
278
+ allowlist = /* @__PURE__ */ new Set();
279
+ blocklist = /* @__PURE__ */ new Set();
280
+ auditLog = [];
281
+ constructor() {
282
+ this.riskAssessor = new RiskAssessor();
283
+ }
284
+ async checkPermission(command) {
285
+ const assessment = this.riskAssessor.assess(command);
286
+ if (this.riskAssessor.isBlocked(command, Array.from(this.blocklist))) {
287
+ this.logDecision(command, assessment.level, "deny");
288
+ return { allowed: false, assessment };
289
+ }
290
+ if (this.riskAssessor.isAllowed(command, Array.from(this.allowlist))) {
291
+ this.logDecision(command, assessment.level, "allow");
292
+ return { allowed: true, assessment };
293
+ }
294
+ if (assessment.level === "high" || assessment.level === "critical") {
295
+ this.logDecision(command, assessment.level, "deny");
296
+ return { allowed: false, assessment };
297
+ }
298
+ this.logDecision(command, assessment.level, "allow");
299
+ return { allowed: true, assessment };
300
+ }
301
+ addToAllowlist(pattern) {
302
+ this.allowlist.add(pattern);
303
+ }
304
+ addToBlocklist(pattern) {
305
+ this.blocklist.add(pattern);
306
+ }
307
+ logDecision(command, riskLevel, decision) {
308
+ this.auditLog.push({
309
+ command,
310
+ riskLevel,
311
+ decision,
312
+ timestamp: Date.now()
313
+ });
314
+ }
315
+ getAuditLog() {
316
+ return [...this.auditLog];
317
+ }
318
+ };
319
+
320
+ // src/utils/errors.ts
321
+ var MimirError = class extends Error {
322
+ constructor(message) {
323
+ super(message);
324
+ this.name = "MimirError";
325
+ }
326
+ };
327
+ var ConfigurationError = class extends MimirError {
328
+ constructor(message) {
329
+ super(message);
330
+ this.name = "ConfigurationError";
331
+ }
332
+ };
333
+ var ProviderError = class extends MimirError {
334
+ constructor(message, provider) {
335
+ super(message);
336
+ this.provider = provider;
337
+ this.name = "ProviderError";
338
+ }
339
+ };
340
+ var ToolExecutionError = class extends MimirError {
341
+ constructor(message, toolName) {
342
+ super(message);
343
+ this.toolName = toolName;
344
+ this.name = "ToolExecutionError";
345
+ }
346
+ };
347
+ var PermissionDeniedError = class extends MimirError {
348
+ constructor(message, command) {
349
+ super(message);
350
+ this.command = command;
351
+ this.name = "PermissionDeniedError";
352
+ }
353
+ };
354
+ var DockerError = class extends MimirError {
355
+ constructor(message) {
356
+ super(message);
357
+ this.name = "DockerError";
358
+ }
359
+ };
360
+ var NetworkError = class extends MimirError {
361
+ constructor(message, statusCode) {
362
+ super(message);
363
+ this.statusCode = statusCode;
364
+ this.name = "NetworkError";
365
+ }
366
+ };
367
+ var RateLimitError = class extends MimirError {
368
+ constructor(message, retryAfter) {
369
+ super(message);
370
+ this.retryAfter = retryAfter;
371
+ this.name = "RateLimitError";
372
+ }
373
+ };
374
+
375
+ // src/providers/BaseLLMProvider.ts
376
+ var BaseLLMProvider = class {
377
+ config;
378
+ retryConfig;
379
+ constructor(config, retryConfig) {
380
+ this.config = config;
381
+ this.retryConfig = retryConfig ?? {
382
+ maxRetries: 3,
383
+ retryDelay: 1e3,
384
+ backoffMultiplier: 2
385
+ };
386
+ }
387
+ getProviderName() {
388
+ return this.config.provider;
389
+ }
390
+ getModelName() {
391
+ return this.config.model;
392
+ }
393
+ /**
394
+ * Retry wrapper for API calls
395
+ * Only retries on NetworkError (5xx server errors)
396
+ * Does NOT retry on auth errors, rate limits, or client errors
397
+ */
398
+ async withRetry(fn) {
399
+ let lastError;
400
+ let delay = this.retryConfig.retryDelay;
401
+ for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
402
+ try {
403
+ return await fn();
404
+ } catch (error) {
405
+ lastError = error;
406
+ const shouldRetry = error instanceof NetworkError && attempt < this.retryConfig.maxRetries;
407
+ if (shouldRetry) {
408
+ await this.sleep(delay);
409
+ delay *= this.retryConfig.backoffMultiplier;
410
+ } else {
411
+ throw lastError;
412
+ }
413
+ }
414
+ }
415
+ throw lastError;
416
+ }
417
+ sleep(ms) {
418
+ return new Promise((resolve) => setTimeout(resolve, ms));
419
+ }
420
+ };
421
+
422
+ // src/providers/DeepSeekProvider.ts
423
+ import { encoding_for_model } from "tiktoken";
424
+
425
+ // src/providers/utils/apiClient.ts
426
+ import axios from "axios";
427
+ var APIClient = class {
428
+ axiosInstance;
429
+ providerName;
430
+ constructor(config) {
431
+ this.axiosInstance = axios.create({
432
+ baseURL: config.baseURL,
433
+ headers: config.headers,
434
+ timeout: config.timeout || 6e4
435
+ // 60 seconds default
436
+ });
437
+ this.providerName = this.extractProviderName(config.baseURL);
438
+ }
439
+ /**
440
+ * POST request with JSON payload
441
+ */
442
+ async post(endpoint, data) {
443
+ try {
444
+ const response = await this.axiosInstance.post(endpoint, data);
445
+ return response.data;
446
+ } catch (error) {
447
+ throw this.mapError(error);
448
+ }
449
+ }
450
+ /**
451
+ * GET request
452
+ */
453
+ async get(endpoint) {
454
+ try {
455
+ const response = await this.axiosInstance.get(endpoint);
456
+ return response.data;
457
+ } catch (error) {
458
+ throw this.mapError(error);
459
+ }
460
+ }
461
+ /**
462
+ * Stream request for Server-Sent Events (SSE)
463
+ * Returns async iterable of string chunks
464
+ */
465
+ async *stream(endpoint, data) {
466
+ try {
467
+ const response = await this.axiosInstance.post(endpoint, data, {
468
+ responseType: "stream",
469
+ headers: {
470
+ Accept: "text/event-stream"
471
+ }
472
+ });
473
+ const stream = response.data;
474
+ for await (const chunk of stream) {
475
+ const chunkStr = chunk.toString("utf-8");
476
+ yield chunkStr;
477
+ }
478
+ } catch (error) {
479
+ throw this.mapError(error);
480
+ }
481
+ }
482
+ /**
483
+ * Map axios errors to custom error types
484
+ */
485
+ mapError(error) {
486
+ if (!axios.isAxiosError(error)) {
487
+ return error;
488
+ }
489
+ const axiosError = error;
490
+ const status = axiosError.response?.status;
491
+ const errorData = axiosError.response?.data;
492
+ const message = errorData?.error?.message || errorData?.message || axiosError.message;
493
+ if (status === 429) {
494
+ const retryAfter = parseInt(axiosError.response?.headers["retry-after"] || "60");
495
+ return new RateLimitError(`${this.providerName} rate limit exceeded: ${message}`, retryAfter);
496
+ }
497
+ if (status === 401 || status === 403) {
498
+ return new ProviderError(
499
+ `${this.providerName} authentication failed: ${message}`,
500
+ this.providerName
501
+ );
502
+ }
503
+ if (status && status >= 500) {
504
+ return new NetworkError(`${this.providerName} server error: ${message}`, status);
505
+ }
506
+ if (status && status >= 400) {
507
+ return new ProviderError(`${this.providerName} request error: ${message}`, this.providerName);
508
+ }
509
+ if (axiosError.code === "ECONNABORTED" || axiosError.code === "ETIMEDOUT") {
510
+ return new NetworkError(`${this.providerName} request timeout: ${message}`);
511
+ }
512
+ return new ProviderError(`${this.providerName} error: ${message}`, this.providerName);
513
+ }
514
+ /**
515
+ * Extract provider name from base URL
516
+ */
517
+ extractProviderName(baseURL) {
518
+ try {
519
+ const url = new URL(baseURL);
520
+ const hostname = url.hostname;
521
+ if (hostname.includes("deepseek")) return "DeepSeek";
522
+ if (hostname.includes("anthropic")) return "Anthropic";
523
+ if (hostname.includes("openai")) return "OpenAI";
524
+ if (hostname.includes("google")) return "Google";
525
+ return hostname;
526
+ } catch {
527
+ return "Unknown Provider";
528
+ }
529
+ }
530
+ };
531
+
532
+ // src/providers/pricing/pricingData.ts
533
+ var STATIC_PRICING_TABLE = {
534
+ deepseek: {
535
+ "deepseek-chat": {
536
+ inputPerMillionTokens: 0.14,
537
+ outputPerMillionTokens: 0.28
538
+ },
539
+ "deepseek-reasoner": {
540
+ inputPerMillionTokens: 0.55,
541
+ outputPerMillionTokens: 2.19
542
+ }
543
+ },
544
+ anthropic: {
545
+ "claude-opus-4-5-20251101": {
546
+ inputPerMillionTokens: 15,
547
+ outputPerMillionTokens: 75
548
+ },
549
+ "claude-opus-4-5": {
550
+ inputPerMillionTokens: 15,
551
+ outputPerMillionTokens: 75
552
+ },
553
+ "claude-sonnet-4-5-20250929": {
554
+ inputPerMillionTokens: 3,
555
+ outputPerMillionTokens: 15
556
+ },
557
+ "claude-sonnet-4-5": {
558
+ inputPerMillionTokens: 3,
559
+ outputPerMillionTokens: 15
560
+ },
561
+ "claude-haiku-4-5": {
562
+ inputPerMillionTokens: 1,
563
+ outputPerMillionTokens: 5
564
+ },
565
+ "claude-3-7-sonnet-latest": {
566
+ inputPerMillionTokens: 3,
567
+ outputPerMillionTokens: 15
568
+ },
569
+ "claude-3-5-haiku-latest": {
570
+ inputPerMillionTokens: 1,
571
+ outputPerMillionTokens: 5
572
+ }
573
+ }
574
+ };
575
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
576
+ function getStaticPricing(provider, model) {
577
+ const providerPricing = STATIC_PRICING_TABLE[provider.toLowerCase()];
578
+ if (!providerPricing) {
579
+ return null;
580
+ }
581
+ return providerPricing[model] || null;
582
+ }
583
+
584
+ // src/providers/utils/toolFormatters.ts
585
+ function toOpenAITools(tools) {
586
+ return tools.map((tool) => ({
587
+ type: "function",
588
+ function: {
589
+ name: tool.name,
590
+ description: tool.description,
591
+ parameters: tool.schema
592
+ // Already JSON Schema
593
+ }
594
+ }));
595
+ }
596
+ function toAnthropicTools(tools) {
597
+ return tools.map((tool) => ({
598
+ name: tool.name,
599
+ description: tool.description,
600
+ input_schema: {
601
+ type: "object",
602
+ ...tool.schema
603
+ // Merge with existing schema
604
+ }
605
+ }));
606
+ }
607
+ function parseOpenAIToolCalls(response) {
608
+ const toolCalls = response.choices?.[0]?.message?.tool_calls || [];
609
+ return toolCalls.map((tc) => {
610
+ let parsedArgs;
611
+ try {
612
+ parsedArgs = JSON.parse(tc.function.arguments);
613
+ } catch {
614
+ parsedArgs = {};
615
+ }
616
+ return {
617
+ id: tc.id,
618
+ name: tc.function.name,
619
+ arguments: parsedArgs
620
+ };
621
+ });
622
+ }
623
+ function parseAnthropicToolCalls(response) {
624
+ const content = response.content || [];
625
+ return content.filter(
626
+ (block) => block.type === "tool_use"
627
+ ).map((block) => ({
628
+ id: block.id,
629
+ name: block.name,
630
+ arguments: block.input
631
+ }));
632
+ }
633
+ function mapOpenAIFinishReason(reason) {
634
+ switch (reason) {
635
+ case "stop":
636
+ return "stop";
637
+ case "tool_calls":
638
+ return "tool_calls";
639
+ case "length":
640
+ return "length";
641
+ case "content_filter":
642
+ case "insufficient_system_resource":
643
+ return "error";
644
+ default:
645
+ return "error";
646
+ }
647
+ }
648
+ function mapAnthropicFinishReason(reason) {
649
+ switch (reason) {
650
+ case "end_turn":
651
+ return "stop";
652
+ case "tool_use":
653
+ return "tool_calls";
654
+ case "max_tokens":
655
+ return "length";
656
+ case "stop_sequence":
657
+ return "stop";
658
+ default:
659
+ return "error";
660
+ }
661
+ }
662
+
663
+ // src/providers/utils/streamParsers.ts
664
+ async function* parseOpenAIStream(stream) {
665
+ let buffer = "";
666
+ for await (const chunk of stream) {
667
+ buffer += chunk;
668
+ const lines = buffer.split("\n");
669
+ buffer = lines.pop() || "";
670
+ for (const line of lines) {
671
+ const trimmed = line.trim();
672
+ if (!trimmed) continue;
673
+ if (trimmed === "data: [DONE]") {
674
+ yield { content: "", done: true };
675
+ return;
676
+ }
677
+ if (trimmed.startsWith("data: ")) {
678
+ const jsonStr = trimmed.substring(6);
679
+ try {
680
+ const data = JSON.parse(jsonStr);
681
+ const delta = data.choices?.[0]?.delta;
682
+ const content = delta?.content || "";
683
+ const finishReason = data.choices?.[0]?.finish_reason;
684
+ if (content) {
685
+ yield { content, done: false };
686
+ }
687
+ if (finishReason) {
688
+ yield { content: "", done: true };
689
+ return;
690
+ }
691
+ } catch (error) {
692
+ continue;
693
+ }
694
+ }
695
+ }
696
+ }
697
+ if (buffer.trim()) {
698
+ yield { content: "", done: true };
699
+ }
700
+ }
701
+ async function* parseAnthropicStream(stream) {
702
+ let buffer = "";
703
+ let currentEvent = "";
704
+ for await (const chunk of stream) {
705
+ buffer += chunk;
706
+ const events = buffer.split("\n\n");
707
+ buffer = events.pop() || "";
708
+ for (const event of events) {
709
+ const lines = event.split("\n");
710
+ for (const line of lines) {
711
+ const trimmed = line.trim();
712
+ if (trimmed.startsWith("event: ")) {
713
+ currentEvent = trimmed.substring(7);
714
+ } else if (trimmed.startsWith("data: ")) {
715
+ const jsonStr = trimmed.substring(6);
716
+ try {
717
+ const data = JSON.parse(jsonStr);
718
+ if (currentEvent === "content_block_delta") {
719
+ const content = data.delta?.text || "";
720
+ if (content) {
721
+ yield { content, done: false };
722
+ }
723
+ }
724
+ if (currentEvent === "content_block_start") {
725
+ const content = data.content_block?.text || "";
726
+ if (content) {
727
+ yield { content, done: false };
728
+ }
729
+ }
730
+ if (currentEvent === "message_delta") {
731
+ continue;
732
+ }
733
+ if (currentEvent === "message_stop") {
734
+ yield { content: "", done: true };
735
+ return;
736
+ }
737
+ if (currentEvent === "error") {
738
+ throw new Error(data.error?.message || "Anthropic streaming error");
739
+ }
740
+ } catch (error) {
741
+ if (currentEvent === "error") {
742
+ throw error;
743
+ }
744
+ continue;
745
+ }
746
+ }
747
+ }
748
+ }
749
+ }
750
+ if (buffer.trim()) {
751
+ yield { content: "", done: true };
752
+ }
753
+ }
754
+
755
+ // src/providers/DeepSeekProvider.ts
756
+ var DeepSeekProvider = class extends BaseLLMProvider {
757
+ apiClient;
758
+ encoder;
759
+ constructor(config) {
760
+ super(config);
761
+ const apiKey = config.apiKey || process.env.DEEPSEEK_API_KEY;
762
+ if (!apiKey) {
763
+ throw new ConfigurationError(
764
+ "DEEPSEEK_API_KEY not found in config or environment variables. Please set DEEPSEEK_API_KEY in your .env file or pass it via config."
765
+ );
766
+ }
767
+ const baseURL = config.baseURL || "https://api.deepseek.com";
768
+ this.apiClient = new APIClient({
769
+ baseURL,
770
+ headers: {
771
+ Authorization: `Bearer ${apiKey}`,
772
+ "Content-Type": "application/json"
773
+ },
774
+ timeout: 6e4
775
+ });
776
+ this.encoder = encoding_for_model("gpt-4");
777
+ }
778
+ async chat(messages, tools) {
779
+ return this.withRetry(async () => {
780
+ const requestBody = {
781
+ model: this.config.model,
782
+ messages: this.formatMessages(messages),
783
+ tools: tools ? toOpenAITools(tools) : void 0,
784
+ temperature: this.config.temperature,
785
+ max_tokens: this.config.maxTokens
786
+ };
787
+ const response = await this.apiClient.post(
788
+ "/chat/completions",
789
+ requestBody
790
+ );
791
+ return this.parseResponse(response);
792
+ });
793
+ }
794
+ async *streamChat(messages, tools) {
795
+ const requestBody = {
796
+ model: this.config.model,
797
+ messages: this.formatMessages(messages),
798
+ tools: tools ? toOpenAITools(tools) : void 0,
799
+ temperature: this.config.temperature,
800
+ max_tokens: this.config.maxTokens,
801
+ stream: true
802
+ };
803
+ const stream = this.apiClient.stream("/chat/completions", requestBody);
804
+ for await (const chunk of parseOpenAIStream(stream)) {
805
+ yield chunk;
806
+ }
807
+ }
808
+ countTokens(text) {
809
+ return this.encoder.encode(text).length;
810
+ }
811
+ calculateCost(inputTokens, outputTokens) {
812
+ const pricing = getStaticPricing("deepseek", this.config.model);
813
+ if (!pricing) {
814
+ return 0;
815
+ }
816
+ return inputTokens / 1e6 * pricing.inputPerMillionTokens + outputTokens / 1e6 * pricing.outputPerMillionTokens;
817
+ }
818
+ /**
819
+ * Format messages for OpenAI-compatible API
820
+ */
821
+ formatMessages(messages) {
822
+ return messages.map((msg) => ({
823
+ role: msg.role,
824
+ content: msg.content,
825
+ name: msg.name
826
+ }));
827
+ }
828
+ /**
829
+ * Parse DeepSeek API response
830
+ */
831
+ parseResponse(response) {
832
+ const choice = response.choices[0];
833
+ const message = choice?.message;
834
+ const usage = response.usage;
835
+ if (!choice || !message) {
836
+ throw new Error("Invalid response from DeepSeek API: missing choice or message");
837
+ }
838
+ return {
839
+ content: message.content || "",
840
+ toolCalls: parseOpenAIToolCalls(response),
841
+ finishReason: mapOpenAIFinishReason(choice.finish_reason),
842
+ usage: {
843
+ inputTokens: usage.prompt_tokens,
844
+ outputTokens: usage.completion_tokens,
845
+ totalTokens: usage.total_tokens
846
+ }
847
+ };
848
+ }
849
+ };
850
+
851
+ // src/providers/AnthropicProvider.ts
852
+ import { encoding_for_model as encoding_for_model2 } from "tiktoken";
853
+ var AnthropicProvider = class extends BaseLLMProvider {
854
+ apiClient;
855
+ encoder;
856
+ constructor(config) {
857
+ super(config);
858
+ const apiKey = config.apiKey || process.env.ANTHROPIC_API_KEY;
859
+ if (!apiKey) {
860
+ throw new ConfigurationError(
861
+ "ANTHROPIC_API_KEY not found in config or environment variables. Please set ANTHROPIC_API_KEY in your .env file or pass it via config."
862
+ );
863
+ }
864
+ const baseURL = config.baseURL || "https://api.anthropic.com";
865
+ this.apiClient = new APIClient({
866
+ baseURL,
867
+ headers: {
868
+ "x-api-key": apiKey,
869
+ "anthropic-version": "2023-06-01",
870
+ "Content-Type": "application/json"
871
+ },
872
+ timeout: 6e4
873
+ });
874
+ this.encoder = encoding_for_model2("gpt-4");
875
+ }
876
+ async chat(messages, tools) {
877
+ return this.withRetry(async () => {
878
+ const { system, messages: userMessages } = this.formatAnthropicMessages(messages);
879
+ const requestBody = {
880
+ model: this.config.model,
881
+ messages: userMessages,
882
+ max_tokens: this.config.maxTokens,
883
+ temperature: this.config.temperature
884
+ };
885
+ if (system) {
886
+ requestBody.system = system;
887
+ }
888
+ if (tools && tools.length > 0) {
889
+ requestBody.tools = toAnthropicTools(tools);
890
+ }
891
+ const response = await this.apiClient.post(
892
+ "/v1/messages",
893
+ requestBody
894
+ );
895
+ return this.parseResponse(response);
896
+ });
897
+ }
898
+ async *streamChat(messages, tools) {
899
+ const { system, messages: userMessages } = this.formatAnthropicMessages(messages);
900
+ const requestBody = {
901
+ model: this.config.model,
902
+ messages: userMessages,
903
+ max_tokens: this.config.maxTokens,
904
+ temperature: this.config.temperature,
905
+ stream: true
906
+ };
907
+ if (system) {
908
+ requestBody.system = system;
909
+ }
910
+ if (tools && tools.length > 0) {
911
+ requestBody.tools = toAnthropicTools(tools);
912
+ }
913
+ const stream = this.apiClient.stream("/v1/messages", requestBody);
914
+ for await (const chunk of parseAnthropicStream(stream)) {
915
+ yield chunk;
916
+ }
917
+ }
918
+ countTokens(text) {
919
+ return this.encoder.encode(text).length;
920
+ }
921
+ calculateCost(inputTokens, outputTokens) {
922
+ const pricing = getStaticPricing("anthropic", this.config.model);
923
+ if (!pricing) {
924
+ return 0;
925
+ }
926
+ return inputTokens / 1e6 * pricing.inputPerMillionTokens + outputTokens / 1e6 * pricing.outputPerMillionTokens;
927
+ }
928
+ /**
929
+ * Format messages for Anthropic API
930
+ * Extracts system messages into separate parameter
931
+ */
932
+ formatAnthropicMessages(messages) {
933
+ const systemMessages = messages.filter((m) => m.role === "system");
934
+ const userMessages = messages.filter((m) => m.role !== "system");
935
+ const system = systemMessages.length > 0 ? systemMessages.map((m) => m.content).join("\n\n") : void 0;
936
+ return {
937
+ system,
938
+ messages: userMessages.map((msg) => ({
939
+ role: msg.role === "assistant" ? "assistant" : "user",
940
+ content: msg.content
941
+ }))
942
+ };
943
+ }
944
+ /**
945
+ * Parse Anthropic API response
946
+ */
947
+ parseResponse(response) {
948
+ const content = response.content || [];
949
+ const usage = response.usage;
950
+ const textContent = content.filter((block) => block.type === "text").map((block) => block.text).join("");
951
+ return {
952
+ content: textContent,
953
+ toolCalls: parseAnthropicToolCalls(response),
954
+ finishReason: mapAnthropicFinishReason(response.stop_reason),
955
+ usage: {
956
+ inputTokens: usage.input_tokens,
957
+ outputTokens: usage.output_tokens,
958
+ totalTokens: usage.input_tokens + usage.output_tokens
959
+ }
960
+ };
961
+ }
962
+ };
963
+
964
+ // src/providers/ProviderFactory.ts
965
+ var ProviderFactory = class {
966
+ static create(config) {
967
+ switch (config.provider.toLowerCase()) {
968
+ case "deepseek":
969
+ return new DeepSeekProvider(config);
970
+ case "anthropic":
971
+ return new AnthropicProvider(config);
972
+ case "openai":
973
+ throw new Error(
974
+ "OpenAI provider coming soon - see roadmap Phase 3. In the meantime, you can use DeepSeek which is OpenAI-compatible."
975
+ );
976
+ case "google":
977
+ case "gemini":
978
+ throw new Error("Google/Gemini provider coming soon - see roadmap Phase 3.");
979
+ case "qwen":
980
+ throw new Error(
981
+ "Qwen provider coming soon - see roadmap Phase 3. In the meantime, you can use DeepSeek which is similar."
982
+ );
983
+ case "ollama":
984
+ throw new Error("Ollama provider coming soon - see roadmap Phase 3.");
985
+ default:
986
+ throw new Error(`Unsupported provider: ${config.provider}`);
987
+ }
988
+ }
989
+ };
990
+
991
+ // src/config/schemas.ts
992
+ import { z } from "zod";
993
+ var ThemeSchema = z.enum([
994
+ "mimir",
995
+ "dark",
996
+ "light",
997
+ "dark-colorblind",
998
+ "light-colorblind",
999
+ "dark-ansi",
1000
+ "light-ansi"
1001
+ ]);
1002
+ var UIConfigSchema = z.object({
1003
+ theme: ThemeSchema.default("mimir"),
1004
+ syntaxHighlighting: z.boolean().default(true),
1005
+ showLineNumbers: z.boolean().default(true),
1006
+ compactMode: z.boolean().default(false),
1007
+ // Autocomplete behavior
1008
+ autocompleteAutoShow: z.boolean().default(true),
1009
+ // Automatically show autocomplete when suggestions available
1010
+ autocompleteExecuteOnSelect: z.boolean().default(true)
1011
+ // Execute command immediately if no more parameters needed
1012
+ });
1013
+ var LLMConfigSchema = z.object({
1014
+ provider: z.enum(["deepseek", "anthropic", "openai", "google", "gemini", "qwen", "ollama"]),
1015
+ model: z.string(),
1016
+ apiKey: z.string().optional(),
1017
+ baseURL: z.string().optional(),
1018
+ temperature: z.number().min(0).max(2).default(0.7),
1019
+ maxTokens: z.number().default(4096)
1020
+ });
1021
+ var PermissionsConfigSchema = z.object({
1022
+ autoAccept: z.boolean().default(false),
1023
+ acceptRiskLevel: z.enum(["low", "medium", "high", "critical"]).default("medium"),
1024
+ alwaysAcceptCommands: z.array(z.string()).nullable().default([]).transform((val) => val ?? [])
1025
+ });
1026
+ var shortcutSchema = z.union([z.string(), z.array(z.string()).min(1)]).transform((val) => Array.isArray(val) ? val : [val]);
1027
+ var KeyBindingsConfigSchema = z.object({
1028
+ // Core actions - Ctrl+C and Escape share the same 'interrupt' logic
1029
+ interrupt: shortcutSchema.default(["Ctrl+C", "Escape"]),
1030
+ accept: shortcutSchema.default(["Enter"]),
1031
+ // Mode and navigation
1032
+ modeSwitch: shortcutSchema.default(["Shift+Tab"]),
1033
+ editCommand: shortcutSchema.default(["Ctrl+E"]),
1034
+ // Autocomplete/tooltips
1035
+ showTooltip: shortcutSchema.default(["Ctrl+Space", "Tab"]),
1036
+ navigateUp: shortcutSchema.default(["ArrowUp"]),
1037
+ navigateDown: shortcutSchema.default(["ArrowDown"]),
1038
+ // Utility
1039
+ help: shortcutSchema.default(["?"]),
1040
+ clearScreen: shortcutSchema.default(["Ctrl+L"]),
1041
+ undo: shortcutSchema.default(["Ctrl+Z"]),
1042
+ redo: shortcutSchema.default(["Ctrl+Y"]),
1043
+ // Auto-converted to Cmd+Shift+Z on Mac
1044
+ // Legacy/deprecated - kept for backwards compatibility
1045
+ reject: shortcutSchema.default([]).optional()
1046
+ });
1047
+ var DockerConfigSchema = z.object({
1048
+ enabled: z.boolean().default(true),
1049
+ baseImage: z.string().default("alpine:latest"),
1050
+ cpuLimit: z.number().optional(),
1051
+ memoryLimit: z.string().optional()
1052
+ });
1053
+ var MonitoringConfigSchema = z.object({
1054
+ metricsRetentionDays: z.number().min(1).max(365).default(90),
1055
+ enableHealthChecks: z.boolean().default(true),
1056
+ healthCheckIntervalSeconds: z.number().min(10).max(3600).default(300),
1057
+ slowOperationThresholdMs: z.number().min(100).default(5e3),
1058
+ batchWriteIntervalSeconds: z.number().min(1).max(60).default(10)
1059
+ });
1060
+ var BudgetConfigSchema = z.object({
1061
+ enabled: z.boolean().default(false),
1062
+ dailyLimit: z.number().min(0).optional(),
1063
+ // USD
1064
+ weeklyLimit: z.number().min(0).optional(),
1065
+ monthlyLimit: z.number().min(0).optional(),
1066
+ warningThreshold: z.number().min(0).max(1).default(0.8)
1067
+ // 80%
1068
+ });
1069
+ var RateLimitConfigSchema = z.object({
1070
+ enabled: z.boolean().default(true),
1071
+ commandsPerMinute: z.number().min(1).default(60),
1072
+ toolExecutionsPerMinute: z.number().min(1).default(30),
1073
+ llmCallsPerMinute: z.number().min(1).default(20),
1074
+ maxFileSizeMB: z.number().min(1).default(100)
1075
+ });
1076
+ var ConfigSchema = z.object({
1077
+ llm: LLMConfigSchema,
1078
+ permissions: PermissionsConfigSchema,
1079
+ keyBindings: KeyBindingsConfigSchema,
1080
+ docker: DockerConfigSchema,
1081
+ ui: UIConfigSchema,
1082
+ monitoring: MonitoringConfigSchema,
1083
+ budget: BudgetConfigSchema,
1084
+ rateLimit: RateLimitConfigSchema
1085
+ });
1086
+
1087
+ // src/utils/logger.ts
1088
+ import winston from "winston";
1089
+ import DailyRotateFile from "winston-daily-rotate-file";
1090
+ import fs from "fs";
1091
+ import path from "path";
1092
+ var Logger = class {
1093
+ logger;
1094
+ fileLoggingEnabled = false;
1095
+ consoleTransport;
1096
+ constructor(logDir = ".mimir/logs") {
1097
+ const absoluteLogDir = path.resolve(process.cwd(), logDir);
1098
+ try {
1099
+ if (!fs.existsSync(absoluteLogDir)) {
1100
+ fs.mkdirSync(absoluteLogDir, { recursive: true });
1101
+ }
1102
+ this.fileLoggingEnabled = true;
1103
+ } catch (error) {
1104
+ console.warn(
1105
+ `[Logger] Warning: Failed to create log directory at ${absoluteLogDir}. File logging disabled. Error: ${error}`
1106
+ );
1107
+ this.fileLoggingEnabled = false;
1108
+ }
1109
+ this.consoleTransport = new winston.transports.Console({
1110
+ format: winston.format.combine(winston.format.colorize(), winston.format.simple())
1111
+ });
1112
+ const transports = [this.consoleTransport];
1113
+ if (this.fileLoggingEnabled) {
1114
+ transports.push(
1115
+ // Error logs with daily rotation
1116
+ new DailyRotateFile({
1117
+ dirname: absoluteLogDir,
1118
+ filename: "%DATE%-error.log",
1119
+ datePattern: "YYYYMMDD",
1120
+ level: "error",
1121
+ maxSize: "10m",
1122
+ // Rotate when file reaches 10MB
1123
+ maxFiles: "30d",
1124
+ // Keep logs for 30 days
1125
+ zippedArchive: true
1126
+ // Compress old logs
1127
+ }),
1128
+ // Combined logs with daily rotation
1129
+ new DailyRotateFile({
1130
+ dirname: absoluteLogDir,
1131
+ filename: "%DATE%.log",
1132
+ datePattern: "YYYYMMDD",
1133
+ maxSize: "10m",
1134
+ // Rotate when file reaches 10MB
1135
+ maxFiles: "30d",
1136
+ // Keep logs for 30 days
1137
+ zippedArchive: true
1138
+ // Compress old logs
1139
+ })
1140
+ );
1141
+ }
1142
+ this.logger = winston.createLogger({
1143
+ level: process.env.LOG_LEVEL || "info",
1144
+ format: winston.format.combine(
1145
+ winston.format.timestamp(),
1146
+ winston.format.errors({ stack: true }),
1147
+ winston.format.json()
1148
+ ),
1149
+ transports
1150
+ });
1151
+ }
1152
+ error(message, meta) {
1153
+ this.logger.error(message, meta);
1154
+ }
1155
+ warn(message, meta) {
1156
+ this.logger.warn(message, meta);
1157
+ }
1158
+ info(message, meta) {
1159
+ this.logger.info(message, meta);
1160
+ }
1161
+ debug(message, meta) {
1162
+ this.logger.debug(message, meta);
1163
+ }
1164
+ /**
1165
+ * Disable console logging (useful when Ink UI is active)
1166
+ */
1167
+ disableConsole() {
1168
+ this.logger.remove(this.consoleTransport);
1169
+ }
1170
+ /**
1171
+ * Enable console logging
1172
+ */
1173
+ enableConsole() {
1174
+ if (!this.logger.transports.includes(this.consoleTransport)) {
1175
+ this.logger.add(this.consoleTransport);
1176
+ }
1177
+ }
1178
+ };
1179
+ var logger = new Logger();
1180
+
1181
+ // src/config/AllowlistLoader.ts
1182
+ import yaml from "yaml";
1183
+ import path2 from "path";
1184
+ import { z as z2 } from "zod";
1185
+ var AllowlistSchema = z2.object({
1186
+ // Command patterns that are always allowed
1187
+ commands: z2.array(z2.string()).default([]),
1188
+ // File patterns that can be modified without confirmation
1189
+ files: z2.array(z2.string()).default([]),
1190
+ // Network destinations that are allowed
1191
+ urls: z2.array(z2.string()).default([]),
1192
+ // Environment variables that can be accessed
1193
+ envVars: z2.array(z2.string()).default([]),
1194
+ // Specific bash commands that are safe
1195
+ bashCommands: z2.array(z2.string()).default([])
1196
+ });
1197
+ var AllowlistLoader = class {
1198
+ constructor(fs3) {
1199
+ this.fs = fs3;
1200
+ }
1201
+ /**
1202
+ * Load allowlist from project .mimir/allowlist.yml
1203
+ */
1204
+ async loadProjectAllowlist(projectRoot) {
1205
+ const allowlistPath = path2.join(projectRoot, ".mimir", "allowlist.yml");
1206
+ return await this.loadAllowlistFile(allowlistPath, "project");
1207
+ }
1208
+ /**
1209
+ * Load allowlist from global ~/.mimir/allowlist.yml
1210
+ */
1211
+ async loadGlobalAllowlist() {
1212
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "~";
1213
+ const allowlistPath = path2.join(homeDir, ".mimir", "allowlist.yml");
1214
+ return await this.loadAllowlistFile(allowlistPath, "global");
1215
+ }
1216
+ /**
1217
+ * Load and parse allowlist file
1218
+ */
1219
+ async loadAllowlistFile(filePath, scope) {
1220
+ try {
1221
+ if (!await this.fs.exists(filePath)) {
1222
+ logger.debug(`No ${scope} allowlist found`, { path: filePath });
1223
+ return null;
1224
+ }
1225
+ const content = await this.fs.readFile(filePath);
1226
+ const parsed = yaml.parse(content);
1227
+ const allowlist = AllowlistSchema.parse(parsed);
1228
+ logger.info(`Loaded ${scope} allowlist`, {
1229
+ path: filePath,
1230
+ commands: allowlist.commands.length,
1231
+ files: allowlist.files.length,
1232
+ urls: allowlist.urls.length
1233
+ });
1234
+ return allowlist;
1235
+ } catch (error) {
1236
+ logger.error(`Failed to load ${scope} allowlist`, {
1237
+ path: filePath,
1238
+ error
1239
+ });
1240
+ return null;
1241
+ }
1242
+ }
1243
+ /**
1244
+ * Merge multiple allowlists (global + project)
1245
+ * Project allowlist takes precedence
1246
+ */
1247
+ merge(global, project) {
1248
+ const merged = {
1249
+ commands: [],
1250
+ files: [],
1251
+ urls: [],
1252
+ envVars: [],
1253
+ bashCommands: []
1254
+ };
1255
+ if (global) {
1256
+ merged.commands.push(...global.commands);
1257
+ merged.files.push(...global.files);
1258
+ merged.urls.push(...global.urls);
1259
+ merged.envVars.push(...global.envVars);
1260
+ merged.bashCommands.push(...global.bashCommands);
1261
+ }
1262
+ if (project) {
1263
+ merged.commands = [.../* @__PURE__ */ new Set([...merged.commands, ...project.commands])];
1264
+ merged.files = [.../* @__PURE__ */ new Set([...merged.files, ...project.files])];
1265
+ merged.urls = [.../* @__PURE__ */ new Set([...merged.urls, ...project.urls])];
1266
+ merged.envVars = [.../* @__PURE__ */ new Set([...merged.envVars, ...project.envVars])];
1267
+ merged.bashCommands = [.../* @__PURE__ */ new Set([...merged.bashCommands, ...project.bashCommands])];
1268
+ }
1269
+ return merged;
1270
+ }
1271
+ /**
1272
+ * Create example allowlist file
1273
+ */
1274
+ async createExample(filePath, scope) {
1275
+ const exampleContent = scope === "global" ? this.getGlobalExample() : this.getProjectExample();
1276
+ try {
1277
+ const dir = path2.dirname(filePath);
1278
+ if (!await this.fs.exists(dir)) {
1279
+ await this.fs.mkdir(dir, { recursive: true });
1280
+ }
1281
+ await this.fs.writeFile(filePath, exampleContent);
1282
+ logger.info(`Created example ${scope} allowlist`, { path: filePath });
1283
+ } catch (error) {
1284
+ logger.error(`Failed to create example allowlist`, {
1285
+ path: filePath,
1286
+ error
1287
+ });
1288
+ throw error;
1289
+ }
1290
+ }
1291
+ /**
1292
+ * Get example global allowlist
1293
+ */
1294
+ getGlobalExample() {
1295
+ return `# Global Allowlist
1296
+ # Commands, files, and operations that are safe across all projects
1297
+
1298
+ # Commands that don't require permission prompt
1299
+ commands:
1300
+ - '/status' # Git status
1301
+ - '/diff' # Git diff
1302
+ - '/help' # Show help
1303
+ - '/version' # Show version
1304
+ - '/doctor' # System diagnostics
1305
+
1306
+ # Safe bash commands
1307
+ bashCommands:
1308
+ - 'git status'
1309
+ - 'git diff'
1310
+ - 'git log'
1311
+ - 'ls'
1312
+ - 'pwd'
1313
+ - 'echo *'
1314
+ - 'cat *.md'
1315
+
1316
+ # Files that can be modified without confirmation (use globs)
1317
+ files:
1318
+ - '**/*.md' # Documentation files
1319
+ - '**/*.txt' # Text files
1320
+ - '**/README.*' # README files
1321
+
1322
+ # URLs that can be accessed without confirmation
1323
+ urls:
1324
+ - 'https://api.github.com/**'
1325
+ - 'https://registry.npmjs.org/**'
1326
+
1327
+ # Environment variables that can be read
1328
+ envVars:
1329
+ - 'NODE_ENV'
1330
+ - 'PATH'
1331
+ - 'USER'
1332
+ - 'HOME'
1333
+ `;
1334
+ }
1335
+ /**
1336
+ * Get example project allowlist
1337
+ */
1338
+ getProjectExample() {
1339
+ return `# Project Allowlist
1340
+ # Team-shared permissions for this project
1341
+ # Commit this file to version control for consistent team experience
1342
+
1343
+ # Custom slash commands that are safe to run
1344
+ commands:
1345
+ - '/test' # Run test suite
1346
+ - '/lint' # Run linter
1347
+ - '/build' # Build project
1348
+ - '/test-coverage' # Run tests with coverage
1349
+
1350
+ # Safe bash commands specific to this project
1351
+ bashCommands:
1352
+ - 'yarn test'
1353
+ - 'yarn lint'
1354
+ - 'yarn build'
1355
+ - 'npm run test'
1356
+ - 'npm run lint'
1357
+
1358
+ # Files that can be auto-formatted or modified
1359
+ files:
1360
+ - 'src/**/*.ts' # TypeScript source files
1361
+ - 'src/**/*.tsx' # React TypeScript files
1362
+ - 'tests/**/*.ts' # Test files
1363
+ - '*.json' # JSON config files
1364
+ - '.prettierrc.*' # Prettier config
1365
+ - '.eslintrc.*' # ESLint config
1366
+
1367
+ # API endpoints used by this project
1368
+ urls:
1369
+ - 'https://api.example.com/**'
1370
+ - 'https://staging.example.com/**'
1371
+
1372
+ # Environment variables specific to this project
1373
+ envVars:
1374
+ - 'API_KEY'
1375
+ - 'DATABASE_URL'
1376
+ - 'REDIS_URL'
1377
+ `;
1378
+ }
1379
+ };
1380
+
1381
+ // src/config/ConfigLoader.ts
1382
+ import yaml2 from "yaml";
1383
+ import path3 from "path";
1384
+ import os from "os";
1385
+ import dotenv from "dotenv";
1386
+ var ConfigLoader = class {
1387
+ constructor(fs3) {
1388
+ this.fs = fs3;
1389
+ this.allowlistLoader = new AllowlistLoader(fs3);
1390
+ }
1391
+ allowlistLoader;
1392
+ /**
1393
+ * Load configuration with full hierarchy:
1394
+ * 1. Default config
1395
+ * 2. Global (~/.mimir/config.yml)
1396
+ * 3. Project (.mimir/config.yml)
1397
+ * 4. Environment variables (.env)
1398
+ * 5. CLI flags
1399
+ *
1400
+ * Also loads allowlist from:
1401
+ * 1. Global (~/.mimir/allowlist.yml)
1402
+ * 2. Project (.mimir/allowlist.yml)
1403
+ */
1404
+ async load(options = {}) {
1405
+ let config = this.getDefaults();
1406
+ const globalConfig = await this.loadGlobalConfig();
1407
+ if (globalConfig) {
1408
+ config = this.merge(config, globalConfig);
1409
+ }
1410
+ if (options.projectRoot) {
1411
+ const projectConfig = await this.loadProjectConfig(options.projectRoot);
1412
+ if (projectConfig) {
1413
+ config = this.merge(config, projectConfig);
1414
+ }
1415
+ }
1416
+ const envConfig = this.loadEnvConfig(options.projectRoot);
1417
+ if (envConfig) {
1418
+ config = this.merge(config, envConfig);
1419
+ }
1420
+ if (options.cliFlags) {
1421
+ config = this.merge(config, options.cliFlags);
1422
+ }
1423
+ const validatedConfig = ConfigSchema.parse(config);
1424
+ const globalAllowlist = await this.allowlistLoader.loadGlobalAllowlist();
1425
+ const projectAllowlist = options.projectRoot ? await this.allowlistLoader.loadProjectAllowlist(options.projectRoot) : null;
1426
+ const mergedAllowlist = this.allowlistLoader.merge(globalAllowlist, projectAllowlist);
1427
+ if (mergedAllowlist.commands.length > 0) {
1428
+ validatedConfig.permissions.alwaysAcceptCommands = [
1429
+ .../* @__PURE__ */ new Set([
1430
+ ...validatedConfig.permissions.alwaysAcceptCommands,
1431
+ ...mergedAllowlist.commands
1432
+ ])
1433
+ ];
1434
+ }
1435
+ return {
1436
+ config: validatedConfig,
1437
+ allowlist: mergedAllowlist
1438
+ };
1439
+ }
1440
+ getDefaults() {
1441
+ const isWindows = process.platform === "win32";
1442
+ return {
1443
+ llm: {
1444
+ provider: "deepseek",
1445
+ model: "deepseek-chat",
1446
+ temperature: 0.7,
1447
+ maxTokens: 4096
1448
+ },
1449
+ permissions: {
1450
+ autoAccept: false,
1451
+ acceptRiskLevel: "medium",
1452
+ alwaysAcceptCommands: []
1453
+ },
1454
+ keyBindings: {
1455
+ interrupt: ["Ctrl+C", "Escape"],
1456
+ accept: ["Enter"],
1457
+ modeSwitch: ["Shift+Tab"],
1458
+ editCommand: ["Ctrl+E"],
1459
+ // Windows: Ctrl+Space is intercepted by terminal - use Tab only
1460
+ // macOS/Linux: Both work
1461
+ showTooltip: isWindows ? ["Tab"] : ["Ctrl+Space", "Tab"],
1462
+ navigateUp: ["ArrowUp"],
1463
+ navigateDown: ["ArrowDown"],
1464
+ help: ["?"],
1465
+ clearScreen: ["Ctrl+L"],
1466
+ undo: ["Ctrl+Z"],
1467
+ redo: ["Ctrl+Y"]
1468
+ },
1469
+ docker: {
1470
+ enabled: true,
1471
+ baseImage: "alpine:latest"
1472
+ },
1473
+ ui: {
1474
+ theme: "mimir",
1475
+ syntaxHighlighting: true,
1476
+ showLineNumbers: true,
1477
+ compactMode: false,
1478
+ autocompleteAutoShow: true,
1479
+ autocompleteExecuteOnSelect: true
1480
+ },
1481
+ monitoring: {
1482
+ metricsRetentionDays: 90,
1483
+ enableHealthChecks: true,
1484
+ healthCheckIntervalSeconds: 300,
1485
+ slowOperationThresholdMs: 5e3,
1486
+ batchWriteIntervalSeconds: 10
1487
+ },
1488
+ budget: {
1489
+ enabled: false,
1490
+ warningThreshold: 0.8
1491
+ },
1492
+ rateLimit: {
1493
+ enabled: true,
1494
+ commandsPerMinute: 60,
1495
+ toolExecutionsPerMinute: 30,
1496
+ llmCallsPerMinute: 20,
1497
+ maxFileSizeMB: 100
1498
+ }
1499
+ };
1500
+ }
1501
+ async loadGlobalConfig() {
1502
+ try {
1503
+ const configPath = path3.join(os.homedir(), ".mimir", "config.yml");
1504
+ if (!await this.fs.exists(configPath)) {
1505
+ return null;
1506
+ }
1507
+ const content = await this.fs.readFile(configPath);
1508
+ return yaml2.parse(content);
1509
+ } catch (error) {
1510
+ logger.warn("Failed to load global config", { error });
1511
+ return null;
1512
+ }
1513
+ }
1514
+ async loadProjectConfig(projectRoot) {
1515
+ try {
1516
+ const configPath = path3.join(projectRoot, ".mimir", "config.yml");
1517
+ if (!await this.fs.exists(configPath)) {
1518
+ return null;
1519
+ }
1520
+ const content = await this.fs.readFile(configPath);
1521
+ return yaml2.parse(content);
1522
+ } catch (error) {
1523
+ logger.warn("Failed to load project config", { error });
1524
+ return null;
1525
+ }
1526
+ }
1527
+ loadEnvConfig(projectRoot) {
1528
+ try {
1529
+ const envPath = projectRoot ? path3.join(projectRoot, ".env") : ".env";
1530
+ dotenv.config({ path: envPath });
1531
+ const envConfig = {};
1532
+ if (process.env.DEEPSEEK_API_KEY || process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY) {
1533
+ const provider = process.env.LLM_PROVIDER?.toUpperCase();
1534
+ const apiKey = provider ? process.env[`${provider}_API_KEY`] : void 0;
1535
+ const baseURL = provider ? process.env[`${provider}_BASE_URL`] : void 0;
1536
+ if (apiKey || baseURL) {
1537
+ envConfig.llm = {
1538
+ ...apiKey && { apiKey },
1539
+ ...baseURL && { baseURL }
1540
+ };
1541
+ }
1542
+ }
1543
+ if (process.env.DOCKER_ENABLED) {
1544
+ envConfig.docker = {
1545
+ enabled: process.env.DOCKER_ENABLED === "true"
1546
+ };
1547
+ }
1548
+ if (process.env.MIMIR_THEME) {
1549
+ envConfig.ui = {
1550
+ theme: process.env.MIMIR_THEME
1551
+ };
1552
+ }
1553
+ return Object.keys(envConfig).length > 0 ? envConfig : null;
1554
+ } catch (error) {
1555
+ logger.warn("Failed to load .env config", { error });
1556
+ return null;
1557
+ }
1558
+ }
1559
+ merge(base, override) {
1560
+ return {
1561
+ llm: { ...base.llm, ...override.llm },
1562
+ permissions: { ...base.permissions, ...override.permissions },
1563
+ keyBindings: { ...base.keyBindings, ...override.keyBindings },
1564
+ docker: { ...base.docker, ...override.docker },
1565
+ ui: { ...base.ui, ...override.ui },
1566
+ monitoring: { ...base.monitoring, ...override.monitoring },
1567
+ budget: { ...base.budget, ...override.budget },
1568
+ rateLimit: { ...base.rateLimit, ...override.rateLimit }
1569
+ };
1570
+ }
1571
+ async save(config, scope, projectRoot) {
1572
+ const configPath = scope === "global" ? path3.join(os.homedir(), ".mimir", "config.yml") : path3.join(projectRoot || process.cwd(), ".mimir", "config.yml");
1573
+ const configDir = path3.dirname(configPath);
1574
+ if (!await this.fs.exists(configDir)) {
1575
+ await this.fs.mkdir(configDir, { recursive: true });
1576
+ }
1577
+ const yamlContent = yaml2.stringify(config);
1578
+ await this.fs.writeFile(configPath, yamlContent);
1579
+ logger.info(`Config saved to ${configPath}`);
1580
+ }
1581
+ validate(config) {
1582
+ return ConfigSchema.parse(config);
1583
+ }
1584
+ };
1585
+
1586
+ // src/platform/FileSystemAdapter.ts
1587
+ import fs2 from "fs/promises";
1588
+ import { globby } from "globby";
1589
+ var FileSystemAdapter = class {
1590
+ async readFile(path4, encoding = "utf-8") {
1591
+ return fs2.readFile(path4, encoding);
1592
+ }
1593
+ async writeFile(path4, content, encoding = "utf-8") {
1594
+ await fs2.writeFile(path4, content, encoding);
1595
+ }
1596
+ async exists(path4) {
1597
+ try {
1598
+ await fs2.access(path4);
1599
+ return true;
1600
+ } catch {
1601
+ return false;
1602
+ }
1603
+ }
1604
+ async mkdir(path4, options) {
1605
+ await fs2.mkdir(path4, options);
1606
+ }
1607
+ async readdir(path4) {
1608
+ return fs2.readdir(path4);
1609
+ }
1610
+ async stat(path4) {
1611
+ const stats = await fs2.stat(path4);
1612
+ return {
1613
+ isFile: () => stats.isFile(),
1614
+ isDirectory: () => stats.isDirectory(),
1615
+ size: stats.size,
1616
+ mtime: stats.mtime
1617
+ };
1618
+ }
1619
+ async unlink(path4) {
1620
+ await fs2.unlink(path4);
1621
+ }
1622
+ async rmdir(path4, options) {
1623
+ await fs2.rmdir(path4, options);
1624
+ }
1625
+ async copyFile(src, dest) {
1626
+ await fs2.copyFile(src, dest);
1627
+ }
1628
+ async glob(pattern, options) {
1629
+ return globby(pattern, {
1630
+ cwd: options?.cwd,
1631
+ ignore: options?.ignore
1632
+ });
1633
+ }
1634
+ };
1635
+ export {
1636
+ Agent,
1637
+ BaseLLMProvider,
1638
+ ConfigLoader,
1639
+ ConfigurationError,
1640
+ DockerError,
1641
+ FileSystemAdapter,
1642
+ Logger,
1643
+ MimirError,
1644
+ NetworkError,
1645
+ PermissionDeniedError,
1646
+ PermissionManager,
1647
+ ProviderError,
1648
+ ProviderFactory,
1649
+ RateLimitError,
1650
+ ToolExecutionError,
1651
+ ToolRegistry,
1652
+ createErr,
1653
+ createOk,
1654
+ logger
1655
+ };
1656
+ //# sourceMappingURL=index.js.map