@cosmicstack/mercury-agent 0.1.0 → 0.2.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
@@ -3,7 +3,7 @@
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
5
  import readline2 from "readline";
6
- import chalk3 from "chalk";
6
+ import chalk6 from "chalk";
7
7
  import figlet from "figlet";
8
8
 
9
9
  // src/utils/config.ts
@@ -67,7 +67,8 @@ function getDefaultConfig() {
67
67
  enabled: getEnvBool("TELEGRAM_ENABLED", false),
68
68
  botToken: getEnv("TELEGRAM_BOT_TOKEN", ""),
69
69
  webhookUrl: getEnv("TELEGRAM_WEBHOOK_URL", ""),
70
- allowedChatIds: getEnv("TELEGRAM_ALLOWED_CHAT_IDS", "").split(",").filter(Boolean).map(Number)
70
+ allowedChatIds: getEnv("TELEGRAM_ALLOWED_CHAT_IDS", "").split(",").filter(Boolean).map(Number),
71
+ streaming: getEnvBool("TELEGRAM_STREAMING", true)
71
72
  }
72
73
  },
73
74
  memory: {
@@ -128,10 +129,6 @@ function deepMerge(target, source) {
128
129
  return result;
129
130
  }
130
131
 
131
- // src/soul/identity.ts
132
- import { existsSync as existsSync2, readFileSync as readFileSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
133
- import { join as join2 } from "path";
134
-
135
132
  // src/utils/logger.ts
136
133
  import pino from "pino";
137
134
  var verbose = process.argv.includes("--verbose") || process.argv.includes("-v");
@@ -145,6 +142,8 @@ var logger = pino(
145
142
  );
146
143
 
147
144
  // src/soul/identity.ts
145
+ import { existsSync as existsSync2, readFileSync as readFileSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
146
+ import { join as join2 } from "path";
148
147
  var DEFAULT_SOUL = `# Identity
149
148
 
150
149
  You are {name}. You are not any underlying model \u2014 you are {name}.
@@ -679,6 +678,7 @@ var Agent = class {
679
678
  this.lifecycle = new Lifecycle();
680
679
  this.scheduler = scheduler;
681
680
  this.capabilities = capabilities;
681
+ this.telegramStreaming = config.channels.telegram.streaming ?? true;
682
682
  this.scheduler.setOnScheduledTask(async (manifest) => this.handleScheduledTask(manifest));
683
683
  this.channels.onIncomingMessage((msg) => this.enqueueMessage(msg));
684
684
  this.scheduler.onHeartbeat(async () => {
@@ -699,6 +699,7 @@ var Agent = class {
699
699
  running = false;
700
700
  messageQueue = [];
701
701
  processing = false;
702
+ telegramStreaming;
702
703
  enqueueMessage(msg) {
703
704
  logger.info({ from: msg.channelType, content: msg.content.slice(0, 50) }, "Message enqueued");
704
705
  this.messageQueue.push(msg);
@@ -758,6 +759,43 @@ var Agent = class {
758
759
  this.lifecycle.transition("idle");
759
760
  return;
760
761
  }
762
+ if (trimmed === "/budget_override") {
763
+ await this.handleBudgetCommand("override", msg.channelType, msg.channelId);
764
+ this.lifecycle.transition("idle");
765
+ return;
766
+ }
767
+ if (trimmed === "/budget_reset") {
768
+ await this.handleBudgetCommand("reset", msg.channelType, msg.channelId);
769
+ this.lifecycle.transition("idle");
770
+ return;
771
+ }
772
+ if (trimmed.startsWith("/budget_set")) {
773
+ const args = trimmed.slice("/budget_set".length).trim();
774
+ await this.handleBudgetCommand("set " + args, msg.channelType, msg.channelId);
775
+ this.lifecycle.transition("idle");
776
+ return;
777
+ }
778
+ if (trimmed.startsWith("/stream")) {
779
+ const sub = trimmed.slice("/stream".length).trim().toLowerCase();
780
+ if (sub === "off") {
781
+ this.telegramStreaming = false;
782
+ } else if (sub === "on") {
783
+ this.telegramStreaming = true;
784
+ } else {
785
+ this.telegramStreaming = !this.telegramStreaming;
786
+ }
787
+ const ch = this.channels.get(msg.channelType);
788
+ if (ch) await ch.send(
789
+ this.telegramStreaming ? "Telegram streaming enabled. Responses will appear progressively." : "Telegram streaming disabled. Responses will arrive as a single message.",
790
+ msg.channelId
791
+ );
792
+ this.lifecycle.transition("idle");
793
+ return;
794
+ }
795
+ if (await this.handleChatCommand(trimmed, msg.channelType, msg.channelId)) {
796
+ this.lifecycle.transition("idle");
797
+ return;
798
+ }
761
799
  if (this.tokenBudget.isOverBudget()) {
762
800
  const channel2 = this.channels.getChannelForMessage(msg);
763
801
  if (channel2 && msg.channelType !== "internal") {
@@ -815,7 +853,7 @@ You can override this:
815
853
  let usedProvider = null;
816
854
  let lastError = null;
817
855
  let streamedText = "";
818
- const canStream = msg.channelType === "cli";
856
+ const canStream = msg.channelType === "cli" || msg.channelType === "telegram" && this.telegramStreaming;
819
857
  for (const provider of fallbackIterator) {
820
858
  try {
821
859
  logger.info({ provider: provider.name, model: provider.getModel(), steps: MAX_STEPS, stream: canStream }, "Generating agentic response");
@@ -835,7 +873,22 @@ You can override this:
835
873
  }
836
874
  }
837
875
  });
838
- const fullText = await channel.stream(streamResult.textStream, msg.channelId);
876
+ let fullText;
877
+ if (msg.channelType === "telegram") {
878
+ const tgChannel = this.channels.get("telegram");
879
+ if (tgChannel && "sendStreamToChat" in tgChannel) {
880
+ const chatId = msg.channelId.startsWith("telegram:") ? Number(msg.channelId.split(":")[1]) : Number(msg.channelId);
881
+ if (!isNaN(chatId)) {
882
+ fullText = await tgChannel.sendStreamToChat(chatId, streamResult.textStream);
883
+ } else {
884
+ fullText = await channel.stream(streamResult.textStream, msg.channelId);
885
+ }
886
+ } else {
887
+ fullText = await channel.stream(streamResult.textStream, msg.channelId);
888
+ }
889
+ } else {
890
+ fullText = await channel.stream(streamResult.textStream, msg.channelId);
891
+ }
839
892
  const [usage] = await Promise.all([
840
893
  streamResult.usage
841
894
  ]);
@@ -1099,6 +1152,79 @@ Or use /budget override, /budget reset, /budget set <number> anytime.`,
1099
1152
  await channel.send(`Unknown budget command "${action}". Available: /budget, /budget override, /budget reset, /budget set <number>, /budget status`, channelId);
1100
1153
  }
1101
1154
  }
1155
+ async handleChatCommand(content, channelType, channelId) {
1156
+ const cmd = content.toLowerCase().trim();
1157
+ const channel = this.channels.get(channelType);
1158
+ if (!channel) return false;
1159
+ const ctx = this.capabilities.getChatCommandContext();
1160
+ if (!ctx) return false;
1161
+ if (cmd === "/help") {
1162
+ await channel.send(ctx.manual(), channelId);
1163
+ return true;
1164
+ }
1165
+ if (cmd === "/status") {
1166
+ const config = ctx.config();
1167
+ const budget = ctx.tokenBudget();
1168
+ const lines = [
1169
+ `**${config.identity.name}** \u2014 Status`,
1170
+ `Owner: ${config.identity.owner || "(not set)"}`,
1171
+ `Provider: ${config.providers.default}`,
1172
+ `Telegram: ${config.channels.telegram.enabled ? "enabled" : "disabled"}`,
1173
+ `Budget: ${budget.getStatusText()}`,
1174
+ `Skills: ${ctx.skillNames().length > 0 ? ctx.skillNames().join(", ") : "none"}`
1175
+ ];
1176
+ await channel.send(lines.join("\n"), channelId);
1177
+ return true;
1178
+ }
1179
+ if (cmd === "/tools") {
1180
+ const tools = ctx.toolNames();
1181
+ const grouped = [
1182
+ `**${tools.length} tools loaded:**`,
1183
+ "",
1184
+ ...tools.sort().map((t) => `\u2022 \`${t}\``)
1185
+ ];
1186
+ await channel.send(grouped.join("\n"), channelId);
1187
+ return true;
1188
+ }
1189
+ if (cmd === "/skills") {
1190
+ const names = ctx.skillNames();
1191
+ if (names.length === 0) {
1192
+ await channel.send('No skills installed. Ask me to "install skill from <url>" to add one.', channelId);
1193
+ } else {
1194
+ const lines = [
1195
+ `**${names.length} skill${names.length > 1 ? "s" : ""} installed:**`,
1196
+ "",
1197
+ ...names.map((n) => `\u2022 ${n}`)
1198
+ ];
1199
+ await channel.send(lines.join("\n"), channelId);
1200
+ }
1201
+ return true;
1202
+ }
1203
+ if (cmd === "/stream on") {
1204
+ this.telegramStreaming = true;
1205
+ await channel.send("Telegram streaming enabled. Responses will appear progressively.", channelId);
1206
+ return true;
1207
+ }
1208
+ if (cmd === "/stream off") {
1209
+ this.telegramStreaming = false;
1210
+ await channel.send("Telegram streaming disabled. Responses will arrive as a single message.", channelId);
1211
+ return true;
1212
+ }
1213
+ if (cmd === "/stream") {
1214
+ this.telegramStreaming = !this.telegramStreaming;
1215
+ await channel.send(
1216
+ this.telegramStreaming ? "Telegram streaming enabled. Responses will appear progressively." : "Telegram streaming disabled. Responses will arrive as a single message.",
1217
+ channelId
1218
+ );
1219
+ return true;
1220
+ }
1221
+ if (cmd === "/stream off") {
1222
+ this.telegramStreaming = false;
1223
+ await channel.send("Telegram streaming disabled. Responses will arrive as a single message.", channelId);
1224
+ return true;
1225
+ }
1226
+ return false;
1227
+ }
1102
1228
  };
1103
1229
 
1104
1230
  // src/core/scheduler.ts
@@ -1607,6 +1733,10 @@ var TelegramChannel = class extends BaseChannel {
1607
1733
  bot = null;
1608
1734
  ownerChatId = null;
1609
1735
  typingInterval = null;
1736
+ chatCommandContext;
1737
+ setChatCommandContext(ctx) {
1738
+ this.chatCommandContext = ctx;
1739
+ }
1610
1740
  async start() {
1611
1741
  const token = this.config.channels.telegram.botToken;
1612
1742
  if (!token) {
@@ -1637,12 +1767,33 @@ var TelegramChannel = class extends BaseChannel {
1637
1767
  });
1638
1768
  this.bot = bot;
1639
1769
  await bot.start({
1640
- onStart: (info) => {
1770
+ onStart: async (info) => {
1641
1771
  logger.info({ bot: info.username }, "Telegram bot started \u2014 long polling active");
1642
1772
  this.ready = true;
1773
+ await this.registerCommands();
1643
1774
  }
1644
1775
  });
1645
1776
  }
1777
+ async registerCommands() {
1778
+ if (!this.bot) return;
1779
+ const commands = [
1780
+ { command: "help", description: "Show capabilities and commands manual" },
1781
+ { command: "status", description: "Show agent config, budget, and uptime" },
1782
+ { command: "tools", description: "List all loaded tools" },
1783
+ { command: "skills", description: "List installed skills" },
1784
+ { command: "budget", description: "Show token budget status" },
1785
+ { command: "budget_override", description: "Override budget for one request" },
1786
+ { command: "budget_reset", description: "Reset token usage to zero" },
1787
+ { command: "budget_set", description: "Set new daily token budget" },
1788
+ { command: "stream", description: "Toggle text streaming on/off" }
1789
+ ];
1790
+ try {
1791
+ await this.bot.api.setMyCommands(commands);
1792
+ logger.info({ count: commands.length }, "Telegram bot commands registered");
1793
+ } catch (err) {
1794
+ logger.warn({ err: err.message }, "Failed to register Telegram commands (non-critical)");
1795
+ }
1796
+ }
1646
1797
  async stop() {
1647
1798
  this.bot?.stop();
1648
1799
  this.ready = false;
@@ -1739,23 +1890,64 @@ var TelegramChannel = class extends BaseChannel {
1739
1890
  }
1740
1891
  }
1741
1892
  async sendStreamToChat(chatId, textStream) {
1742
- if (!this.bot) return;
1893
+ if (!this.bot) return "";
1894
+ const STREAM_EDIT_INTERVAL = 1500;
1895
+ const STREAM_MIN_LENGTH = 20;
1743
1896
  this.startTypingLoop(chatId);
1744
1897
  try {
1745
1898
  let full = "";
1899
+ let messageId = null;
1900
+ let lastEditTime = 0;
1901
+ let lastEditLength = 0;
1746
1902
  for await (const chunk of textStream) {
1747
1903
  full += chunk;
1904
+ const now = Date.now();
1905
+ const timeSinceLastEdit = now - lastEditTime;
1906
+ const charsSinceLastEdit = full.length - lastEditLength;
1907
+ if (messageId === null && full.length >= STREAM_MIN_LENGTH) {
1908
+ try {
1909
+ const msg = await this.bot.api.sendMessage(chatId, this.escapeHtml(full) + " \u258C", { parse_mode: "HTML" });
1910
+ messageId = msg.message_id;
1911
+ lastEditTime = now;
1912
+ lastEditLength = full.length;
1913
+ } catch {
1914
+ messageId = null;
1915
+ }
1916
+ } else if (messageId !== null && timeSinceLastEdit >= STREAM_EDIT_INTERVAL && charsSinceLastEdit >= 20) {
1917
+ try {
1918
+ await this.bot.api.editMessageText(chatId, messageId, this.escapeHtml(full) + " \u258C", { parse_mode: "HTML" });
1919
+ lastEditTime = now;
1920
+ lastEditLength = full.length;
1921
+ } catch {
1922
+ }
1923
+ }
1748
1924
  }
1749
- const html = mdToTelegram(full);
1750
- try {
1751
- await this.bot.api.sendMessage(chatId, html, { parse_mode: "HTML" });
1752
- } catch {
1753
- await this.bot.api.sendMessage(chatId, this.stripHtml(html));
1925
+ if (messageId !== null) {
1926
+ const html = mdToTelegram(full);
1927
+ try {
1928
+ await this.bot.api.editMessageText(chatId, messageId, html, { parse_mode: "HTML" });
1929
+ } catch {
1930
+ try {
1931
+ await this.bot.api.editMessageText(chatId, messageId, this.stripHtml(html));
1932
+ } catch {
1933
+ }
1934
+ }
1935
+ } else {
1936
+ const html = mdToTelegram(full);
1937
+ try {
1938
+ await this.bot.api.sendMessage(chatId, html, { parse_mode: "HTML" });
1939
+ } catch {
1940
+ await this.bot.api.sendMessage(chatId, this.stripHtml(html));
1941
+ }
1754
1942
  }
1943
+ return full;
1755
1944
  } finally {
1756
1945
  this.stopTypingLoop();
1757
1946
  }
1758
1947
  }
1948
+ escapeHtml(text) {
1949
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1950
+ }
1759
1951
  splitMessage(text, maxLen) {
1760
1952
  if (text.length <= maxLen) return [text];
1761
1953
  const chunks = [];
@@ -1973,7 +2165,7 @@ var TokenBudget = class {
1973
2165
 
1974
2166
  // src/capabilities/permissions.ts
1975
2167
  import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
1976
- import { join as join6, resolve } from "path";
2168
+ import { join as join6, resolve, sep } from "path";
1977
2169
  import { homedir as homedir2 } from "os";
1978
2170
  import { parse as parseYaml3, stringify as stringifyYaml3 } from "yaml";
1979
2171
  var DEFAULT_MANIFEST = {
@@ -2229,7 +2421,7 @@ var PermissionManager = class {
2229
2421
  const scopes = this.manifest.capabilities.filesystem.scopes;
2230
2422
  for (const scope of scopes) {
2231
2423
  const scopeResolved = resolve(scope.path.replace(/^~/, homedir2()));
2232
- if (resolvedPath === scopeResolved || resolvedPath.startsWith(scopeResolved + "/")) {
2424
+ if (resolvedPath === scopeResolved || resolvedPath.startsWith(scopeResolved + sep)) {
2233
2425
  return scope;
2234
2426
  }
2235
2427
  }
@@ -2263,7 +2455,9 @@ var PermissionManager = class {
2263
2455
  const pathPatterns = [
2264
2456
  /(?:^|\s)(\/[^\s]+)/,
2265
2457
  /(?:^|\s)(~\/[^\s]+)/,
2266
- /(?:^|\s)\.\.\/([^\s]+)/
2458
+ /(?:^|\s)\.\.\/([^\s]+)/,
2459
+ /(?:^|\s)([A-Za-z]:\\[^\s]+)/,
2460
+ /(?:^|\s)(\\\\[^\s]+)/
2267
2461
  ];
2268
2462
  for (const p of pathPatterns) {
2269
2463
  const match = command.match(p);
@@ -3110,12 +3304,19 @@ var CapabilityRegistry = class {
3110
3304
  sendFileHandler;
3111
3305
  currentChannelId = "cli";
3112
3306
  currentChannelType = "cli";
3307
+ chatCommandContext;
3113
3308
  constructor(skillLoader, scheduler, tokenBudget) {
3114
3309
  this.permissions = new PermissionManager();
3115
3310
  this.skillLoader = skillLoader;
3116
3311
  this.scheduler = scheduler;
3117
3312
  this.tokenBudget = tokenBudget;
3118
3313
  }
3314
+ setChatCommandContext(ctx) {
3315
+ this.chatCommandContext = ctx;
3316
+ }
3317
+ getChatCommandContext() {
3318
+ return this.chatCommandContext;
3319
+ }
3119
3320
  setChannelContext(channelId, channelType) {
3120
3321
  this.currentChannelId = channelId;
3121
3322
  this.currentChannelType = channelType;
@@ -3322,31 +3523,694 @@ Describe what this skill enables Mercury to do. When invoked via the use_skill t
3322
3523
  }
3323
3524
  };
3324
3525
 
3526
+ // src/utils/manual.ts
3527
+ import chalk3 from "chalk";
3528
+ function getManual() {
3529
+ const sections = [];
3530
+ sections.push("");
3531
+ sections.push(chalk3.bold.cyan(" MERCURY \u2014 Capabilities & Commands"));
3532
+ sections.push(chalk3.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
3533
+ sections.push("");
3534
+ sections.push(chalk3.bold.white(" Built-in Tools"));
3535
+ sections.push(chalk3.dim(" Tools Mercury can use during conversations."));
3536
+ sections.push("");
3537
+ const tools = [
3538
+ ["read_file", "Read file contents", "path (required)"],
3539
+ ["write_file", "Write to an existing file", "path, content"],
3540
+ ["create_file", "Create a new file (+ dirs)", "path, content"],
3541
+ ["edit_file", "Replace specific text in a file", "path, old_string, new_string"],
3542
+ ["list_dir", "List directory contents", "path"],
3543
+ ["delete_file", "Delete a file", "path"],
3544
+ ["run_command", "Execute a shell command", "command"],
3545
+ ["approve_command", "Permanently approve a command type", 'command (e.g. "curl")'],
3546
+ ["fetch_url", "Fetch a URL and return content", "url, format? (text/markdown)"],
3547
+ ["git_status", "Show working tree status", "path?"],
3548
+ ["git_diff", "Show file changes", "path?, staged?"],
3549
+ ["git_log", "Show commit history", "count?, path?"],
3550
+ ["git_add", "Stage files for commit", "paths (array)"],
3551
+ ["git_commit", "Create a commit", "message"],
3552
+ ["git_push", "Push to remote (needs approval)", "remote?, branch?"],
3553
+ ["install_skill", "Install a skill from content or URL", "content? or url?"],
3554
+ ["list_skills", "List installed skills", "\u2014"],
3555
+ ["use_skill", "Invoke a skill by name", "name"],
3556
+ ["schedule_task", "Schedule a recurring or delayed task", "cron? or delay_seconds, description, prompt? or skill_name?"],
3557
+ ["list_scheduled_tasks", "List all scheduled tasks", "\u2014"],
3558
+ ["cancel_scheduled_task", "Cancel a scheduled task", "id"],
3559
+ ["budget_status", "Check token budget", "\u2014"]
3560
+ ];
3561
+ for (const [name, desc, params] of tools) {
3562
+ sections.push(` ${chalk3.cyan(name.padEnd(24))} ${desc}`);
3563
+ sections.push(` ${" ".repeat(24)} ${chalk3.dim(params)}`);
3564
+ }
3565
+ sections.push("");
3566
+ sections.push(chalk3.bold.white(" CLI Commands"));
3567
+ sections.push(chalk3.dim(" Run these from your terminal (no API calls consumed)."));
3568
+ sections.push("");
3569
+ const commands = [
3570
+ ["mercury up", "Start persistently (install service + daemon)"],
3571
+ ["mercury", "Start the agent (same as mercury start)"],
3572
+ ["mercury start", "Start the agent in foreground"],
3573
+ ["mercury start -d", "Start in background (daemon mode)"],
3574
+ ["mercury restart", "Restart a background process"],
3575
+ ["mercury stop", "Stop a background process"],
3576
+ ["mercury logs", "Show recent daemon logs"],
3577
+ ["mercury doctor", "Reconfigure settings (Enter keeps current)"],
3578
+ ["mercury setup", "Re-run the setup wizard"],
3579
+ ["mercury status", "Show config and daemon status"],
3580
+ ["mercury help", "Show this manual"],
3581
+ ["mercury service install", "Install as system service (auto-start)"],
3582
+ ["mercury service uninstall", "Uninstall system service"],
3583
+ ["mercury service status", "Show system service status"],
3584
+ ["mercury --verbose", "Start with debug logging on stderr"]
3585
+ ];
3586
+ for (const [cmd, desc] of commands) {
3587
+ sections.push(` ${chalk3.white(cmd.padEnd(26))} ${desc}`);
3588
+ }
3589
+ sections.push("");
3590
+ sections.push(chalk3.bold.white(" In-Chat Commands"));
3591
+ sections.push(chalk3.dim(" Type these during a conversation (no API calls)."));
3592
+ sections.push("");
3593
+ const chat = [
3594
+ ["/help", "Show this manual"],
3595
+ ["/status", "Show config and budget info"],
3596
+ ["/tools", "List currently loaded tools"],
3597
+ ["/skills", "List installed skills"],
3598
+ ["/stream", "Toggle text streaming on/off (Telegram)"],
3599
+ ["/stream on", "Enable streaming (live text updates)"],
3600
+ ["/stream off", "Disable streaming (single message)"]
3601
+ ];
3602
+ for (const [cmd, desc] of chat) {
3603
+ sections.push(` ${chalk3.white(cmd.padEnd(16))} ${desc}`);
3604
+ }
3605
+ sections.push("");
3606
+ sections.push(chalk3.bold.white(" Permissions"));
3607
+ sections.push("");
3608
+ const perms = [
3609
+ "Commands are blocked (never run), auto-approved, or need approval.",
3610
+ 'Say "always" when prompted to permanently approve a command type.',
3611
+ "Edit ~/.mercury/permissions.yaml to customize manually.",
3612
+ "File access is scoped \u2014 new paths need approval (y/n/always)."
3613
+ ];
3614
+ for (const p of perms) {
3615
+ sections.push(` ${chalk3.dim("\u2022")} ${p}`);
3616
+ }
3617
+ sections.push("");
3618
+ sections.push(chalk3.bold.white(" Skills"));
3619
+ sections.push("");
3620
+ const skillInfo = [
3621
+ "Skills live in ~/.mercury/skills/<name>/SKILL.md",
3622
+ 'Install: ask Mercury to "install skill from <url>" or paste content',
3623
+ 'Invoke: ask Mercury to "use skill <name>"',
3624
+ 'Schedule: "remind me daily at 9am to run daily-digest skill"'
3625
+ ];
3626
+ for (const s of skillInfo) {
3627
+ sections.push(` ${chalk3.dim("\u2022")} ${s}`);
3628
+ }
3629
+ sections.push("");
3630
+ sections.push(chalk3.bold.white(" Scheduling"));
3631
+ sections.push("");
3632
+ const schedInfo = [
3633
+ 'Recurring: "every day at 9am remind me to\u2026"',
3634
+ 'One-shot: "remind me in 15 seconds to\u2026"',
3635
+ "Tasks persist to ~/.mercury/schedules.yaml"
3636
+ ];
3637
+ for (const s of schedInfo) {
3638
+ sections.push(` ${chalk3.dim("\u2022")} ${s}`);
3639
+ }
3640
+ sections.push("");
3641
+ sections.push(chalk3.bold.white(" Configuration"));
3642
+ sections.push("");
3643
+ const configInfo = [
3644
+ ["~/.mercury/mercury.yaml", "Main config (providers, channels, budget)"],
3645
+ ["~/.mercury/permissions.yaml", "Capabilities and approval rules"],
3646
+ ["~/.mercury/soul/*.md", "Agent personality (soul, persona, taste, heartbeat)"],
3647
+ ["~/.mercury/skills/", "Installed skills"],
3648
+ ["~/.mercury/schedules.yaml", "Scheduled tasks"],
3649
+ ["~/.mercury/token-usage.json", "Daily token usage tracking"],
3650
+ ["~/.mercury/memory/", "Short-term, long-term, episodic memory"]
3651
+ ];
3652
+ for (const [path3, desc] of configInfo) {
3653
+ sections.push(` ${chalk3.dim(path3.padEnd(36))} ${desc}`);
3654
+ }
3655
+ sections.push("");
3656
+ sections.push(chalk3.dim(" mercury.cosmicstack.org"));
3657
+ sections.push("");
3658
+ return sections.join("\n");
3659
+ }
3660
+
3661
+ // src/cli/daemon.ts
3662
+ import { spawn } from "child_process";
3663
+ import { existsSync as existsSync14, readFileSync as readFileSync11, writeFileSync as writeFileSync11, unlinkSync as unlinkSync3, mkdirSync as mkdirSync9, openSync } from "fs";
3664
+ import { join as join9 } from "path";
3665
+ import process2 from "process";
3666
+ import chalk4 from "chalk";
3667
+ var PID_FILE = "daemon.pid";
3668
+ var LOG_FILE = "daemon.log";
3669
+ function pidPath() {
3670
+ return join9(getMercuryHome(), PID_FILE);
3671
+ }
3672
+ function logPath() {
3673
+ return join9(getMercuryHome(), LOG_FILE);
3674
+ }
3675
+ function readPid() {
3676
+ const path3 = pidPath();
3677
+ if (!existsSync14(path3)) return null;
3678
+ try {
3679
+ const pid = parseInt(readFileSync11(path3, "utf-8").trim(), 10);
3680
+ if (isNaN(pid)) return null;
3681
+ return pid;
3682
+ } catch {
3683
+ return null;
3684
+ }
3685
+ }
3686
+ function isProcessRunning(pid) {
3687
+ try {
3688
+ process2.kill(pid, 0);
3689
+ return true;
3690
+ } catch {
3691
+ return false;
3692
+ }
3693
+ }
3694
+ function getDaemonStatus() {
3695
+ const pid = readPid();
3696
+ if (!pid) return { running: false, pid: null, logPath: logPath() };
3697
+ return { running: isProcessRunning(pid), pid, logPath: logPath() };
3698
+ }
3699
+ function startBackground() {
3700
+ const status = getDaemonStatus();
3701
+ if (status.running && status.pid) {
3702
+ console.log(chalk4.yellow(` Mercury is already running (PID: ${status.pid})`));
3703
+ console.log(chalk4.dim(` Use \`mercury stop\` to stop it first.`));
3704
+ console.log("");
3705
+ process2.exit(1);
3706
+ }
3707
+ if (status.pid && !status.running) {
3708
+ try {
3709
+ unlinkSync3(pidPath());
3710
+ } catch {
3711
+ }
3712
+ }
3713
+ const home = getMercuryHome();
3714
+ if (!existsSync14(home)) {
3715
+ mkdirSync9(home, { recursive: true });
3716
+ }
3717
+ const logFile = logPath();
3718
+ const isWin = process2.platform === "win32";
3719
+ const outFd = openSync(logFile, "a");
3720
+ const child = spawn(process2.execPath, [process2.argv[1], "start", "--daemon"], {
3721
+ detached: true,
3722
+ stdio: ["ignore", outFd, outFd],
3723
+ env: { ...process2.env },
3724
+ windowsHide: isWin
3725
+ });
3726
+ child.unref();
3727
+ writeFileSync11(pidPath(), String(child.pid));
3728
+ console.log("");
3729
+ console.log(chalk4.green(` Mercury started in background (PID: ${child.pid})`));
3730
+ console.log(chalk4.dim(` Logs: ${logFile}`));
3731
+ console.log(chalk4.dim(` Use \`mercury stop\` to stop.`));
3732
+ console.log(chalk4.dim(` Use \`mercury logs\` to view logs.`));
3733
+ console.log("");
3734
+ }
3735
+ function stopDaemon() {
3736
+ const status = getDaemonStatus();
3737
+ if (!status.pid) {
3738
+ console.log(chalk4.yellow(" Mercury is not running as a daemon."));
3739
+ console.log("");
3740
+ process2.exit(0);
3741
+ }
3742
+ if (!status.running) {
3743
+ console.log(chalk4.yellow(` Stale PID file found (PID: ${status.pid} is not running). Cleaning up.`));
3744
+ try {
3745
+ unlinkSync3(pidPath());
3746
+ } catch {
3747
+ }
3748
+ console.log("");
3749
+ process2.exit(0);
3750
+ }
3751
+ try {
3752
+ if (process2.platform === "win32") {
3753
+ process2.kill(status.pid);
3754
+ } else {
3755
+ process2.kill(status.pid, "SIGTERM");
3756
+ }
3757
+ console.log(chalk4.green(` Mercury stopped (PID: ${status.pid})`));
3758
+ } catch {
3759
+ console.log(chalk4.red(` Failed to stop PID ${status.pid}. You may need to kill it manually.`));
3760
+ }
3761
+ try {
3762
+ unlinkSync3(pidPath());
3763
+ } catch {
3764
+ }
3765
+ console.log("");
3766
+ }
3767
+ function restartDaemon() {
3768
+ const status = getDaemonStatus();
3769
+ if (status.running && status.pid) {
3770
+ console.log(chalk4.yellow(` Stopping Mercury (PID: ${status.pid})...`));
3771
+ try {
3772
+ if (process2.platform === "win32") {
3773
+ process2.kill(status.pid);
3774
+ } else {
3775
+ process2.kill(status.pid, "SIGTERM");
3776
+ }
3777
+ } catch {
3778
+ }
3779
+ try {
3780
+ unlinkSync3(pidPath());
3781
+ } catch {
3782
+ }
3783
+ console.log(chalk4.green(" Mercury stopped."));
3784
+ } else if (status.pid) {
3785
+ try {
3786
+ unlinkSync3(pidPath());
3787
+ } catch {
3788
+ }
3789
+ }
3790
+ console.log(chalk4.yellow(" Starting Mercury..."));
3791
+ startBackground();
3792
+ }
3793
+ function showLogs() {
3794
+ const logFile = logPath();
3795
+ if (!existsSync14(logFile)) {
3796
+ console.log(chalk4.dim(" No daemon log file found."));
3797
+ console.log("");
3798
+ return;
3799
+ }
3800
+ const content = readFileSync11(logFile, "utf-8");
3801
+ const lines = content.split("\n").slice(-100);
3802
+ console.log(lines.join("\n"));
3803
+ }
3804
+ function tryAutoDaemonize() {
3805
+ try {
3806
+ const status = getDaemonStatus();
3807
+ if (status.running && status.pid) {
3808
+ return true;
3809
+ }
3810
+ if (status.pid && !status.running) {
3811
+ try {
3812
+ unlinkSync3(pidPath());
3813
+ } catch {
3814
+ }
3815
+ }
3816
+ const home = getMercuryHome();
3817
+ if (!existsSync14(home)) {
3818
+ mkdirSync9(home, { recursive: true });
3819
+ }
3820
+ const logFile = logPath();
3821
+ const isWin = process2.platform === "win32";
3822
+ const outFd = openSync(logFile, "a");
3823
+ const child = spawn(process2.execPath, [process2.argv[1], "start", "--daemon"], {
3824
+ detached: true,
3825
+ stdio: ["ignore", outFd, outFd],
3826
+ env: { ...process2.env },
3827
+ windowsHide: isWin
3828
+ });
3829
+ child.unref();
3830
+ if (!child.pid) {
3831
+ return false;
3832
+ }
3833
+ writeFileSync11(pidPath(), String(child.pid));
3834
+ return true;
3835
+ } catch {
3836
+ return false;
3837
+ }
3838
+ }
3839
+
3840
+ // src/cli/service.ts
3841
+ import { existsSync as existsSync15, mkdirSync as mkdirSync10, writeFileSync as writeFileSync12, unlinkSync as unlinkSync4 } from "fs";
3842
+ import { join as join10 } from "path";
3843
+ import { homedir as homedir3 } from "os";
3844
+ import chalk5 from "chalk";
3845
+ import { execSync as execSync8 } from "child_process";
3846
+ var SERVICE_DESC = "Mercury \u2014 Soul-Driven AI Agent";
3847
+ var WIN_TASK_NAME = "MercuryAgent";
3848
+ function isServiceInstalled() {
3849
+ const platform = process.platform;
3850
+ if (platform === "darwin") {
3851
+ return existsSync15(join10(homedir3(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist"));
3852
+ } else if (platform === "linux") {
3853
+ return existsSync15(join10(homedir3(), ".config", "systemd", "user", "mercury.service"));
3854
+ } else if (platform === "win32") {
3855
+ try {
3856
+ execSync8(`schtasks /query /tn "${WIN_TASK_NAME}"`, { stdio: "pipe", shell: "cmd.exe" });
3857
+ return true;
3858
+ } catch {
3859
+ return false;
3860
+ }
3861
+ }
3862
+ return false;
3863
+ }
3864
+ function getNodeBinPath() {
3865
+ return process.execPath;
3866
+ }
3867
+ function getDistPath() {
3868
+ return join10(process.argv[1] || "/usr/local/bin/mercury", "..", "..", "lib", "node_modules", "@cosmicstack", "mercury-agent", "dist", "index.js");
3869
+ }
3870
+ function installService() {
3871
+ const platform = process.platform;
3872
+ if (platform === "darwin") {
3873
+ installMac();
3874
+ } else if (platform === "linux") {
3875
+ installLinux();
3876
+ } else if (platform === "win32") {
3877
+ installWindows();
3878
+ } else {
3879
+ console.log(chalk5.red(` Unsupported platform: ${platform}`));
3880
+ process.exit(1);
3881
+ }
3882
+ }
3883
+ function uninstallService() {
3884
+ const platform = process.platform;
3885
+ if (platform === "darwin") {
3886
+ uninstallMac();
3887
+ } else if (platform === "linux") {
3888
+ uninstallLinux();
3889
+ } else if (platform === "win32") {
3890
+ uninstallWindows();
3891
+ } else {
3892
+ console.log(chalk5.red(` Unsupported platform: ${platform}`));
3893
+ process.exit(1);
3894
+ }
3895
+ }
3896
+ function showServiceStatus() {
3897
+ const platform = process.platform;
3898
+ if (platform === "darwin") {
3899
+ showMacStatus();
3900
+ } else if (platform === "linux") {
3901
+ showLinuxStatus();
3902
+ } else if (platform === "win32") {
3903
+ showWindowsStatus();
3904
+ }
3905
+ }
3906
+ function installMac() {
3907
+ const plistDir = join10(homedir3(), "Library", "LaunchAgents");
3908
+ const plistPath = join10(plistDir, "com.cosmicstack.mercury.plist");
3909
+ if (!existsSync15(plistDir)) {
3910
+ mkdirSync10(plistDir, { recursive: true });
3911
+ }
3912
+ const nodeBin = getNodeBinPath();
3913
+ const scriptPath = getDistPath();
3914
+ const home = getMercuryHome();
3915
+ const logPath2 = join10(home, "daemon.log");
3916
+ const errPath = join10(home, "daemon-error.log");
3917
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
3918
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3919
+ <plist version="1.0">
3920
+ <dict>
3921
+ <key>Label</key>
3922
+ <string>com.cosmicstack.mercury</string>
3923
+ <key>ProgramArguments</key>
3924
+ <array>
3925
+ <string>${nodeBin}</string>
3926
+ <string>${scriptPath}</string>
3927
+ <string>start</string>
3928
+ <string>--daemon</string>
3929
+ </array>
3930
+ <key>RunAtLoad</key>
3931
+ <true/>
3932
+ <key>KeepAlive</key>
3933
+ <dict>
3934
+ <key>SuccessfulExit</key>
3935
+ <false/>
3936
+ </dict>
3937
+ <key>StandardOutPath</key>
3938
+ <string>${logPath2}</string>
3939
+ <key>StandardErrorPath</key>
3940
+ <string>${errPath}</string>
3941
+ <key>EnvironmentVariables</key>
3942
+ <dict>
3943
+ <key>PATH</key>
3944
+ <string>${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}</string>
3945
+ <key>HOME</key>
3946
+ <string>${homedir3()}</string>
3947
+ </dict>
3948
+ <key>WorkingDirectory</key>
3949
+ <string>${homedir3()}</string>
3950
+ </dict>
3951
+ </plist>`;
3952
+ writeFileSync12(plistPath, plist, "utf-8");
3953
+ try {
3954
+ execSync8(`launchctl load ${plistPath}`, { stdio: "inherit" });
3955
+ } catch {
3956
+ console.log(chalk5.yellow(" launchctl load failed. Try running:"));
3957
+ console.log(chalk5.dim(` launchctl load ${plistPath}`));
3958
+ }
3959
+ console.log("");
3960
+ console.log(chalk5.green(" Mercury service installed (macOS LaunchAgent)"));
3961
+ console.log(chalk5.dim(` Plist: ${plistPath}`));
3962
+ console.log(chalk5.dim(` Logs: ${logPath2}`));
3963
+ console.log(chalk5.dim(" Auto-starts on login. Auto-restarts on crash."));
3964
+ console.log("");
3965
+ console.log(chalk5.dim(" Uninstall: mercury service uninstall"));
3966
+ console.log("");
3967
+ }
3968
+ function uninstallMac() {
3969
+ const plistPath = join10(homedir3(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist");
3970
+ if (!existsSync15(plistPath)) {
3971
+ console.log(chalk5.yellow(" Mercury service is not installed."));
3972
+ console.log("");
3973
+ process.exit(0);
3974
+ }
3975
+ try {
3976
+ execSync8(`launchctl unload ${plistPath}`, { stdio: "inherit" });
3977
+ } catch {
3978
+ }
3979
+ try {
3980
+ unlinkSync4(plistPath);
3981
+ } catch {
3982
+ console.log(chalk5.yellow(" Failed to remove plist file. Remove manually:"));
3983
+ console.log(chalk5.dim(` rm ${plistPath}`));
3984
+ }
3985
+ console.log("");
3986
+ console.log(chalk5.green(" Mercury service uninstalled"));
3987
+ console.log("");
3988
+ }
3989
+ function showMacStatus() {
3990
+ const plistPath = join10(homedir3(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist");
3991
+ if (!existsSync15(plistPath)) {
3992
+ console.log(chalk5.yellow(" Mercury service is not installed."));
3993
+ console.log(chalk5.dim(" Run `mercury service install` to set it up."));
3994
+ console.log("");
3995
+ return;
3996
+ }
3997
+ try {
3998
+ const output = execSync8("launchctl list | grep com.cosmicstack.mercury", { encoding: "utf-8" }).trim();
3999
+ console.log(` ${chalk5.green("Service installed and loaded")}`);
4000
+ console.log(chalk5.dim(` ${output}`));
4001
+ } catch {
4002
+ console.log(` ${chalk5.yellow("Service installed but not loaded")}`);
4003
+ console.log(chalk5.dim(` Plist: ${plistPath}`));
4004
+ }
4005
+ console.log("");
4006
+ }
4007
+ function installLinux() {
4008
+ const systemdDir = join10(homedir3(), ".config", "systemd", "user");
4009
+ if (!existsSync15(systemdDir)) {
4010
+ mkdirSync10(systemdDir, { recursive: true });
4011
+ }
4012
+ const servicePath = join10(systemdDir, "mercury.service");
4013
+ const nodeBin = getNodeBinPath();
4014
+ const scriptPath = getDistPath();
4015
+ const home = getMercuryHome();
4016
+ const service = `[Unit]
4017
+ Description=${SERVICE_DESC}
4018
+ After=network.target
4019
+
4020
+ [Service]
4021
+ Type=simple
4022
+ ExecStart=${nodeBin} ${scriptPath} start --daemon
4023
+ Restart=on-failure
4024
+ RestartSec=5
4025
+ Environment=PATH=${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}
4026
+ Environment=HOME=${homedir3()}
4027
+ WorkingDirectory=${homedir3()}
4028
+ StandardOutput=append:${join10(home, "daemon.log")}
4029
+ StandardError=append:${join10(home, "daemon-error.log")}
4030
+
4031
+ [Install]
4032
+ WantedBy=default.target`;
4033
+ writeFileSync12(servicePath, service, "utf-8");
4034
+ try {
4035
+ execSync8("systemctl --user daemon-reload", { stdio: "inherit" });
4036
+ execSync8("systemctl --user enable mercury.service", { stdio: "inherit" });
4037
+ execSync8("systemctl --user start mercury.service", { stdio: "inherit" });
4038
+ } catch (err) {
4039
+ console.log(chalk5.yellow(" systemd commands failed. Try running manually:"));
4040
+ console.log(chalk5.dim(" systemctl --user daemon-reload"));
4041
+ console.log(chalk5.dim(" systemctl --user enable mercury.service"));
4042
+ console.log(chalk5.dim(" systemctl --user start mercury.service"));
4043
+ }
4044
+ try {
4045
+ execSync8(`loginctl enable-linger ${process.env.USER || ""}`, { stdio: "inherit" });
4046
+ } catch {
4047
+ console.log(chalk5.yellow(" Enable linger failed (needed for boot-without-login). Try:"));
4048
+ console.log(chalk5.dim(` sudo loginctl enable-linger ${process.env.USER || "$USER"}`));
4049
+ }
4050
+ console.log("");
4051
+ console.log(chalk5.green(" Mercury service installed (systemd --user)"));
4052
+ console.log(chalk5.dim(` Service: ${servicePath}`));
4053
+ console.log(chalk5.dim(` Logs: ${join10(home, "daemon.log")}`));
4054
+ console.log(chalk5.dim(" Auto-starts on login. Auto-restarts on crash (5s delay)."));
4055
+ console.log("");
4056
+ console.log(chalk5.dim(" Uninstall: mercury service uninstall"));
4057
+ console.log("");
4058
+ }
4059
+ function uninstallLinux() {
4060
+ const servicePath = join10(homedir3(), ".config", "systemd", "user", "mercury.service");
4061
+ if (!existsSync15(servicePath)) {
4062
+ console.log(chalk5.yellow(" Mercury service is not installed."));
4063
+ console.log("");
4064
+ process.exit(0);
4065
+ }
4066
+ try {
4067
+ execSync8("systemctl --user stop mercury.service", { stdio: "inherit" });
4068
+ execSync8("systemctl --user disable mercury.service", { stdio: "inherit" });
4069
+ } catch {
4070
+ }
4071
+ try {
4072
+ unlinkSync4(servicePath);
4073
+ } catch {
4074
+ console.log(chalk5.yellow(" Failed to remove service file. Remove manually:"));
4075
+ console.log(chalk5.dim(` rm ${servicePath}`));
4076
+ }
4077
+ try {
4078
+ execSync8("systemctl --user daemon-reload", { stdio: "inherit" });
4079
+ } catch {
4080
+ }
4081
+ console.log("");
4082
+ console.log(chalk5.green(" Mercury service uninstalled"));
4083
+ console.log("");
4084
+ }
4085
+ function showLinuxStatus() {
4086
+ const servicePath = join10(homedir3(), ".config", "systemd", "user", "mercury.service");
4087
+ if (!existsSync15(servicePath)) {
4088
+ console.log(chalk5.yellow(" Mercury service is not installed."));
4089
+ console.log(chalk5.dim(" Run `mercury service install` to set it up."));
4090
+ console.log("");
4091
+ return;
4092
+ }
4093
+ try {
4094
+ const output = execSync8("systemctl --user status mercury.service", { encoding: "utf-8" }).trim();
4095
+ console.log(output);
4096
+ } catch (err) {
4097
+ console.log(chalk5.yellow(" Could not get service status:"));
4098
+ console.log(chalk5.dim(` ${err.message || err}`));
4099
+ }
4100
+ console.log("");
4101
+ }
4102
+ function installWindows() {
4103
+ const nodeBin = getNodeBinPath();
4104
+ const scriptPath = getDistPath();
4105
+ const home = getMercuryHome();
4106
+ const logPath2 = join10(home, "daemon.log");
4107
+ const cmd = `"${nodeBin}" "${scriptPath}" start --daemon`;
4108
+ try {
4109
+ execSync8(
4110
+ `schtasks /create /tn "${WIN_TASK_NAME}" /tr "${cmd}" /sc onlogon /rl limited /f`,
4111
+ { stdio: "inherit", shell: "cmd.exe" }
4112
+ );
4113
+ } catch {
4114
+ console.log(chalk5.yellow(" schtasks create failed. Try running from an Administrator cmd:"));
4115
+ console.log(chalk5.dim(` schtasks /create /tn "${WIN_TASK_NAME}" /tr "${cmd}" /sc onlogon /rl limited /f`));
4116
+ }
4117
+ try {
4118
+ execSync8(`schtasks /run /tn "${WIN_TASK_NAME}"`, { stdio: "inherit", shell: "cmd.exe" });
4119
+ } catch {
4120
+ console.log(chalk5.yellow(" Task created but failed to start immediately. It will start on next login."));
4121
+ }
4122
+ console.log("");
4123
+ console.log(chalk5.green(" Mercury service installed (Windows Task Scheduler)"));
4124
+ console.log(chalk5.dim(` Task: ${WIN_TASK_NAME}`));
4125
+ console.log(chalk5.dim(` Trigger: on logon`));
4126
+ console.log(chalk5.dim(` Logs: ${logPath2}`));
4127
+ console.log(chalk5.dim(" Auto-starts on login. Use --daemon flag for crash recovery."));
4128
+ console.log("");
4129
+ console.log(chalk5.dim(" Uninstall: mercury service uninstall"));
4130
+ console.log("");
4131
+ }
4132
+ function uninstallWindows() {
4133
+ try {
4134
+ execSync8(`schtasks /delete /tn "${WIN_TASK_NAME}" /f`, { stdio: "inherit", shell: "cmd.exe" });
4135
+ console.log("");
4136
+ console.log(chalk5.green(" Mercury service uninstalled"));
4137
+ console.log("");
4138
+ } catch {
4139
+ console.log(chalk5.yellow(" Task not found or failed to delete. Remove manually:"));
4140
+ console.log(chalk5.dim(` schtasks /delete /tn "${WIN_TASK_NAME}" /f`));
4141
+ console.log("");
4142
+ }
4143
+ }
4144
+ function showWindowsStatus() {
4145
+ try {
4146
+ const output = execSync8(`schtasks /query /tn "${WIN_TASK_NAME}" /fo list`, {
4147
+ encoding: "utf-8",
4148
+ shell: "cmd.exe"
4149
+ }).trim();
4150
+ console.log(output);
4151
+ console.log("");
4152
+ } catch {
4153
+ console.log(chalk5.yellow(" Mercury service is not installed."));
4154
+ console.log(chalk5.dim(" Run `mercury service install` to set it up."));
4155
+ console.log("");
4156
+ }
4157
+ }
4158
+
4159
+ // src/cli/watchdog.ts
4160
+ var MAX_RESTARTS = 10;
4161
+ var RESTART_WINDOW_MS = 6e4;
4162
+ var BASE_DELAY_MS = 1e3;
4163
+ async function runWithWatchdog(agentFn) {
4164
+ const restarts = [];
4165
+ async function attempt() {
4166
+ try {
4167
+ await agentFn();
4168
+ } catch (err) {
4169
+ const now = Date.now();
4170
+ restarts.push(now);
4171
+ const recentRestarts = restarts.filter((t) => now - t < RESTART_WINDOW_MS);
4172
+ const restartCount = recentRestarts.length;
4173
+ if (restartCount >= MAX_RESTARTS) {
4174
+ logger.error({ restartCount }, "Max restarts exceeded within 60s. Exiting.");
4175
+ process.exit(1);
4176
+ }
4177
+ const delay = BASE_DELAY_MS * Math.pow(1.25, restartCount);
4178
+ logger.warn({ err, restartCount, delay }, "Crash detected. Restarting with backoff...");
4179
+ await sleep(delay);
4180
+ await attempt();
4181
+ }
4182
+ }
4183
+ await attempt();
4184
+ }
4185
+ function sleep(ms) {
4186
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
4187
+ }
4188
+
3325
4189
  // src/index.ts
3326
4190
  function hr() {
3327
- console.log(chalk3.dim("\u2500".repeat(50)));
4191
+ console.log(chalk6.dim("\u2500".repeat(50)));
3328
4192
  }
3329
4193
  function banner() {
3330
4194
  console.log("");
3331
4195
  const art = figlet.textSync("MERCURY", { font: "Slant", horizontalLayout: "default" });
3332
4196
  for (const line of art.split("\n")) {
3333
- if (line.trim()) console.log(chalk3.bold.cyan(` ${line}`));
4197
+ if (line.trim()) console.log(chalk6.bold.cyan(` ${line}`));
3334
4198
  }
3335
4199
  console.log("");
3336
- console.log(chalk3.white(" an AI agent for personal tasks"));
3337
- console.log(chalk3.dim(" v0.1.0 \xB7 by Cosmic Stack \xB7 mercury.cosmicstack.org"));
4200
+ console.log(chalk6.white(" an AI agent for personal tasks"));
4201
+ console.log(chalk6.dim(" v0.2.1 \xB7 by Cosmic Stack \xB7 mercury.cosmicstack.org"));
3338
4202
  console.log("");
3339
4203
  }
3340
4204
  function splashScreen() {
3341
4205
  console.log("");
3342
4206
  const art = figlet.textSync("MERCURY", { font: "Slant", horizontalLayout: "default" });
3343
4207
  for (const line of art.split("\n")) {
3344
- if (line.trim()) console.log(chalk3.bold.cyan(` ${line}`));
4208
+ if (line.trim()) console.log(chalk6.bold.cyan(` ${line}`));
3345
4209
  }
3346
4210
  console.log("");
3347
- console.log(chalk3.dim(" an AI agent for personal tasks"));
3348
- console.log(chalk3.cyan(" by Cosmic Stack"));
3349
- console.log(chalk3.dim(" mercury.cosmicstack.org"));
4211
+ console.log(chalk6.dim(" an AI agent for personal tasks"));
4212
+ console.log(chalk6.cyan(" by Cosmic Stack"));
4213
+ console.log(chalk6.dim(" mercury.cosmicstack.org"));
3350
4214
  console.log("");
3351
4215
  }
3352
4216
  async function ask(prompt) {
@@ -3358,81 +4222,189 @@ async function ask(prompt) {
3358
4222
  });
3359
4223
  });
3360
4224
  }
3361
- async function onboarding() {
3362
- splashScreen();
3363
- console.log(chalk3.yellow(" First run detected \u2014 let's set you up."));
4225
+ function maskKey(key) {
4226
+ if (!key) return "";
4227
+ if (key.length <= 8) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
4228
+ return key.slice(0, 4) + "\u2022\u2022\u2022\u2022" + key.slice(-4);
4229
+ }
4230
+ async function configure(existingConfig) {
4231
+ const isReconfig = !!existingConfig;
4232
+ const config = existingConfig ?? loadConfig();
4233
+ if (isReconfig) {
4234
+ banner();
4235
+ console.log(chalk6.yellow(" Reconfiguring Mercury \u2014 press Enter to keep current value."));
4236
+ } else {
4237
+ splashScreen();
4238
+ console.log(chalk6.yellow(" First run detected \u2014 let's set you up."));
4239
+ }
3364
4240
  hr();
3365
4241
  console.log("");
3366
- const config = loadConfig();
3367
- const ownerName = await ask(chalk3.white(" Your name: "));
3368
- if (!ownerName) {
3369
- console.log(chalk3.red(" Name is required."));
3370
- process.exit(1);
4242
+ console.log(chalk6.bold.white(" Identity"));
4243
+ console.log("");
4244
+ if (isReconfig) {
4245
+ const ownerName = await ask(chalk6.white(` Your name [${config.identity.owner}]: `));
4246
+ if (ownerName) config.identity.owner = ownerName;
4247
+ const agentName = await ask(chalk6.white(` Agent name [${config.identity.name}]: `));
4248
+ if (agentName) config.identity.name = agentName;
4249
+ } else {
4250
+ const ownerName = await ask(chalk6.white(" Your name: "));
4251
+ if (!ownerName) {
4252
+ console.log(chalk6.red(" Name is required."));
4253
+ process.exit(1);
4254
+ }
4255
+ config.identity.owner = ownerName;
4256
+ const agentName = await ask(chalk6.white(` Agent name [${config.identity.name}]: `));
4257
+ if (agentName) config.identity.name = agentName;
3371
4258
  }
3372
- config.identity.owner = ownerName;
3373
- const agentName = await ask(chalk3.white(` Agent name [${config.identity.name}]: `));
3374
- if (agentName) config.identity.name = agentName;
3375
- config.identity.creator = "Cosmic Stack";
4259
+ config.identity.creator = config.identity.creator || "Cosmic Stack";
3376
4260
  hr();
3377
4261
  console.log("");
3378
- console.log(chalk3.white(" LLM Providers"));
3379
- console.log(chalk3.dim(" At least one API key is required."));
4262
+ console.log(chalk6.bold.white(" LLM Providers"));
4263
+ if (isReconfig) {
4264
+ console.log(chalk6.dim(" Current keys shown masked. Enter new value to change, Enter to keep."));
4265
+ } else {
4266
+ console.log(chalk6.dim(" At least one API key is required."));
4267
+ }
3380
4268
  console.log("");
3381
- const deepseekKey = await ask(chalk3.white(" DeepSeek API key: "));
4269
+ const dsMask = isReconfig && config.providers.deepseek.apiKey ? ` [${maskKey(config.providers.deepseek.apiKey)}]` : "";
4270
+ const deepseekKey = await ask(chalk6.white(` DeepSeek API key${dsMask}: `));
3382
4271
  if (deepseekKey) {
3383
4272
  config.providers.deepseek.apiKey = deepseekKey;
3384
- config.providers.default = "deepseek";
3385
4273
  }
3386
- const openaiKey = await ask(chalk3.white(" OpenAI API key (Enter to skip): "));
4274
+ const oaiMask = isReconfig && config.providers.openai.apiKey ? ` [${maskKey(config.providers.openai.apiKey)}]` : " (Enter to skip)";
4275
+ const openaiKey = await ask(chalk6.white(` OpenAI API key${oaiMask}: `));
3387
4276
  if (openaiKey) config.providers.openai.apiKey = openaiKey;
3388
- const anthropicKey = await ask(chalk3.white(" Anthropic API key (Enter to skip): "));
4277
+ const antMask = isReconfig && config.providers.anthropic.apiKey ? ` [${maskKey(config.providers.anthropic.apiKey)}]` : " (Enter to skip)";
4278
+ const anthropicKey = await ask(chalk6.white(` Anthropic API key${antMask}: `));
3389
4279
  if (anthropicKey) config.providers.anthropic.apiKey = anthropicKey;
3390
- if (!deepseekKey && !openaiKey && !anthropicKey) {
3391
- console.log(chalk3.red("\n At least one LLM API key is required."));
4280
+ const hasKey = config.providers.deepseek.apiKey || config.providers.openai.apiKey || config.providers.anthropic.apiKey;
4281
+ if (!hasKey) {
4282
+ console.log(chalk6.red("\n At least one LLM API key is required."));
3392
4283
  process.exit(1);
3393
4284
  }
4285
+ const availableProviders = [];
4286
+ if (config.providers.deepseek.apiKey) availableProviders.push("deepseek");
4287
+ if (config.providers.openai.apiKey) availableProviders.push("openai");
4288
+ if (config.providers.anthropic.apiKey) availableProviders.push("anthropic");
4289
+ if (isReconfig && availableProviders.length > 1) {
4290
+ console.log("");
4291
+ console.log(chalk6.bold.white(" Default Provider"));
4292
+ console.log(chalk6.dim(" Select the default LLM provider (the one used first)."));
4293
+ console.log("");
4294
+ for (let i = 0; i < availableProviders.length; i++) {
4295
+ const marker = availableProviders[i] === config.providers.default ? " (current)" : "";
4296
+ console.log(chalk6.white(` ${i + 1}. ${availableProviders[i]}${marker}`));
4297
+ }
4298
+ console.log("");
4299
+ const choice = await ask(chalk6.white(` Choose [1-${availableProviders.length}] [Enter to keep ${config.providers.default}]: `));
4300
+ const num = parseInt(choice, 10);
4301
+ if (num >= 1 && num <= availableProviders.length) {
4302
+ config.providers.default = availableProviders[num - 1];
4303
+ }
4304
+ } else if (!isReconfig) {
4305
+ config.providers.default = availableProviders[0];
4306
+ console.log(chalk6.dim(` Default provider set to ${config.providers.default}`));
4307
+ }
3394
4308
  hr();
3395
4309
  console.log("");
3396
- console.log(chalk3.white(" Telegram (optional)"));
3397
- console.log(chalk3.dim(" Leave empty to skip. You can add it later."));
4310
+ console.log(chalk6.bold.white(" Telegram (optional)"));
4311
+ if (isReconfig) {
4312
+ console.log(chalk6.dim(' Leave empty to keep current value. Enter "none" to disable.'));
4313
+ } else {
4314
+ console.log(chalk6.dim(" Leave empty to skip. You can add it later."));
4315
+ }
3398
4316
  console.log("");
3399
- const telegramToken = await ask(chalk3.white(" Telegram Bot Token: "));
3400
- if (telegramToken) {
4317
+ const tgMask = isReconfig && config.channels.telegram.botToken ? ` [${maskKey(config.channels.telegram.botToken)}]` : "";
4318
+ const telegramToken = await ask(chalk6.white(` Telegram Bot Token${tgMask}: `));
4319
+ if (isReconfig && telegramToken.toLowerCase() === "none") {
4320
+ config.channels.telegram.enabled = false;
4321
+ config.channels.telegram.botToken = "";
4322
+ } else if (telegramToken) {
3401
4323
  config.channels.telegram.botToken = telegramToken;
3402
4324
  config.channels.telegram.enabled = true;
3403
4325
  }
3404
4326
  hr();
4327
+ console.log("");
4328
+ console.log(chalk6.bold.white(" Token Budget"));
4329
+ console.log("");
4330
+ const budgetPrompt = isReconfig ? chalk6.white(` Daily token budget [${config.tokens.dailyBudget.toLocaleString()}]: `) : chalk6.white(` Daily token budget [${config.tokens.dailyBudget.toLocaleString()}]: `);
4331
+ const budgetStr = await ask(budgetPrompt);
4332
+ if (budgetStr) {
4333
+ const budget = parseInt(budgetStr.replace(/,/g, ""), 10);
4334
+ if (!isNaN(budget) && budget > 0) {
4335
+ config.tokens.dailyBudget = budget;
4336
+ }
4337
+ }
4338
+ hr();
3405
4339
  saveConfig(config);
3406
4340
  const home = getMercuryHome();
3407
4341
  console.log("");
3408
- console.log(chalk3.green(` \u2713 Config saved to ${home}/mercury.yaml`));
3409
- console.log(chalk3.green(` \u2713 Soul files seeded in ${home}/soul/`));
3410
- console.log(chalk3.green(` \u2713 Memory stored in ${home}/memory/`));
3411
- console.log(chalk3.green(` \u2713 Permissions seeded in ${home}/permissions.yaml`));
3412
- console.log(chalk3.green(` \u2713 Skills directory ready in ${home}/skills/`));
4342
+ console.log(chalk6.green(` \u2713 Config saved to ${home}/mercury.yaml`));
4343
+ console.log(chalk6.green(` \u2713 Soul files seeded in ${home}/soul/`));
4344
+ console.log(chalk6.green(` \u2713 Memory stored in ${home}/memory/`));
4345
+ console.log(chalk6.green(` \u2713 Permissions seeded in ${home}/permissions.yaml`));
4346
+ console.log(chalk6.green(` \u2713 Skills directory ready in ${home}/skills/`));
3413
4347
  console.log("");
3414
- console.log(chalk3.cyan(` ${config.identity.name} is ready. Run \`mercury start\` to begin.`));
3415
- console.log(chalk3.dim(" mercury.cosmicstack.org"));
4348
+ console.log(chalk6.cyan(` ${config.identity.name} is ready. Run \`mercury start\` to chat.`));
4349
+ console.log(chalk6.dim(" mercury.cosmicstack.org"));
3416
4350
  console.log("");
3417
4351
  }
3418
- async function runAgent() {
4352
+ function autoDaemonize() {
4353
+ const daemon = getDaemonStatus();
4354
+ if (daemon.running) {
4355
+ return;
4356
+ }
4357
+ console.log(chalk6.dim(" Setting up background mode..."));
4358
+ try {
4359
+ if (!isServiceInstalled()) {
4360
+ installService();
4361
+ }
4362
+ } catch {
4363
+ console.log(chalk6.dim(" Service install skipped (can run `mercury service install` later)."));
4364
+ }
4365
+ const ok = tryAutoDaemonize();
4366
+ if (ok) {
4367
+ const status = getDaemonStatus();
4368
+ console.log(chalk6.green(` \u2713 Mercury is running in background (PID: ${status.pid})`));
4369
+ console.log(chalk6.green(" \u2713 Auto-starts on login. Auto-restarts on crash."));
4370
+ console.log(chalk6.dim(" Use `mercury stop` to stop. `mercury restart` to restart."));
4371
+ } else {
4372
+ console.log(chalk6.dim(" Background mode not available. Run `mercury up` to set it up."));
4373
+ }
4374
+ console.log("");
4375
+ }
4376
+ async function runAgent(isDaemon = false) {
3419
4377
  let config = loadConfig();
3420
4378
  config = ensureCreatorField(config);
3421
4379
  const name = config.identity.name;
3422
- banner();
3423
- console.log(chalk3.white(` ${name} is waking up...`));
3424
- console.log("");
4380
+ if (!isDaemon) {
4381
+ banner();
4382
+ console.log(chalk6.white(` ${name} is waking up...`));
4383
+ console.log("");
4384
+ } else {
4385
+ logger.info(`${name} is waking up (daemon mode)...`);
4386
+ }
3425
4387
  const tokenBudget = new TokenBudget(config);
3426
4388
  const providers = new ProviderRegistry(config);
3427
4389
  if (!providers.hasProviders()) {
3428
- console.log(chalk3.red(" No LLM providers available. Run `mercury setup` to configure API keys."));
4390
+ if (isDaemon) {
4391
+ logger.error("No LLM providers available. Run `mercury doctor` to configure API keys.");
4392
+ return;
4393
+ }
4394
+ console.log(chalk6.red(" No LLM providers available. Run `mercury doctor` to configure API keys."));
3429
4395
  process.exit(1);
3430
4396
  }
3431
4397
  const available = providers.listAvailable();
3432
- console.log(chalk3.dim(` Providers: ${available.join(", ")}`));
4398
+ if (!isDaemon) {
4399
+ console.log(chalk6.dim(` Providers: ${available.join(", ")}`));
4400
+ } else {
4401
+ logger.info({ providers: available }, "Providers loaded");
4402
+ }
3433
4403
  const skillLoader = new SkillLoader();
3434
4404
  const skills = skillLoader.discover();
3435
- console.log(chalk3.dim(` Skills: ${skills.length > 0 ? skills.map((s) => s.name).join(", ") : "none installed"}`));
4405
+ if (!isDaemon) {
4406
+ console.log(chalk6.dim(` Skills: ${skills.length > 0 ? skills.map((s) => s.name).join(", ") : "none installed"}`));
4407
+ }
3436
4408
  const scheduler = new Scheduler(config);
3437
4409
  const identity = new Identity();
3438
4410
  const shortTerm = new ShortTermMemory(config);
@@ -3440,6 +4412,13 @@ async function runAgent() {
3440
4412
  const episodic = new EpisodicMemory(config);
3441
4413
  const channels = new ChannelRegistry(config);
3442
4414
  const capabilities = new CapabilityRegistry(skillLoader, scheduler, tokenBudget);
4415
+ capabilities.setChatCommandContext({
4416
+ toolNames: () => capabilities.getToolNames(),
4417
+ skillNames: () => skills.map((s) => s.name),
4418
+ config: () => config,
4419
+ tokenBudget: () => tokenBudget,
4420
+ manual: () => getManual()
4421
+ });
3443
4422
  capabilities.setSendFileHandler(async (filePath) => {
3444
4423
  const msg = channels.getActiveChannels().includes("telegram") ? channels.get("telegram") : channels.get("cli");
3445
4424
  if (msg) {
@@ -3469,21 +4448,29 @@ async function runAgent() {
3469
4448
  }
3470
4449
  const activeCh = channels.getActiveChannels();
3471
4450
  const toolNames = capabilities.getToolNames();
3472
- console.log(chalk3.dim(` Channels: ${activeCh.join(", ")}`));
3473
- console.log(chalk3.dim(` Tools: ${toolNames.join(", ")}`));
3474
- console.log(chalk3.dim(` Permissions: ${getMercuryHome()}/permissions.yaml`));
3475
- console.log(chalk3.dim(` Schedules: ${getMercuryHome()}/schedules.yaml`));
3476
- if (config.identity.creator) {
3477
- console.log(chalk3.dim(` Creator: ${config.identity.creator}`));
4451
+ if (!isDaemon) {
4452
+ console.log(chalk6.dim(` Channels: ${activeCh.join(", ")}`));
4453
+ console.log(chalk6.dim(` Tools: ${toolNames.join(", ")}`));
4454
+ console.log(chalk6.dim(` Permissions: ${getMercuryHome()}/permissions.yaml`));
4455
+ console.log(chalk6.dim(` Schedules: ${getMercuryHome()}/schedules.yaml`));
4456
+ if (config.identity.creator) {
4457
+ console.log(chalk6.dim(` Creator: ${config.identity.creator}`));
4458
+ }
4459
+ hr();
4460
+ console.log("");
4461
+ console.log(chalk6.green(` ${name} is live. Type a message and press Enter.`));
4462
+ console.log(chalk6.dim(" Ctrl+C to exit \xB7 /help for commands"));
4463
+ console.log("");
4464
+ } else {
4465
+ logger.info({ channels: activeCh, tools: toolNames }, "Mercury is live (daemon mode)");
3478
4466
  }
3479
- hr();
3480
- console.log("");
3481
- console.log(chalk3.green(` ${name} is live. Type a message and press Enter.`));
3482
- console.log(chalk3.dim(" Ctrl+C to exit."));
3483
- console.log("");
3484
4467
  const shutdown = async () => {
3485
- console.log("");
3486
- console.log(chalk3.dim(` ${name} is shutting down...`));
4468
+ if (!isDaemon) {
4469
+ console.log("");
4470
+ console.log(chalk6.dim(` ${name} is shutting down...`));
4471
+ } else {
4472
+ logger.info("Mercury is shutting down (daemon mode)");
4473
+ }
3487
4474
  await agent.shutdown();
3488
4475
  process.exit(0);
3489
4476
  };
@@ -3491,41 +4478,105 @@ async function runAgent() {
3491
4478
  process.on("SIGTERM", shutdown);
3492
4479
  }
3493
4480
  var program = new Command();
3494
- program.name("mercury").description("Mercury \u2014 an AI agent for personal tasks").version("0.1.0").option("-v, --verbose", "Show debug logs").action(async () => {
4481
+ program.name("mercury").description("Mercury \u2014 Soul-driven AI agent with permission-hardened tools, token budgets, and multi-channel access.").version("0.2.1").option("-v, --verbose", "Show debug logs").action(async () => {
3495
4482
  if (!isSetupComplete()) {
3496
- await onboarding();
4483
+ await configure();
4484
+ autoDaemonize();
3497
4485
  return;
3498
4486
  }
3499
4487
  await runAgent();
3500
4488
  });
3501
- program.command("start").description("Start Mercury agent").option("-v, --verbose", "Show debug logs").action(async () => {
4489
+ program.command("start").description("Start Mercury agent").option("-v, --verbose", "Show debug logs").option("-d, --detached", "Run in background (daemon mode)").option("--daemon", "Internal flag for daemon child process").action(async (opts) => {
4490
+ if (opts.daemon) {
4491
+ await runWithWatchdog(() => runAgent(true));
4492
+ return;
4493
+ }
4494
+ if (opts.detached) {
4495
+ startBackground();
4496
+ return;
4497
+ }
3502
4498
  if (!isSetupComplete()) {
3503
- await onboarding();
4499
+ await configure();
3504
4500
  return;
3505
4501
  }
3506
4502
  await runAgent();
3507
4503
  });
3508
- program.command("setup").description("Re-run the setup wizard").action(async () => {
3509
- await onboarding();
4504
+ program.command("stop").description("Stop a background Mercury process").action(() => {
4505
+ stopDaemon();
4506
+ });
4507
+ program.command("restart").description("Restart a background Mercury process").action(() => {
4508
+ restartDaemon();
4509
+ });
4510
+ program.command("up").description("Ensure Mercury is running persistently \u2014 installs service if needed, starts daemon").action(async () => {
4511
+ if (!isSetupComplete()) {
4512
+ await configure();
4513
+ }
4514
+ const daemon = getDaemonStatus();
4515
+ if (daemon.running && daemon.pid) {
4516
+ console.log("");
4517
+ console.log(chalk6.green(` Mercury is already running (PID: ${daemon.pid})`));
4518
+ console.log(chalk6.dim(` Logs: ${daemon.logPath}`));
4519
+ console.log("");
4520
+ return;
4521
+ }
4522
+ if (!isServiceInstalled()) {
4523
+ console.log("");
4524
+ console.log(chalk6.cyan(" Installing Mercury as a system service..."));
4525
+ installService();
4526
+ }
4527
+ console.log(chalk6.cyan(" Starting Mercury in background..."));
4528
+ startBackground();
4529
+ });
4530
+ program.command("logs").description("Show recent daemon logs").action(() => {
4531
+ showLogs();
4532
+ });
4533
+ program.command("setup").description("Re-run the setup wizard (reconfigure)").action(async () => {
4534
+ if (isSetupComplete()) {
4535
+ await configure(loadConfig());
4536
+ } else {
4537
+ await configure();
4538
+ }
4539
+ });
4540
+ program.command("doctor").description("Reconfigure Mercury \u2014 change keys, name, settings (Enter to keep current)").action(async () => {
4541
+ if (isSetupComplete()) {
4542
+ await configure(loadConfig());
4543
+ } else {
4544
+ await configure();
4545
+ }
3510
4546
  });
3511
- program.command("status").description("Show current configuration").action(() => {
4547
+ program.command("status").description("Show current configuration and daemon status").action(() => {
3512
4548
  const config = loadConfig();
3513
4549
  const home = getMercuryHome();
3514
4550
  const skillLoader = new SkillLoader();
3515
4551
  const skills = skillLoader.discover();
4552
+ const daemon = getDaemonStatus();
3516
4553
  banner();
3517
- console.log(` Name: ${chalk3.cyan(config.identity.name)}`);
3518
- console.log(` Owner: ${chalk3.white(config.identity.owner || "(not set)")}`);
4554
+ console.log(` Name: ${chalk6.cyan(config.identity.name)}`);
4555
+ console.log(` Owner: ${chalk6.white(config.identity.owner || "(not set)")}`);
3519
4556
  if (config.identity.creator) {
3520
- console.log(` Creator: ${chalk3.white(config.identity.creator)}`);
3521
- }
3522
- console.log(` Provider: ${chalk3.white(config.providers.default)}`);
3523
- console.log(` Telegram: ${config.channels.telegram.enabled ? chalk3.green("enabled") : chalk3.dim("disabled")}`);
3524
- console.log(` Skills: ${skills.length > 0 ? chalk3.green(skills.map((s) => s.name).join(", ")) : chalk3.dim("none")}`);
3525
- console.log(` Budget: ${chalk3.white(config.tokens.dailyBudget)} tokens/day`);
3526
- console.log(` Setup: ${isSetupComplete() ? chalk3.green("complete") : chalk3.red("not done")}`);
3527
- console.log(` Home: ${chalk3.dim(home)}`);
4557
+ console.log(` Creator: ${chalk6.white(config.identity.creator)}`);
4558
+ }
4559
+ console.log(` Provider: ${chalk6.white(config.providers.default)}`);
4560
+ console.log(` Telegram: ${config.channels.telegram.enabled ? chalk6.green("enabled") : chalk6.dim("disabled")}`);
4561
+ console.log(` Skills: ${skills.length > 0 ? chalk6.green(skills.map((s) => s.name).join(", ")) : chalk6.dim("none")}`);
4562
+ console.log(` Budget: ${chalk6.white(config.tokens.dailyBudget.toLocaleString())} tokens/day`);
4563
+ console.log(` Setup: ${isSetupComplete() ? chalk6.green("complete") : chalk6.red("not done")}`);
4564
+ console.log(` Daemon: ${daemon.running ? chalk6.green(`running (PID: ${daemon.pid})`) : chalk6.dim("not running")}`);
4565
+ console.log(` Home: ${chalk6.dim(home)}`);
3528
4566
  console.log("");
3529
4567
  });
4568
+ program.command("help").description("Show capabilities and commands manual").action(() => {
4569
+ console.log(getManual());
4570
+ });
4571
+ var serviceCmd = program.command("service").description("Manage Mercury as a system service (auto-start, crash recovery)");
4572
+ serviceCmd.command("install").description("Install Mercury as a system service (auto-start on boot)").action(() => {
4573
+ installService();
4574
+ });
4575
+ serviceCmd.command("uninstall").description("Uninstall the system service").action(() => {
4576
+ uninstallService();
4577
+ });
4578
+ serviceCmd.command("status").description("Show system service status").action(() => {
4579
+ showServiceStatus();
4580
+ });
3530
4581
  program.parse();
3531
4582
  //# sourceMappingURL=index.js.map