@cosmicstack/mercury-agent 1.0.0 → 1.0.1

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 CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync13, existsSync as existsSync19 } from "fs";
4
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync14, existsSync as existsSync19 } from "fs";
5
5
  import { fileURLToPath } from "url";
6
- import { dirname as dirname4, join as join12 } from "path";
6
+ import { dirname as dirname4, join as join14 } from "path";
7
7
  import { Command } from "commander";
8
8
  import readline3 from "readline";
9
9
  import chalk7 from "chalk";
@@ -599,7 +599,7 @@ var LongTermMemory = class {
599
599
  }
600
600
  load() {
601
601
  if (!existsSync3(this.filepath)) return;
602
- const lines = readFileSync3(this.filepath, "utf-8").split("\n").filter(Boolean);
602
+ const lines = readFileSync3(this.filepath, "utf-8").split(/\r?\n/).filter(Boolean);
603
603
  this.facts = lines.map((line) => {
604
604
  try {
605
605
  return JSON.parse(line);
@@ -641,7 +641,7 @@ var EpisodicMemory = class {
641
641
  }
642
642
  load() {
643
643
  if (!existsSync3(this.filepath)) return;
644
- const lines = readFileSync3(this.filepath, "utf-8").split("\n").filter(Boolean);
644
+ const lines = readFileSync3(this.filepath, "utf-8").split(/\r?\n/).filter(Boolean);
645
645
  this.events = lines.map((line) => {
646
646
  try {
647
647
  return JSON.parse(line);
@@ -656,17 +656,43 @@ function generateId() {
656
656
  }
657
657
 
658
658
  // src/memory/second-brain-db.ts
659
- import Database from "better-sqlite3";
660
- import { existsSync as existsSync4, mkdirSync as mkdirSync4 } from "fs";
661
- import { dirname } from "path";
659
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, rmSync as rmSync2 } from "fs";
660
+ import { dirname, join as join4 } from "path";
661
+ import { tmpdir } from "os";
662
+ import { createRequire } from "module";
663
+ var require2 = createRequire(import.meta.url);
664
+ var syncDatabaseClass = null;
665
+ try {
666
+ const mod = require2("better-sqlite3");
667
+ const probeDir = join4(tmpdir(), `mercury-sqlite3-probe-${process.pid}`);
668
+ try {
669
+ mkdirSync4(probeDir, { recursive: true });
670
+ const probeDb = new mod(join4(probeDir, "probe.db"));
671
+ probeDb.close();
672
+ rmSync2(probeDir, { recursive: true, force: true });
673
+ syncDatabaseClass = mod;
674
+ } catch {
675
+ syncDatabaseClass = null;
676
+ }
677
+ } catch {
678
+ syncDatabaseClass = null;
679
+ }
680
+ function isBetterSqlite3Available() {
681
+ return syncDatabaseClass !== null;
682
+ }
662
683
  var SecondBrainDB = class {
663
684
  db;
664
685
  constructor(dbPath) {
686
+ if (!syncDatabaseClass) {
687
+ throw new Error(
688
+ "better-sqlite3 is not available \u2014 second brain memory requires it. Install build tools (make, gcc/g++, python3) or upgrade to Node >= 20. See: https://github.com/WiseLibs/better-sqlite3/blob/master/docs/compilation.md"
689
+ );
690
+ }
665
691
  const dir = dirname(dbPath);
666
692
  if (!existsSync4(dir)) {
667
693
  mkdirSync4(dir, { recursive: true });
668
694
  }
669
- this.db = new Database(dbPath);
695
+ this.db = new syncDatabaseClass(dbPath);
670
696
  this.db.pragma("journal_mode = WAL");
671
697
  this.db.pragma("synchronous = NORMAL");
672
698
  }
@@ -954,7 +980,7 @@ var SecondBrainDB = class {
954
980
  };
955
981
 
956
982
  // src/memory/user-memory.ts
957
- import { join as join4 } from "path";
983
+ import { join as join5 } from "path";
958
984
  var MIN_CONFIDENCE = 0.55;
959
985
  var UserMemoryStore = class {
960
986
  db;
@@ -966,7 +992,7 @@ var UserMemoryStore = class {
966
992
  this.userKey = userKey;
967
993
  this.maxRecords = config.memory.secondBrain?.maxRecords ?? 50;
968
994
  this.consolidateThrottleMs = 5 * 60 * 1e3;
969
- const resolvedDbPath = dbPath ?? join4(getMemoryDir(), "second-brain", "second-brain.db");
995
+ const resolvedDbPath = dbPath ?? join5(getMemoryDir(), "second-brain", "second-brain.db");
970
996
  this.db = new SecondBrainDB(resolvedDbPath);
971
997
  this.db.init();
972
998
  }
@@ -3411,31 +3437,52 @@ var ToolCallLoopDetector = class _ToolCallLoopDetector {
3411
3437
  recentCalls = [];
3412
3438
  totalCalls = 0;
3413
3439
  hardAborted = false;
3440
+ recentStepTexts = [];
3414
3441
  static ABSOLUTE_MAX = 25;
3442
+ static FAILED_ABSOLUTE_MAX = 12;
3415
3443
  static HIGH_TOLERANCE_TOOLS = /* @__PURE__ */ new Set([
3416
3444
  "fetch_url",
3417
3445
  "read_file",
3418
3446
  "list_dir",
3419
3447
  "web_search",
3420
- "github_api",
3421
- "run_command"
3448
+ "github_api"
3422
3449
  ]);
3423
- static getIdenticalThreshold() {
3424
- return 3;
3425
- }
3426
- static getSameToolThreshold(toolName) {
3427
- return _ToolCallLoopDetector.HIGH_TOLERANCE_TOOLS.has(toolName) ? 8 : 4;
3428
- }
3429
- record(toolName, params) {
3450
+ static IDENTICAL_THRESHOLD = 3;
3451
+ static SIMILAR_THRESHOLD = 4;
3452
+ static TEXT_REPEAT_THRESHOLD = 3;
3453
+ static MAX_STEP_TEXTS = 12;
3454
+ static getSameToolThreshold(toolName, failingCount) {
3455
+ const baseHigh = 5;
3456
+ const baseNormal = 3;
3457
+ const isHigh = _ToolCallLoopDetector.HIGH_TOLERANCE_TOOLS.has(toolName);
3458
+ let threshold = isHigh ? baseHigh : baseNormal;
3459
+ if (failingCount >= 3) {
3460
+ threshold = Math.min(threshold, isHigh ? 3 : 2);
3461
+ }
3462
+ return threshold;
3463
+ }
3464
+ record(toolName, params, failed = false) {
3430
3465
  const paramsKey = JSON.stringify(params).slice(0, 200);
3431
- this.recentCalls.push({ tool: toolName, params: paramsKey });
3466
+ this.recentCalls.push({ tool: toolName, params: paramsKey, failed });
3432
3467
  this.totalCalls++;
3433
3468
  if (this.recentCalls.length > 30) {
3434
3469
  this.recentCalls.shift();
3435
3470
  }
3436
3471
  }
3472
+ recordStepText(text) {
3473
+ if (!text || text.length < 10) return;
3474
+ const normalized = text.toLowerCase().replace(/\s+/g, " ").trim().slice(0, 200);
3475
+ if (!normalized) return;
3476
+ this.recentStepTexts.push(normalized);
3477
+ if (this.recentStepTexts.length > _ToolCallLoopDetector.MAX_STEP_TEXTS) {
3478
+ this.recentStepTexts.shift();
3479
+ }
3480
+ }
3437
3481
  detectAbsoluteLimit() {
3438
- return this.totalCalls >= _ToolCallLoopDetector.ABSOLUTE_MAX;
3482
+ if (this.totalCalls >= _ToolCallLoopDetector.ABSOLUTE_MAX) return true;
3483
+ const failCount = this.recentCalls.filter((c) => c.failed).length;
3484
+ if (failCount >= _ToolCallLoopDetector.FAILED_ABSOLUTE_MAX) return true;
3485
+ return false;
3439
3486
  }
3440
3487
  detectIdentical() {
3441
3488
  if (this.recentCalls.length < 3) return null;
@@ -3448,7 +3495,7 @@ var ToolCallLoopDetector = class _ToolCallLoopDetector {
3448
3495
  break;
3449
3496
  }
3450
3497
  }
3451
- if (identicalCount >= _ToolCallLoopDetector.getIdenticalThreshold()) {
3498
+ if (identicalCount >= _ToolCallLoopDetector.IDENTICAL_THRESHOLD) {
3452
3499
  this.hardAborted = true;
3453
3500
  return {
3454
3501
  tool: last.tool,
@@ -3458,18 +3505,73 @@ var ToolCallLoopDetector = class _ToolCallLoopDetector {
3458
3505
  }
3459
3506
  return null;
3460
3507
  }
3508
+ detectSimilarLoop() {
3509
+ if (this.recentCalls.length < 4) return null;
3510
+ const last = this.recentCalls[this.recentCalls.length - 1];
3511
+ let similarCount = 0;
3512
+ for (let i = this.recentCalls.length - 1; i >= 0; i--) {
3513
+ const call = this.recentCalls[i];
3514
+ if (call.tool !== last.tool) break;
3515
+ if (call.failed || last.failed) {
3516
+ similarCount++;
3517
+ } else {
3518
+ break;
3519
+ }
3520
+ }
3521
+ if (similarCount >= _ToolCallLoopDetector.SIMILAR_THRESHOLD) {
3522
+ this.hardAborted = true;
3523
+ return {
3524
+ tool: last.tool,
3525
+ count: similarCount,
3526
+ message: `[SYSTEM] You called "${last.tool}" ${similarCount} times with different params but all are failing. This is a failing loop \u2014 stop immediately. Tell the user you cannot complete this task.`
3527
+ };
3528
+ }
3529
+ return null;
3530
+ }
3531
+ detectTextRepetition() {
3532
+ if (this.recentStepTexts.length < _ToolCallLoopDetector.TEXT_REPEAT_THRESHOLD) return null;
3533
+ const texts = this.recentStepTexts;
3534
+ const last = texts[texts.length - 1];
3535
+ let repeatCount = 0;
3536
+ for (let i = texts.length - 1; i >= 0; i--) {
3537
+ const similarity = this.textSimilarity(last, texts[i]);
3538
+ if (similarity >= 0.7) {
3539
+ repeatCount++;
3540
+ } else {
3541
+ break;
3542
+ }
3543
+ }
3544
+ if (repeatCount >= _ToolCallLoopDetector.TEXT_REPEAT_THRESHOLD) {
3545
+ return {
3546
+ pattern: last.slice(0, 60),
3547
+ count: repeatCount
3548
+ };
3549
+ }
3550
+ return null;
3551
+ }
3552
+ textSimilarity(a, b) {
3553
+ if (a === b) return 1;
3554
+ if (!a || !b) return 0;
3555
+ const setA = new Set(a.split(" "));
3556
+ const setB = new Set(b.split(" "));
3557
+ const intersection = [...setA].filter((w) => setB.has(w)).length;
3558
+ const union = (/* @__PURE__ */ new Set([...setA, ...setB])).size;
3559
+ return union === 0 ? 0 : intersection / union;
3560
+ }
3461
3561
  detectSameTool() {
3462
3562
  if (this.recentCalls.length < 3) return null;
3463
3563
  const last = this.recentCalls[this.recentCalls.length - 1];
3464
3564
  let consecutiveCount = 0;
3565
+ let failingConsecutive = 0;
3465
3566
  for (let i = this.recentCalls.length - 1; i >= 0; i--) {
3466
3567
  if (this.recentCalls[i].tool === last.tool) {
3467
3568
  consecutiveCount++;
3569
+ if (this.recentCalls[i].failed) failingConsecutive++;
3468
3570
  } else {
3469
3571
  break;
3470
3572
  }
3471
3573
  }
3472
- const threshold = _ToolCallLoopDetector.getSameToolThreshold(last.tool);
3574
+ const threshold = _ToolCallLoopDetector.getSameToolThreshold(last.tool, failingConsecutive);
3473
3575
  if (consecutiveCount >= threshold) {
3474
3576
  return { tool: last.tool, count: consecutiveCount };
3475
3577
  }
@@ -3494,6 +3596,7 @@ var ToolCallLoopDetector = class _ToolCallLoopDetector {
3494
3596
  this.recentCalls = [];
3495
3597
  this.totalCalls = 0;
3496
3598
  this.hardAborted = false;
3599
+ this.recentStepTexts = [];
3497
3600
  }
3498
3601
  };
3499
3602
  var MAX_STEPS = 10;
@@ -3659,7 +3762,7 @@ You can override this:
3659
3762
  const systemPrompt = this.buildSystemPrompt();
3660
3763
  const recentMemory = this.shortTerm.getRecent(msg.channelId, 10);
3661
3764
  const messages = [];
3662
- const recentSteps = this.shortTerm.getRecent(msg.channelId, 4);
3765
+ const recentSteps = this.shortTerm.getRecent(msg.channelId, 6);
3663
3766
  let loopWarning = null;
3664
3767
  if (recentSteps.length >= 3) {
3665
3768
  const toolCallPattern = /\[Using: (.+?)\]/g;
@@ -3678,10 +3781,24 @@ You can override this:
3678
3781
  loopWarning = `[SYSTEM WARNING] You have called ${last3[0]} 3+ times in a row with the same result. Stop repeating this call. Try a different approach \u2014 if you're failing on permissions, try a different path. If you're failing on git push auth, use github_api with PUT /repos/{owner}/{repo}/contents/{path} to push files directly through the API.`;
3679
3782
  }
3680
3783
  }
3784
+ if (!loopWarning) {
3785
+ const assistantMessages = recentSteps.filter((m) => m.role === "assistant" && m.content.length > 20);
3786
+ if (assistantMessages.length >= 3) {
3787
+ const last3 = assistantMessages.slice(-3);
3788
+ const normalizeText = (t) => t.toLowerCase().replace(/[^\w\s]/g, "").replace(/\s+/g, " ").trim().slice(0, 150);
3789
+ const normalized = last3.map((m) => normalizeText(m.content));
3790
+ const words0 = new Set(normalized[0].split(" "));
3791
+ const overlap01 = normalized[0] && normalized[1] ? [...words0].filter((w) => new Set(normalized[1].split(" ")).has(w)).length / Math.max(words0.size, 1) : 0;
3792
+ const overlap12 = normalized[1] && normalized[2] ? [...new Set(normalized[1].split(" "))].filter((w) => new Set(normalized[2].split(" ")).has(w)).length / Math.max(new Set(normalized[1].split(" ")).size, 1) : 0;
3793
+ if (overlap01 > 0.75 && overlap12 > 0.75) {
3794
+ loopWarning = `[SYSTEM WARNING] Your last 3 responses are nearly identical. You are stuck in a text repetition loop. Stop immediately and give a completely different response. If you cannot complete the task, tell the user clearly why.`;
3795
+ }
3796
+ }
3797
+ }
3681
3798
  }
3682
3799
  if (loopWarning) {
3683
3800
  messages.push({ role: "user", content: loopWarning });
3684
- messages.push({ role: "assistant", content: "Understood. I will try a different approach." });
3801
+ messages.push({ role: "assistant", content: "Acknowledged. I will stop repeating and respond differently, or clearly state if the task cannot be completed." });
3685
3802
  }
3686
3803
  if (this.userMemory) {
3687
3804
  const memoryContext = this.userMemory.retrieveRelevant(msg.content, { maxRecords: 5, maxChars: 900 });
@@ -3744,11 +3861,15 @@ You can override this:
3744
3861
  maxSteps: MAX_STEPS,
3745
3862
  abortSignal: loopAbortController.signal,
3746
3863
  onStepFinish: async ({ toolCalls, toolResults }) => {
3747
- if (toolCalls && toolCalls.length > 0) {
3864
+ if (toolCalls && toolResults && toolCalls.length > 0) {
3748
3865
  const names = toolCalls.map((tc) => tc.toolName).join(", ");
3749
3866
  logger.info({ tools: names }, "Tool call step");
3750
- for (const tc of toolCalls) {
3751
- loopDetector.record(tc.toolName, tc.args);
3867
+ for (let i = 0; i < toolCalls.length; i++) {
3868
+ const tc = toolCalls[i];
3869
+ const tr = toolResults[i];
3870
+ const resultStr = typeof tr?.result === "string" ? tr.result : JSON.stringify(tr?.result ?? "");
3871
+ const failed = resultStr.length < 5e3 && (resultStr.startsWith("Error:") || resultStr.startsWith("\u26A0") || resultStr.includes("exited with code") || resultStr.includes("Command failed") || resultStr.startsWith("Command exited with code"));
3872
+ loopDetector.record(tc.toolName, tc.args, failed);
3752
3873
  }
3753
3874
  if (loopDetector.detectAbsoluteLimit()) {
3754
3875
  logger.warn("Absolute tool call limit reached \u2014 aborting");
@@ -3773,6 +3894,17 @@ You can override this:
3773
3894
  loopAbortController.abort();
3774
3895
  return;
3775
3896
  }
3897
+ const similarLoop = loopDetector.detectSimilarLoop();
3898
+ if (similarLoop) {
3899
+ logger.warn({ tool: similarLoop.tool, count: similarLoop.count }, "Failing loop detected \u2014 aborting");
3900
+ if (!loopWarningSent && channel && msg.channelType !== "internal") {
3901
+ loopWarningSent = true;
3902
+ await channel.send(`\u26A0 Failing loop detected \u2014 ${similarLoop.tool} called ${similarLoop.count}x, all failing. Stopping.`, msg.channelId).catch(() => {
3903
+ });
3904
+ }
3905
+ loopAbortController.abort();
3906
+ return;
3907
+ }
3776
3908
  const softLoop = loopDetector.detectSameTool();
3777
3909
  if (softLoop && !loopWarningSent && channel && msg.channelType !== "internal") {
3778
3910
  if (this.capabilities.permissions.isAutoApproveAll()) {
@@ -3828,6 +3960,21 @@ You can override this:
3828
3960
  });
3829
3961
  }
3830
3962
  }
3963
+ } else if (toolResults === void 0 || toolCalls === void 0) {
3964
+ const stepText = toolResults?.text ?? "";
3965
+ if (stepText) {
3966
+ loopDetector.recordStepText(String(stepText));
3967
+ }
3968
+ const textRepeat = loopDetector.detectTextRepetition();
3969
+ if (textRepeat) {
3970
+ logger.warn({ pattern: textRepeat.pattern, count: textRepeat.count }, "Text repetition loop detected \u2014 aborting");
3971
+ if (!loopWarningSent && channel && msg.channelType !== "internal") {
3972
+ loopWarningSent = true;
3973
+ await channel.send("\u26A0 I keep generating the same response. Stopping to prevent repetition.", msg.channelId).catch(() => {
3974
+ });
3975
+ }
3976
+ loopAbortController.abort();
3977
+ }
3831
3978
  }
3832
3979
  }
3833
3980
  });
@@ -3852,6 +3999,7 @@ You can override this:
3852
3999
  ]);
3853
4000
  result = { text: fullText, usage };
3854
4001
  streamedText = fullText;
4002
+ loopDetector.recordStepText(fullText);
3855
4003
  } else {
3856
4004
  result = await generateText3({
3857
4005
  model: provider.getModelInstance(),
@@ -3861,11 +4009,15 @@ You can override this:
3861
4009
  maxSteps: MAX_STEPS,
3862
4010
  abortSignal: loopAbortController.signal,
3863
4011
  onStepFinish: async ({ toolCalls, toolResults }) => {
3864
- if (toolCalls && toolCalls.length > 0) {
4012
+ if (toolCalls && toolResults && toolCalls.length > 0) {
3865
4013
  const names = toolCalls.map((tc) => tc.toolName).join(", ");
3866
4014
  logger.info({ tools: names }, "Tool call step");
3867
- for (const tc of toolCalls) {
3868
- loopDetector.record(tc.toolName, tc.args);
4015
+ for (let i = 0; i < toolCalls.length; i++) {
4016
+ const tc = toolCalls[i];
4017
+ const tr = toolResults[i];
4018
+ const resultStr = typeof tr?.result === "string" ? tr.result : JSON.stringify(tr?.result ?? "");
4019
+ const failed = resultStr.length < 5e3 && (resultStr.startsWith("Error:") || resultStr.startsWith("\u26A0") || resultStr.includes("exited with code") || resultStr.includes("Command failed") || resultStr.startsWith("Command exited with code"));
4020
+ loopDetector.record(tc.toolName, tc.args, failed);
3869
4021
  }
3870
4022
  if (loopDetector.detectAbsoluteLimit()) {
3871
4023
  logger.warn("Absolute tool call limit reached \u2014 aborting");
@@ -3890,6 +4042,17 @@ You can override this:
3890
4042
  loopAbortController.abort();
3891
4043
  return;
3892
4044
  }
4045
+ const similarLoop = loopDetector.detectSimilarLoop();
4046
+ if (similarLoop) {
4047
+ logger.warn({ tool: similarLoop.tool, count: similarLoop.count }, "Failing loop detected \u2014 aborting");
4048
+ if (!loopWarningSent && channel && msg.channelType !== "internal") {
4049
+ loopWarningSent = true;
4050
+ await channel.send(`\u26A0 Failing loop detected \u2014 ${similarLoop.tool} called ${similarLoop.count}x, all failing. Stopping.`, msg.channelId).catch(() => {
4051
+ });
4052
+ }
4053
+ loopAbortController.abort();
4054
+ return;
4055
+ }
3893
4056
  const softLoop = loopDetector.detectSameTool();
3894
4057
  if (softLoop && !loopWarningSent && channel && msg.channelType !== "internal") {
3895
4058
  if (this.capabilities.permissions.isAutoApproveAll()) {
@@ -3945,6 +4108,21 @@ You can override this:
3945
4108
  });
3946
4109
  }
3947
4110
  }
4111
+ } else if (toolResults === void 0 || toolCalls === void 0) {
4112
+ const stepText = toolResults?.text ?? "";
4113
+ if (stepText) {
4114
+ loopDetector.recordStepText(String(stepText));
4115
+ }
4116
+ const textRepeat = loopDetector.detectTextRepetition();
4117
+ if (textRepeat) {
4118
+ logger.warn({ pattern: textRepeat.pattern, count: textRepeat.count }, "Text repetition loop detected \u2014 aborting");
4119
+ if (!loopWarningSent && channel && msg.channelType !== "internal") {
4120
+ loopWarningSent = true;
4121
+ await channel.send("\u26A0 I keep generating the same response. Stopping to prevent repetition.", msg.channelId).catch(() => {
4122
+ });
4123
+ }
4124
+ loopAbortController.abort();
4125
+ }
3948
4126
  }
3949
4127
  }
3950
4128
  });
@@ -3959,7 +4137,7 @@ You can override this:
3959
4137
  result = { text: streamedText, usage: void 0 };
3960
4138
  }
3961
4139
  if (!result) {
3962
- result = { text: "I stopped because I was repeating the same tool calls. What would you like me to do differently?", usage: void 0 };
4140
+ result = { text: "I stopped because I detected I was stuck in a loop (repeating the same action without progress). I cannot complete this task as requested. Please let me know if you'd like me to try a completely different approach, or if there's something else I can help with.", usage: void 0 };
3963
4141
  }
3964
4142
  if (usedProvider) {
3965
4143
  this.providers.markSuccess(usedProvider.name);
@@ -4862,11 +5040,11 @@ Or use /budget override, /budget reset, /budget set <number> anytime.`,
4862
5040
  // src/core/scheduler.ts
4863
5041
  import cron from "node-cron";
4864
5042
  import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5 } from "fs";
4865
- import { join as join5 } from "path";
5043
+ import { join as join6 } from "path";
4866
5044
  import { parse as parseYaml2, stringify as stringifyYaml2 } from "yaml";
4867
5045
  var SCHEDULES_FILE = "schedules.yaml";
4868
5046
  function getSchedulesPath() {
4869
- return join5(getMercuryHome(), SCHEDULES_FILE);
5047
+ return join6(getMercuryHome(), SCHEDULES_FILE);
4870
5048
  }
4871
5049
  function loadSchedules() {
4872
5050
  const path3 = getSchedulesPath();
@@ -5085,7 +5263,7 @@ var ChannelRegistry = class {
5085
5263
 
5086
5264
  // src/utils/tokens.ts
5087
5265
  import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
5088
- import { join as join6 } from "path";
5266
+ import { join as join7 } from "path";
5089
5267
  var TOKEN_FILE = "token-usage.json";
5090
5268
  var TokenBudget = class {
5091
5269
  constructor(config) {
@@ -5167,7 +5345,7 @@ var TokenBudget = class {
5167
5345
  }
5168
5346
  }
5169
5347
  persist() {
5170
- const path3 = join6(getMercuryHome(), TOKEN_FILE);
5348
+ const path3 = join7(getMercuryHome(), TOKEN_FILE);
5171
5349
  try {
5172
5350
  const data = {
5173
5351
  dailyUsed: this.dailyUsed,
@@ -5181,7 +5359,7 @@ var TokenBudget = class {
5181
5359
  }
5182
5360
  }
5183
5361
  restore() {
5184
- const path3 = join6(getMercuryHome(), TOKEN_FILE);
5362
+ const path3 = join7(getMercuryHome(), TOKEN_FILE);
5185
5363
  if (!existsSync6(path3)) return;
5186
5364
  try {
5187
5365
  const raw = readFileSync5(path3, "utf-8");
@@ -5200,7 +5378,7 @@ var TokenBudget = class {
5200
5378
 
5201
5379
  // src/capabilities/permissions.ts
5202
5380
  import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync7 } from "fs";
5203
- import { join as join7, resolve as resolve3, sep } from "path";
5381
+ import { join as join8, resolve as resolve3, sep } from "path";
5204
5382
  import { homedir as homedir2 } from "os";
5205
5383
  import { parse as parseYaml3, stringify as stringifyYaml3 } from "yaml";
5206
5384
  var DEFAULT_MANIFEST = {
@@ -5230,7 +5408,15 @@ var DEFAULT_MANIFEST = {
5230
5408
  "init 6",
5231
5409
  "kill -9 1",
5232
5410
  "> /dev/sda",
5233
- "mv /* /dev/null"
5411
+ "mv /* /dev/null",
5412
+ "del /s /q C:\\*",
5413
+ "rmdir /s /q C:\\*",
5414
+ "format *",
5415
+ "icacls * C:\\* /grant",
5416
+ "net user *",
5417
+ "netsh *",
5418
+ "reg delete *",
5419
+ "cmd /c rd /s /q *"
5234
5420
  ],
5235
5421
  autoApproved: [
5236
5422
  "ls *",
@@ -5257,7 +5443,15 @@ var DEFAULT_MANIFEST = {
5257
5443
  "du *",
5258
5444
  "uname *",
5259
5445
  "curl *",
5260
- "wget *"
5446
+ "wget *",
5447
+ "dir *",
5448
+ "type *",
5449
+ "cd *",
5450
+ "where *",
5451
+ "tree *",
5452
+ "findstr *",
5453
+ "tasklist *",
5454
+ "systeminfo *"
5261
5455
  ],
5262
5456
  needsApproval: [
5263
5457
  "npm publish *",
@@ -5274,7 +5468,13 @@ var DEFAULT_MANIFEST = {
5274
5468
  "cp -r *",
5275
5469
  "chmod *",
5276
5470
  "mkdir *",
5277
- "rmdir *"
5471
+ "rmdir *",
5472
+ "xcopy *",
5473
+ "robocopy *",
5474
+ "del *",
5475
+ "rd /s *",
5476
+ "powershell *",
5477
+ "cmd /c *"
5278
5478
  ],
5279
5479
  cwdOnly: true
5280
5480
  },
@@ -5285,7 +5485,7 @@ var DEFAULT_MANIFEST = {
5285
5485
  }
5286
5486
  }
5287
5487
  };
5288
- var PERMISSIONS_FILE = join7(getMercuryHome(), "permissions.yaml");
5488
+ var PERMISSIONS_FILE = join8(getMercuryHome(), "permissions.yaml");
5289
5489
  var PermissionManager = class {
5290
5490
  manifest;
5291
5491
  cwd;
@@ -5694,7 +5894,7 @@ function createCreateFileTool(permissions, getCwd) {
5694
5894
  import { tool as tool4 } from "ai";
5695
5895
  import { z as z4 } from "zod";
5696
5896
  import { existsSync as existsSync11, readdirSync as readdirSync2, statSync } from "fs";
5697
- import { resolve as resolve7, isAbsolute as isAbsolute4 } from "path";
5897
+ import { resolve as resolve7, isAbsolute as isAbsolute4, join as join9 } from "path";
5698
5898
  function createListDirTool(permissions, getCwd) {
5699
5899
  return tool4({
5700
5900
  description: "List the contents of a directory. Shows file names, types, and sizes.",
@@ -5718,7 +5918,7 @@ function createListDirTool(permissions, getCwd) {
5718
5918
  const entries = readdirSync2(resolved, { withFileTypes: true });
5719
5919
  const lines = entries.map((entry) => {
5720
5920
  const isDir = entry.isDirectory();
5721
- const fullPath = join8(resolved, entry.name);
5921
+ const fullPath = join9(resolved, entry.name);
5722
5922
  let size = "";
5723
5923
  try {
5724
5924
  if (!isDir) {
@@ -5739,9 +5939,6 @@ ${lines.join("\n")}`;
5739
5939
  }
5740
5940
  });
5741
5941
  }
5742
- function join8(base, name) {
5743
- return base.endsWith("/") ? base + name : base + "/" + name;
5744
- }
5745
5942
  function formatSize(bytes) {
5746
5943
  if (bytes < 1024) return `${bytes}B`;
5747
5944
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
@@ -5919,6 +6116,7 @@ import { z as z10 } from "zod";
5919
6116
  import { execSync } from "child_process";
5920
6117
  import { resolve as resolve12, isAbsolute as isAbsolute9 } from "path";
5921
6118
  import { existsSync as existsSync14 } from "fs";
6119
+ import { homedir as homedir3 } from "os";
5922
6120
  function createRunCommandTool(permissions, getCwd, setCwd) {
5923
6121
  return tool10({
5924
6122
  description: `Run a shell command in the current working directory. Use the cd tool to change directories first \u2014 cd commands within this tool only affect chained commands (e.g., "cd /path && ls"), not subsequent calls.
@@ -5973,7 +6171,7 @@ function detectCd(command, currentCwd, setCwd) {
5973
6171
  const trimmed = command.trim();
5974
6172
  const cdOnly = trimmed.match(/^cd\s+(.+)$/);
5975
6173
  if (cdOnly) {
5976
- const target = cdOnly[1].replace(/^["']|["']$/g, "").replace(/~/, process.env.HOME || "");
6174
+ const target = cdOnly[1].replace(/^["']|["']$/g, "").replace(/^~/, homedir3());
5977
6175
  const resolved = isAbsolute9(target) ? target : resolve12(currentCwd, target);
5978
6176
  if (existsSync14(resolved)) {
5979
6177
  setCwd(resolved);
@@ -5982,7 +6180,7 @@ function detectCd(command, currentCwd, setCwd) {
5982
6180
  }
5983
6181
  const cdChain = trimmed.match(/cd\s+(.+?)\s*&&/);
5984
6182
  if (cdChain) {
5985
- const target = cdChain[1].replace(/^["']|["']$/g, "").replace(/~/, process.env.HOME || "");
6183
+ const target = cdChain[1].replace(/^["']|["']$/g, "").replace(/^~/, homedir3());
5986
6184
  const resolved = isAbsolute9(target) ? target : resolve12(currentCwd, target);
5987
6185
  if (existsSync14(resolved)) {
5988
6186
  setCwd(resolved);
@@ -6357,6 +6555,8 @@ function createGitAddTool(getCwd) {
6357
6555
  import { tool as tool24 } from "ai";
6358
6556
  import { z as z24 } from "zod";
6359
6557
  import { execSync as execSync6 } from "child_process";
6558
+ import { writeFileSync as writeFileSync10, unlinkSync as unlinkSync3 } from "fs";
6559
+ import { join as join10 } from "path";
6360
6560
  var CO_AUTHOR = "Mercury <mercury@cosmicstack.org>";
6361
6561
  function createGitCommitTool(getCwd) {
6362
6562
  return tool24({
@@ -6369,10 +6569,24 @@ function createGitCommitTool(getCwd) {
6369
6569
  const fullMessage = `${message}
6370
6570
 
6371
6571
  Co-authored-by: ${CO_AUTHOR}`;
6372
- const escapedMsg = fullMessage.replace(/"/g, '\\"');
6373
- const result = execSync6(`git commit -m "${escapedMsg}"`, { encoding: "utf-8", timeout: 1e4, cwd: getCwd() });
6572
+ const cwd = getCwd();
6573
+ const msgFilePath = join10(cwd, ".git", "MERCU_MSG");
6574
+ writeFileSync10(msgFilePath, fullMessage, "utf-8");
6575
+ const result = execSync6(`git commit -F "${msgFilePath}"`, {
6576
+ encoding: "utf-8",
6577
+ timeout: 1e4,
6578
+ cwd
6579
+ });
6580
+ try {
6581
+ unlinkSync3(msgFilePath);
6582
+ } catch {
6583
+ }
6374
6584
  return result.trim() || "Committed successfully.";
6375
6585
  } catch (err) {
6586
+ try {
6587
+ unlinkSync3(join10(getCwd(), ".git", "MERCU_MSG"));
6588
+ } catch {
6589
+ }
6376
6590
  const stderr = err.stderr?.trim() || "";
6377
6591
  if (stderr.includes("nothing to commit")) {
6378
6592
  return "Nothing to commit \u2014 no staged changes.";
@@ -6912,8 +7126,8 @@ var CapabilityRegistry = class {
6912
7126
  };
6913
7127
 
6914
7128
  // src/skills/loader.ts
6915
- import { existsSync as existsSync16, readFileSync as readFileSync10, readdirSync as readdirSync3, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10 } from "fs";
6916
- import { join as join9 } from "path";
7129
+ import { existsSync as existsSync16, readFileSync as readFileSync10, readdirSync as readdirSync3, mkdirSync as mkdirSync9, writeFileSync as writeFileSync11 } from "fs";
7130
+ import { join as join11 } from "path";
6917
7131
  import { parse as parseYaml5 } from "yaml";
6918
7132
  var SKILL_FILE = "SKILL.md";
6919
7133
  function parseSkillMd(content) {
@@ -6937,7 +7151,7 @@ var SkillLoader = class {
6937
7151
  discovered = /* @__PURE__ */ new Map();
6938
7152
  loaded = /* @__PURE__ */ new Map();
6939
7153
  constructor(skillsDir) {
6940
- this.skillsDir = skillsDir || join9(getMercuryHome(), "skills");
7154
+ this.skillsDir = skillsDir || join11(getMercuryHome(), "skills");
6941
7155
  }
6942
7156
  discover() {
6943
7157
  this.discovered.clear();
@@ -6950,7 +7164,7 @@ var SkillLoader = class {
6950
7164
  const entries = readdirSync3(this.skillsDir, { withFileTypes: true });
6951
7165
  for (const entry of entries) {
6952
7166
  if (!entry.isDirectory() || entry.name.startsWith("_")) continue;
6953
- const skillPath = join9(this.skillsDir, entry.name, SKILL_FILE);
7167
+ const skillPath = join11(this.skillsDir, entry.name, SKILL_FILE);
6954
7168
  if (!existsSync16(skillPath)) continue;
6955
7169
  try {
6956
7170
  const raw = readFileSync10(skillPath, "utf-8");
@@ -6972,18 +7186,18 @@ var SkillLoader = class {
6972
7186
  if (cached) return cached;
6973
7187
  for (const entry of readdirSync3(this.skillsDir, { withFileTypes: true })) {
6974
7188
  if (!entry.isDirectory() || entry.name.startsWith("_")) continue;
6975
- const skillPath = join9(this.skillsDir, entry.name, SKILL_FILE);
7189
+ const skillPath = join11(this.skillsDir, entry.name, SKILL_FILE);
6976
7190
  if (!existsSync16(skillPath)) continue;
6977
7191
  try {
6978
7192
  const raw = readFileSync10(skillPath, "utf-8");
6979
7193
  const parsed = parseSkillMd(raw);
6980
7194
  if (!parsed || parsed.meta.name !== name) continue;
6981
- const skillDir = join9(this.skillsDir, entry.name);
7195
+ const skillDir = join11(this.skillsDir, entry.name);
6982
7196
  const skill = {
6983
7197
  ...parsed.meta,
6984
7198
  instructions: parsed.instructions,
6985
- scriptsDir: existsSync16(join9(skillDir, "scripts")) ? join9(skillDir, "scripts") : void 0,
6986
- referencesDir: existsSync16(join9(skillDir, "references")) ? join9(skillDir, "references") : void 0
7199
+ scriptsDir: existsSync16(join11(skillDir, "scripts")) ? join11(skillDir, "scripts") : void 0,
7200
+ referencesDir: existsSync16(join11(skillDir, "references")) ? join11(skillDir, "references") : void 0
6987
7201
  };
6988
7202
  this.loaded.set(name, skill);
6989
7203
  return skill;
@@ -7003,17 +7217,17 @@ var SkillLoader = class {
7003
7217
  return "Available skills:\n" + skills.map((s) => `- ${s.name}: ${s.description}`).join("\n");
7004
7218
  }
7005
7219
  saveSkill(name, content) {
7006
- const skillDir = join9(this.skillsDir, name);
7220
+ const skillDir = join11(this.skillsDir, name);
7007
7221
  if (!existsSync16(skillDir)) {
7008
7222
  mkdirSync9(skillDir, { recursive: true });
7009
7223
  }
7010
- writeFileSync10(join9(skillDir, SKILL_FILE), content, "utf-8");
7224
+ writeFileSync11(join11(skillDir, SKILL_FILE), content, "utf-8");
7011
7225
  logger.info({ skill: name }, "Skill saved");
7012
7226
  this.discover();
7013
7227
  return skillDir;
7014
7228
  }
7015
7229
  seedTemplate() {
7016
- const templateDir = join9(this.skillsDir, "_template");
7230
+ const templateDir = join11(this.skillsDir, "_template");
7017
7231
  mkdirSync9(templateDir, { recursive: true });
7018
7232
  const content = `---
7019
7233
  name: template-skill
@@ -7044,7 +7258,7 @@ Describe what this skill enables Mercury to do. When invoked via the use_skill t
7044
7258
  - List only the tools you need in allowed-tools
7045
7259
  - The skill name must be unique among installed skills
7046
7260
  `;
7047
- writeFileSync10(join9(templateDir, SKILL_FILE), content, "utf-8");
7261
+ writeFileSync11(join11(templateDir, SKILL_FILE), content, "utf-8");
7048
7262
  logger.info("Seeded template skill");
7049
7263
  }
7050
7264
  };
@@ -7213,17 +7427,17 @@ function getManual() {
7213
7427
 
7214
7428
  // src/cli/daemon.ts
7215
7429
  import { spawn } from "child_process";
7216
- import { existsSync as existsSync17, readFileSync as readFileSync11, writeFileSync as writeFileSync11, unlinkSync as unlinkSync3, mkdirSync as mkdirSync10, openSync } from "fs";
7217
- import { join as join10 } from "path";
7430
+ import { existsSync as existsSync17, readFileSync as readFileSync11, writeFileSync as writeFileSync12, unlinkSync as unlinkSync4, mkdirSync as mkdirSync10, openSync } from "fs";
7431
+ import { join as join12 } from "path";
7218
7432
  import process2 from "process";
7219
7433
  import chalk5 from "chalk";
7220
7434
  var PID_FILE = "daemon.pid";
7221
7435
  var LOG_FILE = "daemon.log";
7222
7436
  function pidPath() {
7223
- return join10(getMercuryHome(), PID_FILE);
7437
+ return join12(getMercuryHome(), PID_FILE);
7224
7438
  }
7225
7439
  function logPath() {
7226
- return join10(getMercuryHome(), LOG_FILE);
7440
+ return join12(getMercuryHome(), LOG_FILE);
7227
7441
  }
7228
7442
  function readPid() {
7229
7443
  const path3 = pidPath();
@@ -7259,7 +7473,7 @@ function startBackground() {
7259
7473
  }
7260
7474
  if (status.pid && !status.running) {
7261
7475
  try {
7262
- unlinkSync3(pidPath());
7476
+ unlinkSync4(pidPath());
7263
7477
  } catch {
7264
7478
  }
7265
7479
  }
@@ -7277,7 +7491,7 @@ function startBackground() {
7277
7491
  windowsHide: isWin
7278
7492
  });
7279
7493
  child.unref();
7280
- writeFileSync11(pidPath(), String(child.pid));
7494
+ writeFileSync12(pidPath(), String(child.pid));
7281
7495
  console.log("");
7282
7496
  console.log(chalk5.green(` Mercury started in background (PID: ${child.pid})`));
7283
7497
  console.log(chalk5.dim(` Logs: ${logFile}`));
@@ -7295,7 +7509,7 @@ function stopDaemon() {
7295
7509
  if (!status.running) {
7296
7510
  console.log(chalk5.yellow(` Stale PID file found (PID: ${status.pid} is not running). Cleaning up.`));
7297
7511
  try {
7298
- unlinkSync3(pidPath());
7512
+ unlinkSync4(pidPath());
7299
7513
  } catch {
7300
7514
  }
7301
7515
  console.log("");
@@ -7312,7 +7526,7 @@ function stopDaemon() {
7312
7526
  console.log(chalk5.red(` Failed to stop PID ${status.pid}. You may need to kill it manually.`));
7313
7527
  }
7314
7528
  try {
7315
- unlinkSync3(pidPath());
7529
+ unlinkSync4(pidPath());
7316
7530
  } catch {
7317
7531
  }
7318
7532
  console.log("");
@@ -7330,13 +7544,13 @@ function restartDaemon() {
7330
7544
  } catch {
7331
7545
  }
7332
7546
  try {
7333
- unlinkSync3(pidPath());
7547
+ unlinkSync4(pidPath());
7334
7548
  } catch {
7335
7549
  }
7336
7550
  console.log(chalk5.green(" Mercury stopped."));
7337
7551
  } else if (status.pid) {
7338
7552
  try {
7339
- unlinkSync3(pidPath());
7553
+ unlinkSync4(pidPath());
7340
7554
  } catch {
7341
7555
  }
7342
7556
  }
@@ -7362,7 +7576,7 @@ function tryAutoDaemonize() {
7362
7576
  }
7363
7577
  if (status.pid && !status.running) {
7364
7578
  try {
7365
- unlinkSync3(pidPath());
7579
+ unlinkSync4(pidPath());
7366
7580
  } catch {
7367
7581
  }
7368
7582
  }
@@ -7383,7 +7597,7 @@ function tryAutoDaemonize() {
7383
7597
  if (!child.pid) {
7384
7598
  return false;
7385
7599
  }
7386
- writeFileSync11(pidPath(), String(child.pid));
7600
+ writeFileSync12(pidPath(), String(child.pid));
7387
7601
  return true;
7388
7602
  } catch {
7389
7603
  return false;
@@ -7391,9 +7605,9 @@ function tryAutoDaemonize() {
7391
7605
  }
7392
7606
 
7393
7607
  // src/cli/service.ts
7394
- import { existsSync as existsSync18, mkdirSync as mkdirSync11, writeFileSync as writeFileSync12, unlinkSync as unlinkSync4 } from "fs";
7395
- import { join as join11 } from "path";
7396
- import { homedir as homedir3 } from "os";
7608
+ import { existsSync as existsSync18, mkdirSync as mkdirSync11, writeFileSync as writeFileSync13, unlinkSync as unlinkSync5 } from "fs";
7609
+ import { join as join13 } from "path";
7610
+ import { homedir as homedir4 } from "os";
7397
7611
  import chalk6 from "chalk";
7398
7612
  import { execSync as execSync8 } from "child_process";
7399
7613
  var SERVICE_DESC = "Mercury \u2014 Soul-Driven AI Agent";
@@ -7401,9 +7615,9 @@ var WIN_TASK_NAME = "MercuryAgent";
7401
7615
  function isServiceInstalled() {
7402
7616
  const platform = process.platform;
7403
7617
  if (platform === "darwin") {
7404
- return existsSync18(join11(homedir3(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist"));
7618
+ return existsSync18(join13(homedir4(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist"));
7405
7619
  } else if (platform === "linux") {
7406
- return existsSync18(join11(homedir3(), ".config", "systemd", "user", "mercury.service"));
7620
+ return existsSync18(join13(homedir4(), ".config", "systemd", "user", "mercury.service"));
7407
7621
  } else if (platform === "win32") {
7408
7622
  try {
7409
7623
  execSync8(`schtasks /query /tn "${WIN_TASK_NAME}"`, { stdio: "pipe", shell: "cmd.exe" });
@@ -7418,7 +7632,10 @@ function getNodeBinPath() {
7418
7632
  return process.execPath;
7419
7633
  }
7420
7634
  function getDistPath() {
7421
- return join11(process.argv[1] || "/usr/local/bin/mercury", "..", "..", "lib", "node_modules", "@cosmicstack", "mercury-agent", "dist", "index.js");
7635
+ if (!process.argv[1]) {
7636
+ return join13(homedir4(), ".nvm", "versions", "node", `v${process.version.slice(1)}`, "lib", "node_modules", "@cosmicstack", "mercury-agent", "dist", "index.js");
7637
+ }
7638
+ return join13(process.argv[1], "..", "..", "lib", "node_modules", "@cosmicstack", "mercury-agent", "dist", "index.js");
7422
7639
  }
7423
7640
  function installService() {
7424
7641
  const platform = process.platform;
@@ -7457,16 +7674,16 @@ function showServiceStatus() {
7457
7674
  }
7458
7675
  }
7459
7676
  function installMac() {
7460
- const plistDir = join11(homedir3(), "Library", "LaunchAgents");
7461
- const plistPath = join11(plistDir, "com.cosmicstack.mercury.plist");
7677
+ const plistDir = join13(homedir4(), "Library", "LaunchAgents");
7678
+ const plistPath = join13(plistDir, "com.cosmicstack.mercury.plist");
7462
7679
  if (!existsSync18(plistDir)) {
7463
7680
  mkdirSync11(plistDir, { recursive: true });
7464
7681
  }
7465
7682
  const nodeBin = getNodeBinPath();
7466
7683
  const scriptPath = getDistPath();
7467
7684
  const home = getMercuryHome();
7468
- const logPath2 = join11(home, "daemon.log");
7469
- const errPath = join11(home, "daemon-error.log");
7685
+ const logPath2 = join13(home, "daemon.log");
7686
+ const errPath = join13(home, "daemon-error.log");
7470
7687
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
7471
7688
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
7472
7689
  <plist version="1.0">
@@ -7496,13 +7713,13 @@ function installMac() {
7496
7713
  <key>PATH</key>
7497
7714
  <string>${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}</string>
7498
7715
  <key>HOME</key>
7499
- <string>${homedir3()}</string>
7716
+ <string>${homedir4()}</string>
7500
7717
  </dict>
7501
7718
  <key>WorkingDirectory</key>
7502
- <string>${homedir3()}</string>
7719
+ <string>${homedir4()}</string>
7503
7720
  </dict>
7504
7721
  </plist>`;
7505
- writeFileSync12(plistPath, plist, "utf-8");
7722
+ writeFileSync13(plistPath, plist, "utf-8");
7506
7723
  try {
7507
7724
  execSync8(`launchctl load ${plistPath}`, { stdio: "inherit" });
7508
7725
  } catch {
@@ -7519,7 +7736,7 @@ function installMac() {
7519
7736
  console.log("");
7520
7737
  }
7521
7738
  function uninstallMac() {
7522
- const plistPath = join11(homedir3(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist");
7739
+ const plistPath = join13(homedir4(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist");
7523
7740
  if (!existsSync18(plistPath)) {
7524
7741
  console.log(chalk6.yellow(" Mercury service is not installed."));
7525
7742
  console.log("");
@@ -7530,7 +7747,7 @@ function uninstallMac() {
7530
7747
  } catch {
7531
7748
  }
7532
7749
  try {
7533
- unlinkSync4(plistPath);
7750
+ unlinkSync5(plistPath);
7534
7751
  } catch {
7535
7752
  console.log(chalk6.yellow(" Failed to remove plist file. Remove manually:"));
7536
7753
  console.log(chalk6.dim(` rm ${plistPath}`));
@@ -7540,7 +7757,7 @@ function uninstallMac() {
7540
7757
  console.log("");
7541
7758
  }
7542
7759
  function showMacStatus() {
7543
- const plistPath = join11(homedir3(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist");
7760
+ const plistPath = join13(homedir4(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist");
7544
7761
  if (!existsSync18(plistPath)) {
7545
7762
  console.log(chalk6.yellow(" Mercury service is not installed."));
7546
7763
  console.log(chalk6.dim(" Run `mercury service install` to set it up."));
@@ -7558,11 +7775,11 @@ function showMacStatus() {
7558
7775
  console.log("");
7559
7776
  }
7560
7777
  function installLinux() {
7561
- const systemdDir = join11(homedir3(), ".config", "systemd", "user");
7778
+ const systemdDir = join13(homedir4(), ".config", "systemd", "user");
7562
7779
  if (!existsSync18(systemdDir)) {
7563
7780
  mkdirSync11(systemdDir, { recursive: true });
7564
7781
  }
7565
- const servicePath = join11(systemdDir, "mercury.service");
7782
+ const servicePath = join13(systemdDir, "mercury.service");
7566
7783
  const nodeBin = getNodeBinPath();
7567
7784
  const scriptPath = getDistPath();
7568
7785
  const home = getMercuryHome();
@@ -7576,14 +7793,14 @@ ExecStart=${nodeBin} ${scriptPath} start --daemon
7576
7793
  Restart=on-failure
7577
7794
  RestartSec=5
7578
7795
  Environment=PATH=${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}
7579
- Environment=HOME=${homedir3()}
7580
- WorkingDirectory=${homedir3()}
7581
- StandardOutput=append:${join11(home, "daemon.log")}
7582
- StandardError=append:${join11(home, "daemon-error.log")}
7796
+ Environment=HOME=${homedir4()}
7797
+ WorkingDirectory=${homedir4()}
7798
+ StandardOutput=append:${join13(home, "daemon.log")}
7799
+ StandardError=append:${join13(home, "daemon-error.log")}
7583
7800
 
7584
7801
  [Install]
7585
7802
  WantedBy=default.target`;
7586
- writeFileSync12(servicePath, service, "utf-8");
7803
+ writeFileSync13(servicePath, service, "utf-8");
7587
7804
  try {
7588
7805
  execSync8("systemctl --user daemon-reload", { stdio: "inherit" });
7589
7806
  execSync8("systemctl --user enable mercury.service", { stdio: "inherit" });
@@ -7603,14 +7820,14 @@ WantedBy=default.target`;
7603
7820
  console.log("");
7604
7821
  console.log(chalk6.green(" Mercury service installed (systemd --user)"));
7605
7822
  console.log(chalk6.dim(` Service: ${servicePath}`));
7606
- console.log(chalk6.dim(` Logs: ${join11(home, "daemon.log")}`));
7823
+ console.log(chalk6.dim(` Logs: ${join13(home, "daemon.log")}`));
7607
7824
  console.log(chalk6.dim(" Auto-starts on login. Auto-restarts on crash (5s delay)."));
7608
7825
  console.log("");
7609
7826
  console.log(chalk6.dim(" Uninstall: mercury service uninstall"));
7610
7827
  console.log("");
7611
7828
  }
7612
7829
  function uninstallLinux() {
7613
- const servicePath = join11(homedir3(), ".config", "systemd", "user", "mercury.service");
7830
+ const servicePath = join13(homedir4(), ".config", "systemd", "user", "mercury.service");
7614
7831
  if (!existsSync18(servicePath)) {
7615
7832
  console.log(chalk6.yellow(" Mercury service is not installed."));
7616
7833
  console.log("");
@@ -7622,7 +7839,7 @@ function uninstallLinux() {
7622
7839
  } catch {
7623
7840
  }
7624
7841
  try {
7625
- unlinkSync4(servicePath);
7842
+ unlinkSync5(servicePath);
7626
7843
  } catch {
7627
7844
  console.log(chalk6.yellow(" Failed to remove service file. Remove manually:"));
7628
7845
  console.log(chalk6.dim(` rm ${servicePath}`));
@@ -7636,7 +7853,7 @@ function uninstallLinux() {
7636
7853
  console.log("");
7637
7854
  }
7638
7855
  function showLinuxStatus() {
7639
- const servicePath = join11(homedir3(), ".config", "systemd", "user", "mercury.service");
7856
+ const servicePath = join13(homedir4(), ".config", "systemd", "user", "mercury.service");
7640
7857
  if (!existsSync18(servicePath)) {
7641
7858
  console.log(chalk6.yellow(" Mercury service is not installed."));
7642
7859
  console.log(chalk6.dim(" Run `mercury service install` to set it up."));
@@ -7656,7 +7873,7 @@ function installWindows() {
7656
7873
  const nodeBin = getNodeBinPath();
7657
7874
  const scriptPath = getDistPath();
7658
7875
  const home = getMercuryHome();
7659
- const logPath2 = join11(home, "daemon.log");
7876
+ const logPath2 = join13(home, "daemon.log");
7660
7877
  const cmd = `"${nodeBin}" "${scriptPath}" start --daemon`;
7661
7878
  try {
7662
7879
  execSync8(
@@ -7938,7 +8155,7 @@ async function fetchProviderModelCatalog(provider, config) {
7938
8155
 
7939
8156
  // src/index.ts
7940
8157
  var __dirname = dirname4(fileURLToPath(import.meta.url));
7941
- var pkgVersion = JSON.parse(readFileSync12(join12(__dirname, "..", "package.json"), "utf8")).version;
8158
+ var pkgVersion = JSON.parse(readFileSync12(join14(__dirname, "..", "package.json"), "utf8")).version;
7942
8159
  function hr() {
7943
8160
  console.log(chalk7.dim("\u2500".repeat(50)));
7944
8161
  }
@@ -8223,14 +8440,14 @@ async function promptValidatedValue(prompt, validator, existingValue, options) {
8223
8440
  }
8224
8441
  }
8225
8442
  function appendToEnv(key, value) {
8226
- const envPath = join12(getMercuryHome(), ".env");
8443
+ const envPath = join14(getMercuryHome(), ".env");
8227
8444
  let envContent = "";
8228
8445
  if (existsSync19(envPath)) {
8229
8446
  envContent = readFileSync12(envPath, "utf-8");
8230
8447
  }
8231
8448
  const lines = envContent.split("\n").filter((l) => !l.startsWith(`${key}=`) && l.trim() !== "");
8232
8449
  lines.push(`${key}=${value}`);
8233
- writeFileSync13(envPath, lines.join("\n") + "\n", "utf-8");
8450
+ writeFileSync14(envPath, lines.join("\n") + "\n", "utf-8");
8234
8451
  process.env[key] = value;
8235
8452
  }
8236
8453
  function parseGithubRepo(input) {
@@ -8618,7 +8835,7 @@ async function runAgent(isDaemon = false) {
8618
8835
  const longTerm = new LongTermMemory(config);
8619
8836
  const episodic = new EpisodicMemory(config);
8620
8837
  let userMemory = null;
8621
- if (config.memory.secondBrain?.enabled !== false) {
8838
+ if (config.memory.secondBrain?.enabled !== false && isBetterSqlite3Available()) {
8622
8839
  try {
8623
8840
  userMemory = new UserMemoryStore(config);
8624
8841
  if (!isDaemon) {
@@ -8630,6 +8847,10 @@ async function runAgent(isDaemon = false) {
8630
8847
  logger.warn({ err }, "Second brain initialization failed, continuing without it");
8631
8848
  userMemory = null;
8632
8849
  }
8850
+ } else if (config.memory.secondBrain?.enabled !== false && !isBetterSqlite3Available()) {
8851
+ logger.warn(
8852
+ "better-sqlite3 is not available \u2014 second brain memory is disabled. To enable it, install build tools (make, gcc/g++, python3) and ensure Node >= 20, then reinstall."
8853
+ );
8633
8854
  }
8634
8855
  const channels = new ChannelRegistry(config);
8635
8856
  const capabilities = new CapabilityRegistry(skillLoader, scheduler, tokenBudget);
@@ -9046,10 +9267,10 @@ program.command("upgrade").description("Upgrade Mercury to the latest version fr
9046
9267
  } catch {
9047
9268
  try {
9048
9269
  const globalDir = execSync9("npm root -g", { encoding: "utf-8" }).trim();
9049
- const pkgDir = join12(globalDir, "@cosmicstack", "mercury-agent");
9050
- const { rmSync: rmSync2 } = await import("fs");
9270
+ const pkgDir = join14(globalDir, "@cosmicstack", "mercury-agent");
9271
+ const { rmSync: rmSync3 } = await import("fs");
9051
9272
  try {
9052
- rmSync2(pkgDir, { recursive: true, force: true });
9273
+ rmSync3(pkgDir, { recursive: true, force: true });
9053
9274
  } catch {
9054
9275
  }
9055
9276
  } catch {