@cosmicstack/mercury-agent 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js 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 = [];
@@ -3110,12 +3302,19 @@ var CapabilityRegistry = class {
3110
3302
  sendFileHandler;
3111
3303
  currentChannelId = "cli";
3112
3304
  currentChannelType = "cli";
3305
+ chatCommandContext;
3113
3306
  constructor(skillLoader, scheduler, tokenBudget) {
3114
3307
  this.permissions = new PermissionManager();
3115
3308
  this.skillLoader = skillLoader;
3116
3309
  this.scheduler = scheduler;
3117
3310
  this.tokenBudget = tokenBudget;
3118
3311
  }
3312
+ setChatCommandContext(ctx) {
3313
+ this.chatCommandContext = ctx;
3314
+ }
3315
+ getChatCommandContext() {
3316
+ return this.chatCommandContext;
3317
+ }
3119
3318
  setChannelContext(channelId, channelType) {
3120
3319
  this.currentChannelId = channelId;
3121
3320
  this.currentChannelType = channelType;
@@ -3322,31 +3521,694 @@ Describe what this skill enables Mercury to do. When invoked via the use_skill t
3322
3521
  }
3323
3522
  };
3324
3523
 
3524
+ // src/utils/manual.ts
3525
+ import chalk3 from "chalk";
3526
+ function getManual() {
3527
+ const sections = [];
3528
+ sections.push("");
3529
+ sections.push(chalk3.bold.cyan(" MERCURY \u2014 Capabilities & Commands"));
3530
+ 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"));
3531
+ sections.push("");
3532
+ sections.push(chalk3.bold.white(" Built-in Tools"));
3533
+ sections.push(chalk3.dim(" Tools Mercury can use during conversations."));
3534
+ sections.push("");
3535
+ const tools = [
3536
+ ["read_file", "Read file contents", "path (required)"],
3537
+ ["write_file", "Write to an existing file", "path, content"],
3538
+ ["create_file", "Create a new file (+ dirs)", "path, content"],
3539
+ ["edit_file", "Replace specific text in a file", "path, old_string, new_string"],
3540
+ ["list_dir", "List directory contents", "path"],
3541
+ ["delete_file", "Delete a file", "path"],
3542
+ ["run_command", "Execute a shell command", "command"],
3543
+ ["approve_command", "Permanently approve a command type", 'command (e.g. "curl")'],
3544
+ ["fetch_url", "Fetch a URL and return content", "url, format? (text/markdown)"],
3545
+ ["git_status", "Show working tree status", "path?"],
3546
+ ["git_diff", "Show file changes", "path?, staged?"],
3547
+ ["git_log", "Show commit history", "count?, path?"],
3548
+ ["git_add", "Stage files for commit", "paths (array)"],
3549
+ ["git_commit", "Create a commit", "message"],
3550
+ ["git_push", "Push to remote (needs approval)", "remote?, branch?"],
3551
+ ["install_skill", "Install a skill from content or URL", "content? or url?"],
3552
+ ["list_skills", "List installed skills", "\u2014"],
3553
+ ["use_skill", "Invoke a skill by name", "name"],
3554
+ ["schedule_task", "Schedule a recurring or delayed task", "cron? or delay_seconds, description, prompt? or skill_name?"],
3555
+ ["list_scheduled_tasks", "List all scheduled tasks", "\u2014"],
3556
+ ["cancel_scheduled_task", "Cancel a scheduled task", "id"],
3557
+ ["budget_status", "Check token budget", "\u2014"]
3558
+ ];
3559
+ for (const [name, desc, params] of tools) {
3560
+ sections.push(` ${chalk3.cyan(name.padEnd(24))} ${desc}`);
3561
+ sections.push(` ${" ".repeat(24)} ${chalk3.dim(params)}`);
3562
+ }
3563
+ sections.push("");
3564
+ sections.push(chalk3.bold.white(" CLI Commands"));
3565
+ sections.push(chalk3.dim(" Run these from your terminal (no API calls consumed)."));
3566
+ sections.push("");
3567
+ const commands = [
3568
+ ["mercury up", "Start persistently (install service + daemon)"],
3569
+ ["mercury", "Start the agent (same as mercury start)"],
3570
+ ["mercury start", "Start the agent in foreground"],
3571
+ ["mercury start -d", "Start in background (daemon mode)"],
3572
+ ["mercury restart", "Restart a background process"],
3573
+ ["mercury stop", "Stop a background process"],
3574
+ ["mercury logs", "Show recent daemon logs"],
3575
+ ["mercury doctor", "Reconfigure settings (Enter keeps current)"],
3576
+ ["mercury setup", "Re-run the setup wizard"],
3577
+ ["mercury status", "Show config and daemon status"],
3578
+ ["mercury help", "Show this manual"],
3579
+ ["mercury service install", "Install as system service (auto-start)"],
3580
+ ["mercury service uninstall", "Uninstall system service"],
3581
+ ["mercury service status", "Show system service status"],
3582
+ ["mercury --verbose", "Start with debug logging on stderr"]
3583
+ ];
3584
+ for (const [cmd, desc] of commands) {
3585
+ sections.push(` ${chalk3.white(cmd.padEnd(26))} ${desc}`);
3586
+ }
3587
+ sections.push("");
3588
+ sections.push(chalk3.bold.white(" In-Chat Commands"));
3589
+ sections.push(chalk3.dim(" Type these during a conversation (no API calls)."));
3590
+ sections.push("");
3591
+ const chat = [
3592
+ ["/help", "Show this manual"],
3593
+ ["/status", "Show config and budget info"],
3594
+ ["/tools", "List currently loaded tools"],
3595
+ ["/skills", "List installed skills"],
3596
+ ["/stream", "Toggle text streaming on/off (Telegram)"],
3597
+ ["/stream on", "Enable streaming (live text updates)"],
3598
+ ["/stream off", "Disable streaming (single message)"]
3599
+ ];
3600
+ for (const [cmd, desc] of chat) {
3601
+ sections.push(` ${chalk3.white(cmd.padEnd(16))} ${desc}`);
3602
+ }
3603
+ sections.push("");
3604
+ sections.push(chalk3.bold.white(" Permissions"));
3605
+ sections.push("");
3606
+ const perms = [
3607
+ "Commands are blocked (never run), auto-approved, or need approval.",
3608
+ 'Say "always" when prompted to permanently approve a command type.',
3609
+ "Edit ~/.mercury/permissions.yaml to customize manually.",
3610
+ "File access is scoped \u2014 new paths need approval (y/n/always)."
3611
+ ];
3612
+ for (const p of perms) {
3613
+ sections.push(` ${chalk3.dim("\u2022")} ${p}`);
3614
+ }
3615
+ sections.push("");
3616
+ sections.push(chalk3.bold.white(" Skills"));
3617
+ sections.push("");
3618
+ const skillInfo = [
3619
+ "Skills live in ~/.mercury/skills/<name>/SKILL.md",
3620
+ 'Install: ask Mercury to "install skill from <url>" or paste content',
3621
+ 'Invoke: ask Mercury to "use skill <name>"',
3622
+ 'Schedule: "remind me daily at 9am to run daily-digest skill"'
3623
+ ];
3624
+ for (const s of skillInfo) {
3625
+ sections.push(` ${chalk3.dim("\u2022")} ${s}`);
3626
+ }
3627
+ sections.push("");
3628
+ sections.push(chalk3.bold.white(" Scheduling"));
3629
+ sections.push("");
3630
+ const schedInfo = [
3631
+ 'Recurring: "every day at 9am remind me to\u2026"',
3632
+ 'One-shot: "remind me in 15 seconds to\u2026"',
3633
+ "Tasks persist to ~/.mercury/schedules.yaml"
3634
+ ];
3635
+ for (const s of schedInfo) {
3636
+ sections.push(` ${chalk3.dim("\u2022")} ${s}`);
3637
+ }
3638
+ sections.push("");
3639
+ sections.push(chalk3.bold.white(" Configuration"));
3640
+ sections.push("");
3641
+ const configInfo = [
3642
+ ["~/.mercury/mercury.yaml", "Main config (providers, channels, budget)"],
3643
+ ["~/.mercury/permissions.yaml", "Capabilities and approval rules"],
3644
+ ["~/.mercury/soul/*.md", "Agent personality (soul, persona, taste, heartbeat)"],
3645
+ ["~/.mercury/skills/", "Installed skills"],
3646
+ ["~/.mercury/schedules.yaml", "Scheduled tasks"],
3647
+ ["~/.mercury/token-usage.json", "Daily token usage tracking"],
3648
+ ["~/.mercury/memory/", "Short-term, long-term, episodic memory"]
3649
+ ];
3650
+ for (const [path3, desc] of configInfo) {
3651
+ sections.push(` ${chalk3.dim(path3.padEnd(36))} ${desc}`);
3652
+ }
3653
+ sections.push("");
3654
+ sections.push(chalk3.dim(" mercury.cosmicstack.org"));
3655
+ sections.push("");
3656
+ return sections.join("\n");
3657
+ }
3658
+
3659
+ // src/cli/daemon.ts
3660
+ import { spawn } from "child_process";
3661
+ import { existsSync as existsSync14, readFileSync as readFileSync11, writeFileSync as writeFileSync11, unlinkSync as unlinkSync3, mkdirSync as mkdirSync9, openSync } from "fs";
3662
+ import { join as join9 } from "path";
3663
+ import process2 from "process";
3664
+ import chalk4 from "chalk";
3665
+ var PID_FILE = "daemon.pid";
3666
+ var LOG_FILE = "daemon.log";
3667
+ function pidPath() {
3668
+ return join9(getMercuryHome(), PID_FILE);
3669
+ }
3670
+ function logPath() {
3671
+ return join9(getMercuryHome(), LOG_FILE);
3672
+ }
3673
+ function readPid() {
3674
+ const path3 = pidPath();
3675
+ if (!existsSync14(path3)) return null;
3676
+ try {
3677
+ const pid = parseInt(readFileSync11(path3, "utf-8").trim(), 10);
3678
+ if (isNaN(pid)) return null;
3679
+ return pid;
3680
+ } catch {
3681
+ return null;
3682
+ }
3683
+ }
3684
+ function isProcessRunning(pid) {
3685
+ try {
3686
+ process2.kill(pid, 0);
3687
+ return true;
3688
+ } catch {
3689
+ return false;
3690
+ }
3691
+ }
3692
+ function getDaemonStatus() {
3693
+ const pid = readPid();
3694
+ if (!pid) return { running: false, pid: null, logPath: logPath() };
3695
+ return { running: isProcessRunning(pid), pid, logPath: logPath() };
3696
+ }
3697
+ function startBackground() {
3698
+ const status = getDaemonStatus();
3699
+ if (status.running && status.pid) {
3700
+ console.log(chalk4.yellow(` Mercury is already running (PID: ${status.pid})`));
3701
+ console.log(chalk4.dim(` Use \`mercury stop\` to stop it first.`));
3702
+ console.log("");
3703
+ process2.exit(1);
3704
+ }
3705
+ if (status.pid && !status.running) {
3706
+ try {
3707
+ unlinkSync3(pidPath());
3708
+ } catch {
3709
+ }
3710
+ }
3711
+ const home = getMercuryHome();
3712
+ if (!existsSync14(home)) {
3713
+ mkdirSync9(home, { recursive: true });
3714
+ }
3715
+ const logFile = logPath();
3716
+ const isWin = process2.platform === "win32";
3717
+ const outFd = openSync(logFile, "a");
3718
+ const child = spawn(process2.execPath, [process2.argv[1], "start", "--daemon"], {
3719
+ detached: true,
3720
+ stdio: ["ignore", outFd, outFd],
3721
+ env: { ...process2.env },
3722
+ windowsHide: isWin
3723
+ });
3724
+ child.unref();
3725
+ writeFileSync11(pidPath(), String(child.pid));
3726
+ console.log("");
3727
+ console.log(chalk4.green(` Mercury started in background (PID: ${child.pid})`));
3728
+ console.log(chalk4.dim(` Logs: ${logFile}`));
3729
+ console.log(chalk4.dim(` Use \`mercury stop\` to stop.`));
3730
+ console.log(chalk4.dim(` Use \`mercury logs\` to view logs.`));
3731
+ console.log("");
3732
+ }
3733
+ function stopDaemon() {
3734
+ const status = getDaemonStatus();
3735
+ if (!status.pid) {
3736
+ console.log(chalk4.yellow(" Mercury is not running as a daemon."));
3737
+ console.log("");
3738
+ process2.exit(0);
3739
+ }
3740
+ if (!status.running) {
3741
+ console.log(chalk4.yellow(` Stale PID file found (PID: ${status.pid} is not running). Cleaning up.`));
3742
+ try {
3743
+ unlinkSync3(pidPath());
3744
+ } catch {
3745
+ }
3746
+ console.log("");
3747
+ process2.exit(0);
3748
+ }
3749
+ try {
3750
+ if (process2.platform === "win32") {
3751
+ process2.kill(status.pid);
3752
+ } else {
3753
+ process2.kill(status.pid, "SIGTERM");
3754
+ }
3755
+ console.log(chalk4.green(` Mercury stopped (PID: ${status.pid})`));
3756
+ } catch {
3757
+ console.log(chalk4.red(` Failed to stop PID ${status.pid}. You may need to kill it manually.`));
3758
+ }
3759
+ try {
3760
+ unlinkSync3(pidPath());
3761
+ } catch {
3762
+ }
3763
+ console.log("");
3764
+ }
3765
+ function restartDaemon() {
3766
+ const status = getDaemonStatus();
3767
+ if (status.running && status.pid) {
3768
+ console.log(chalk4.yellow(` Stopping Mercury (PID: ${status.pid})...`));
3769
+ try {
3770
+ if (process2.platform === "win32") {
3771
+ process2.kill(status.pid);
3772
+ } else {
3773
+ process2.kill(status.pid, "SIGTERM");
3774
+ }
3775
+ } catch {
3776
+ }
3777
+ try {
3778
+ unlinkSync3(pidPath());
3779
+ } catch {
3780
+ }
3781
+ console.log(chalk4.green(" Mercury stopped."));
3782
+ } else if (status.pid) {
3783
+ try {
3784
+ unlinkSync3(pidPath());
3785
+ } catch {
3786
+ }
3787
+ }
3788
+ console.log(chalk4.yellow(" Starting Mercury..."));
3789
+ startBackground();
3790
+ }
3791
+ function showLogs() {
3792
+ const logFile = logPath();
3793
+ if (!existsSync14(logFile)) {
3794
+ console.log(chalk4.dim(" No daemon log file found."));
3795
+ console.log("");
3796
+ return;
3797
+ }
3798
+ const content = readFileSync11(logFile, "utf-8");
3799
+ const lines = content.split("\n").slice(-100);
3800
+ console.log(lines.join("\n"));
3801
+ }
3802
+ function tryAutoDaemonize() {
3803
+ try {
3804
+ const status = getDaemonStatus();
3805
+ if (status.running && status.pid) {
3806
+ return true;
3807
+ }
3808
+ if (status.pid && !status.running) {
3809
+ try {
3810
+ unlinkSync3(pidPath());
3811
+ } catch {
3812
+ }
3813
+ }
3814
+ const home = getMercuryHome();
3815
+ if (!existsSync14(home)) {
3816
+ mkdirSync9(home, { recursive: true });
3817
+ }
3818
+ const logFile = logPath();
3819
+ const isWin = process2.platform === "win32";
3820
+ const outFd = openSync(logFile, "a");
3821
+ const child = spawn(process2.execPath, [process2.argv[1], "start", "--daemon"], {
3822
+ detached: true,
3823
+ stdio: ["ignore", outFd, outFd],
3824
+ env: { ...process2.env },
3825
+ windowsHide: isWin
3826
+ });
3827
+ child.unref();
3828
+ if (!child.pid) {
3829
+ return false;
3830
+ }
3831
+ writeFileSync11(pidPath(), String(child.pid));
3832
+ return true;
3833
+ } catch {
3834
+ return false;
3835
+ }
3836
+ }
3837
+
3838
+ // src/cli/service.ts
3839
+ import { existsSync as existsSync15, mkdirSync as mkdirSync10, writeFileSync as writeFileSync12, unlinkSync as unlinkSync4 } from "fs";
3840
+ import { join as join10 } from "path";
3841
+ import { homedir as homedir3 } from "os";
3842
+ import chalk5 from "chalk";
3843
+ import { execSync as execSync8 } from "child_process";
3844
+ var SERVICE_DESC = "Mercury \u2014 Soul-Driven AI Agent";
3845
+ var WIN_TASK_NAME = "MercuryAgent";
3846
+ function isServiceInstalled() {
3847
+ const platform = process.platform;
3848
+ if (platform === "darwin") {
3849
+ return existsSync15(join10(homedir3(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist"));
3850
+ } else if (platform === "linux") {
3851
+ return existsSync15(join10(homedir3(), ".config", "systemd", "user", "mercury.service"));
3852
+ } else if (platform === "win32") {
3853
+ try {
3854
+ execSync8(`schtasks /query /tn "${WIN_TASK_NAME}"`, { stdio: "pipe", shell: "cmd.exe" });
3855
+ return true;
3856
+ } catch {
3857
+ return false;
3858
+ }
3859
+ }
3860
+ return false;
3861
+ }
3862
+ function getNodeBinPath() {
3863
+ return process.execPath;
3864
+ }
3865
+ function getDistPath() {
3866
+ return join10(process.argv[1] || "/usr/local/bin/mercury", "..", "..", "lib", "node_modules", "@cosmicstack", "mercury-agent", "dist", "index.js");
3867
+ }
3868
+ function installService() {
3869
+ const platform = process.platform;
3870
+ if (platform === "darwin") {
3871
+ installMac();
3872
+ } else if (platform === "linux") {
3873
+ installLinux();
3874
+ } else if (platform === "win32") {
3875
+ installWindows();
3876
+ } else {
3877
+ console.log(chalk5.red(` Unsupported platform: ${platform}`));
3878
+ process.exit(1);
3879
+ }
3880
+ }
3881
+ function uninstallService() {
3882
+ const platform = process.platform;
3883
+ if (platform === "darwin") {
3884
+ uninstallMac();
3885
+ } else if (platform === "linux") {
3886
+ uninstallLinux();
3887
+ } else if (platform === "win32") {
3888
+ uninstallWindows();
3889
+ } else {
3890
+ console.log(chalk5.red(` Unsupported platform: ${platform}`));
3891
+ process.exit(1);
3892
+ }
3893
+ }
3894
+ function showServiceStatus() {
3895
+ const platform = process.platform;
3896
+ if (platform === "darwin") {
3897
+ showMacStatus();
3898
+ } else if (platform === "linux") {
3899
+ showLinuxStatus();
3900
+ } else if (platform === "win32") {
3901
+ showWindowsStatus();
3902
+ }
3903
+ }
3904
+ function installMac() {
3905
+ const plistDir = join10(homedir3(), "Library", "LaunchAgents");
3906
+ const plistPath = join10(plistDir, "com.cosmicstack.mercury.plist");
3907
+ if (!existsSync15(plistDir)) {
3908
+ mkdirSync10(plistDir, { recursive: true });
3909
+ }
3910
+ const nodeBin = getNodeBinPath();
3911
+ const scriptPath = getDistPath();
3912
+ const home = getMercuryHome();
3913
+ const logPath2 = join10(home, "daemon.log");
3914
+ const errPath = join10(home, "daemon-error.log");
3915
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
3916
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3917
+ <plist version="1.0">
3918
+ <dict>
3919
+ <key>Label</key>
3920
+ <string>com.cosmicstack.mercury</string>
3921
+ <key>ProgramArguments</key>
3922
+ <array>
3923
+ <string>${nodeBin}</string>
3924
+ <string>${scriptPath}</string>
3925
+ <string>start</string>
3926
+ <string>--daemon</string>
3927
+ </array>
3928
+ <key>RunAtLoad</key>
3929
+ <true/>
3930
+ <key>KeepAlive</key>
3931
+ <dict>
3932
+ <key>SuccessfulExit</key>
3933
+ <false/>
3934
+ </dict>
3935
+ <key>StandardOutPath</key>
3936
+ <string>${logPath2}</string>
3937
+ <key>StandardErrorPath</key>
3938
+ <string>${errPath}</string>
3939
+ <key>EnvironmentVariables</key>
3940
+ <dict>
3941
+ <key>PATH</key>
3942
+ <string>${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}</string>
3943
+ <key>HOME</key>
3944
+ <string>${homedir3()}</string>
3945
+ </dict>
3946
+ <key>WorkingDirectory</key>
3947
+ <string>${homedir3()}</string>
3948
+ </dict>
3949
+ </plist>`;
3950
+ writeFileSync12(plistPath, plist, "utf-8");
3951
+ try {
3952
+ execSync8(`launchctl load ${plistPath}`, { stdio: "inherit" });
3953
+ } catch {
3954
+ console.log(chalk5.yellow(" launchctl load failed. Try running:"));
3955
+ console.log(chalk5.dim(` launchctl load ${plistPath}`));
3956
+ }
3957
+ console.log("");
3958
+ console.log(chalk5.green(" Mercury service installed (macOS LaunchAgent)"));
3959
+ console.log(chalk5.dim(` Plist: ${plistPath}`));
3960
+ console.log(chalk5.dim(` Logs: ${logPath2}`));
3961
+ console.log(chalk5.dim(" Auto-starts on login. Auto-restarts on crash."));
3962
+ console.log("");
3963
+ console.log(chalk5.dim(" Uninstall: mercury service uninstall"));
3964
+ console.log("");
3965
+ }
3966
+ function uninstallMac() {
3967
+ const plistPath = join10(homedir3(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist");
3968
+ if (!existsSync15(plistPath)) {
3969
+ console.log(chalk5.yellow(" Mercury service is not installed."));
3970
+ console.log("");
3971
+ process.exit(0);
3972
+ }
3973
+ try {
3974
+ execSync8(`launchctl unload ${plistPath}`, { stdio: "inherit" });
3975
+ } catch {
3976
+ }
3977
+ try {
3978
+ unlinkSync4(plistPath);
3979
+ } catch {
3980
+ console.log(chalk5.yellow(" Failed to remove plist file. Remove manually:"));
3981
+ console.log(chalk5.dim(` rm ${plistPath}`));
3982
+ }
3983
+ console.log("");
3984
+ console.log(chalk5.green(" Mercury service uninstalled"));
3985
+ console.log("");
3986
+ }
3987
+ function showMacStatus() {
3988
+ const plistPath = join10(homedir3(), "Library", "LaunchAgents", "com.cosmicstack.mercury.plist");
3989
+ if (!existsSync15(plistPath)) {
3990
+ console.log(chalk5.yellow(" Mercury service is not installed."));
3991
+ console.log(chalk5.dim(" Run `mercury service install` to set it up."));
3992
+ console.log("");
3993
+ return;
3994
+ }
3995
+ try {
3996
+ const output = execSync8("launchctl list | grep com.cosmicstack.mercury", { encoding: "utf-8" }).trim();
3997
+ console.log(` ${chalk5.green("Service installed and loaded")}`);
3998
+ console.log(chalk5.dim(` ${output}`));
3999
+ } catch {
4000
+ console.log(` ${chalk5.yellow("Service installed but not loaded")}`);
4001
+ console.log(chalk5.dim(` Plist: ${plistPath}`));
4002
+ }
4003
+ console.log("");
4004
+ }
4005
+ function installLinux() {
4006
+ const systemdDir = join10(homedir3(), ".config", "systemd", "user");
4007
+ if (!existsSync15(systemdDir)) {
4008
+ mkdirSync10(systemdDir, { recursive: true });
4009
+ }
4010
+ const servicePath = join10(systemdDir, "mercury.service");
4011
+ const nodeBin = getNodeBinPath();
4012
+ const scriptPath = getDistPath();
4013
+ const home = getMercuryHome();
4014
+ const service = `[Unit]
4015
+ Description=${SERVICE_DESC}
4016
+ After=network.target
4017
+
4018
+ [Service]
4019
+ Type=simple
4020
+ ExecStart=${nodeBin} ${scriptPath} start --daemon
4021
+ Restart=on-failure
4022
+ RestartSec=5
4023
+ Environment=PATH=${process.env.PATH || "/usr/local/bin:/usr/bin:/bin"}
4024
+ Environment=HOME=${homedir3()}
4025
+ WorkingDirectory=${homedir3()}
4026
+ StandardOutput=append:${join10(home, "daemon.log")}
4027
+ StandardError=append:${join10(home, "daemon-error.log")}
4028
+
4029
+ [Install]
4030
+ WantedBy=default.target`;
4031
+ writeFileSync12(servicePath, service, "utf-8");
4032
+ try {
4033
+ execSync8("systemctl --user daemon-reload", { stdio: "inherit" });
4034
+ execSync8("systemctl --user enable mercury.service", { stdio: "inherit" });
4035
+ execSync8("systemctl --user start mercury.service", { stdio: "inherit" });
4036
+ } catch (err) {
4037
+ console.log(chalk5.yellow(" systemd commands failed. Try running manually:"));
4038
+ console.log(chalk5.dim(" systemctl --user daemon-reload"));
4039
+ console.log(chalk5.dim(" systemctl --user enable mercury.service"));
4040
+ console.log(chalk5.dim(" systemctl --user start mercury.service"));
4041
+ }
4042
+ try {
4043
+ execSync8(`loginctl enable-linger ${process.env.USER || ""}`, { stdio: "inherit" });
4044
+ } catch {
4045
+ console.log(chalk5.yellow(" Enable linger failed (needed for boot-without-login). Try:"));
4046
+ console.log(chalk5.dim(` sudo loginctl enable-linger ${process.env.USER || "$USER"}`));
4047
+ }
4048
+ console.log("");
4049
+ console.log(chalk5.green(" Mercury service installed (systemd --user)"));
4050
+ console.log(chalk5.dim(` Service: ${servicePath}`));
4051
+ console.log(chalk5.dim(` Logs: ${join10(home, "daemon.log")}`));
4052
+ console.log(chalk5.dim(" Auto-starts on login. Auto-restarts on crash (5s delay)."));
4053
+ console.log("");
4054
+ console.log(chalk5.dim(" Uninstall: mercury service uninstall"));
4055
+ console.log("");
4056
+ }
4057
+ function uninstallLinux() {
4058
+ const servicePath = join10(homedir3(), ".config", "systemd", "user", "mercury.service");
4059
+ if (!existsSync15(servicePath)) {
4060
+ console.log(chalk5.yellow(" Mercury service is not installed."));
4061
+ console.log("");
4062
+ process.exit(0);
4063
+ }
4064
+ try {
4065
+ execSync8("systemctl --user stop mercury.service", { stdio: "inherit" });
4066
+ execSync8("systemctl --user disable mercury.service", { stdio: "inherit" });
4067
+ } catch {
4068
+ }
4069
+ try {
4070
+ unlinkSync4(servicePath);
4071
+ } catch {
4072
+ console.log(chalk5.yellow(" Failed to remove service file. Remove manually:"));
4073
+ console.log(chalk5.dim(` rm ${servicePath}`));
4074
+ }
4075
+ try {
4076
+ execSync8("systemctl --user daemon-reload", { stdio: "inherit" });
4077
+ } catch {
4078
+ }
4079
+ console.log("");
4080
+ console.log(chalk5.green(" Mercury service uninstalled"));
4081
+ console.log("");
4082
+ }
4083
+ function showLinuxStatus() {
4084
+ const servicePath = join10(homedir3(), ".config", "systemd", "user", "mercury.service");
4085
+ if (!existsSync15(servicePath)) {
4086
+ console.log(chalk5.yellow(" Mercury service is not installed."));
4087
+ console.log(chalk5.dim(" Run `mercury service install` to set it up."));
4088
+ console.log("");
4089
+ return;
4090
+ }
4091
+ try {
4092
+ const output = execSync8("systemctl --user status mercury.service", { encoding: "utf-8" }).trim();
4093
+ console.log(output);
4094
+ } catch (err) {
4095
+ console.log(chalk5.yellow(" Could not get service status:"));
4096
+ console.log(chalk5.dim(` ${err.message || err}`));
4097
+ }
4098
+ console.log("");
4099
+ }
4100
+ function installWindows() {
4101
+ const nodeBin = getNodeBinPath();
4102
+ const scriptPath = getDistPath();
4103
+ const home = getMercuryHome();
4104
+ const logPath2 = join10(home, "daemon.log");
4105
+ const cmd = `"${nodeBin}" "${scriptPath}" start --daemon`;
4106
+ try {
4107
+ execSync8(
4108
+ `schtasks /create /tn "${WIN_TASK_NAME}" /tr "${cmd}" /sc onlogon /rl limited /f`,
4109
+ { stdio: "inherit", shell: "cmd.exe" }
4110
+ );
4111
+ } catch {
4112
+ console.log(chalk5.yellow(" schtasks create failed. Try running from an Administrator cmd:"));
4113
+ console.log(chalk5.dim(` schtasks /create /tn "${WIN_TASK_NAME}" /tr "${cmd}" /sc onlogon /rl limited /f`));
4114
+ }
4115
+ try {
4116
+ execSync8(`schtasks /run /tn "${WIN_TASK_NAME}"`, { stdio: "inherit", shell: "cmd.exe" });
4117
+ } catch {
4118
+ console.log(chalk5.yellow(" Task created but failed to start immediately. It will start on next login."));
4119
+ }
4120
+ console.log("");
4121
+ console.log(chalk5.green(" Mercury service installed (Windows Task Scheduler)"));
4122
+ console.log(chalk5.dim(` Task: ${WIN_TASK_NAME}`));
4123
+ console.log(chalk5.dim(` Trigger: on logon`));
4124
+ console.log(chalk5.dim(` Logs: ${logPath2}`));
4125
+ console.log(chalk5.dim(" Auto-starts on login. Use --daemon flag for crash recovery."));
4126
+ console.log("");
4127
+ console.log(chalk5.dim(" Uninstall: mercury service uninstall"));
4128
+ console.log("");
4129
+ }
4130
+ function uninstallWindows() {
4131
+ try {
4132
+ execSync8(`schtasks /delete /tn "${WIN_TASK_NAME}" /f`, { stdio: "inherit", shell: "cmd.exe" });
4133
+ console.log("");
4134
+ console.log(chalk5.green(" Mercury service uninstalled"));
4135
+ console.log("");
4136
+ } catch {
4137
+ console.log(chalk5.yellow(" Task not found or failed to delete. Remove manually:"));
4138
+ console.log(chalk5.dim(` schtasks /delete /tn "${WIN_TASK_NAME}" /f`));
4139
+ console.log("");
4140
+ }
4141
+ }
4142
+ function showWindowsStatus() {
4143
+ try {
4144
+ const output = execSync8(`schtasks /query /tn "${WIN_TASK_NAME}" /fo list`, {
4145
+ encoding: "utf-8",
4146
+ shell: "cmd.exe"
4147
+ }).trim();
4148
+ console.log(output);
4149
+ console.log("");
4150
+ } catch {
4151
+ console.log(chalk5.yellow(" Mercury service is not installed."));
4152
+ console.log(chalk5.dim(" Run `mercury service install` to set it up."));
4153
+ console.log("");
4154
+ }
4155
+ }
4156
+
4157
+ // src/cli/watchdog.ts
4158
+ var MAX_RESTARTS = 10;
4159
+ var RESTART_WINDOW_MS = 6e4;
4160
+ var BASE_DELAY_MS = 1e3;
4161
+ async function runWithWatchdog(agentFn) {
4162
+ const restarts = [];
4163
+ async function attempt() {
4164
+ try {
4165
+ await agentFn();
4166
+ } catch (err) {
4167
+ const now = Date.now();
4168
+ restarts.push(now);
4169
+ const recentRestarts = restarts.filter((t) => now - t < RESTART_WINDOW_MS);
4170
+ const restartCount = recentRestarts.length;
4171
+ if (restartCount >= MAX_RESTARTS) {
4172
+ logger.error({ restartCount }, "Max restarts exceeded within 60s. Exiting.");
4173
+ process.exit(1);
4174
+ }
4175
+ const delay = BASE_DELAY_MS * Math.pow(1.25, restartCount);
4176
+ logger.warn({ err, restartCount, delay }, "Crash detected. Restarting with backoff...");
4177
+ await sleep(delay);
4178
+ await attempt();
4179
+ }
4180
+ }
4181
+ await attempt();
4182
+ }
4183
+ function sleep(ms) {
4184
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
4185
+ }
4186
+
3325
4187
  // src/index.ts
3326
4188
  function hr() {
3327
- console.log(chalk3.dim("\u2500".repeat(50)));
4189
+ console.log(chalk6.dim("\u2500".repeat(50)));
3328
4190
  }
3329
4191
  function banner() {
3330
4192
  console.log("");
3331
4193
  const art = figlet.textSync("MERCURY", { font: "Slant", horizontalLayout: "default" });
3332
4194
  for (const line of art.split("\n")) {
3333
- if (line.trim()) console.log(chalk3.bold.cyan(` ${line}`));
4195
+ if (line.trim()) console.log(chalk6.bold.cyan(` ${line}`));
3334
4196
  }
3335
4197
  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"));
4198
+ console.log(chalk6.white(" an AI agent for personal tasks"));
4199
+ console.log(chalk6.dim(" v0.2.0 \xB7 by Cosmic Stack \xB7 mercury.cosmicstack.org"));
3338
4200
  console.log("");
3339
4201
  }
3340
4202
  function splashScreen() {
3341
4203
  console.log("");
3342
4204
  const art = figlet.textSync("MERCURY", { font: "Slant", horizontalLayout: "default" });
3343
4205
  for (const line of art.split("\n")) {
3344
- if (line.trim()) console.log(chalk3.bold.cyan(` ${line}`));
4206
+ if (line.trim()) console.log(chalk6.bold.cyan(` ${line}`));
3345
4207
  }
3346
4208
  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"));
4209
+ console.log(chalk6.dim(" an AI agent for personal tasks"));
4210
+ console.log(chalk6.cyan(" by Cosmic Stack"));
4211
+ console.log(chalk6.dim(" mercury.cosmicstack.org"));
3350
4212
  console.log("");
3351
4213
  }
3352
4214
  async function ask(prompt) {
@@ -3358,81 +4220,189 @@ async function ask(prompt) {
3358
4220
  });
3359
4221
  });
3360
4222
  }
3361
- async function onboarding() {
3362
- splashScreen();
3363
- console.log(chalk3.yellow(" First run detected \u2014 let's set you up."));
4223
+ function maskKey(key) {
4224
+ if (!key) return "";
4225
+ if (key.length <= 8) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
4226
+ return key.slice(0, 4) + "\u2022\u2022\u2022\u2022" + key.slice(-4);
4227
+ }
4228
+ async function configure(existingConfig) {
4229
+ const isReconfig = !!existingConfig;
4230
+ const config = existingConfig ?? loadConfig();
4231
+ if (isReconfig) {
4232
+ banner();
4233
+ console.log(chalk6.yellow(" Reconfiguring Mercury \u2014 press Enter to keep current value."));
4234
+ } else {
4235
+ splashScreen();
4236
+ console.log(chalk6.yellow(" First run detected \u2014 let's set you up."));
4237
+ }
3364
4238
  hr();
3365
4239
  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);
4240
+ console.log(chalk6.bold.white(" Identity"));
4241
+ console.log("");
4242
+ if (isReconfig) {
4243
+ const ownerName = await ask(chalk6.white(` Your name [${config.identity.owner}]: `));
4244
+ if (ownerName) config.identity.owner = ownerName;
4245
+ const agentName = await ask(chalk6.white(` Agent name [${config.identity.name}]: `));
4246
+ if (agentName) config.identity.name = agentName;
4247
+ } else {
4248
+ const ownerName = await ask(chalk6.white(" Your name: "));
4249
+ if (!ownerName) {
4250
+ console.log(chalk6.red(" Name is required."));
4251
+ process.exit(1);
4252
+ }
4253
+ config.identity.owner = ownerName;
4254
+ const agentName = await ask(chalk6.white(` Agent name [${config.identity.name}]: `));
4255
+ if (agentName) config.identity.name = agentName;
3371
4256
  }
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";
4257
+ config.identity.creator = config.identity.creator || "Cosmic Stack";
3376
4258
  hr();
3377
4259
  console.log("");
3378
- console.log(chalk3.white(" LLM Providers"));
3379
- console.log(chalk3.dim(" At least one API key is required."));
4260
+ console.log(chalk6.bold.white(" LLM Providers"));
4261
+ if (isReconfig) {
4262
+ console.log(chalk6.dim(" Current keys shown masked. Enter new value to change, Enter to keep."));
4263
+ } else {
4264
+ console.log(chalk6.dim(" At least one API key is required."));
4265
+ }
3380
4266
  console.log("");
3381
- const deepseekKey = await ask(chalk3.white(" DeepSeek API key: "));
4267
+ const dsMask = isReconfig && config.providers.deepseek.apiKey ? ` [${maskKey(config.providers.deepseek.apiKey)}]` : "";
4268
+ const deepseekKey = await ask(chalk6.white(` DeepSeek API key${dsMask}: `));
3382
4269
  if (deepseekKey) {
3383
4270
  config.providers.deepseek.apiKey = deepseekKey;
3384
- config.providers.default = "deepseek";
3385
4271
  }
3386
- const openaiKey = await ask(chalk3.white(" OpenAI API key (Enter to skip): "));
4272
+ const oaiMask = isReconfig && config.providers.openai.apiKey ? ` [${maskKey(config.providers.openai.apiKey)}]` : " (Enter to skip)";
4273
+ const openaiKey = await ask(chalk6.white(` OpenAI API key${oaiMask}: `));
3387
4274
  if (openaiKey) config.providers.openai.apiKey = openaiKey;
3388
- const anthropicKey = await ask(chalk3.white(" Anthropic API key (Enter to skip): "));
4275
+ const antMask = isReconfig && config.providers.anthropic.apiKey ? ` [${maskKey(config.providers.anthropic.apiKey)}]` : " (Enter to skip)";
4276
+ const anthropicKey = await ask(chalk6.white(` Anthropic API key${antMask}: `));
3389
4277
  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."));
4278
+ const hasKey = config.providers.deepseek.apiKey || config.providers.openai.apiKey || config.providers.anthropic.apiKey;
4279
+ if (!hasKey) {
4280
+ console.log(chalk6.red("\n At least one LLM API key is required."));
3392
4281
  process.exit(1);
3393
4282
  }
4283
+ const availableProviders = [];
4284
+ if (config.providers.deepseek.apiKey) availableProviders.push("deepseek");
4285
+ if (config.providers.openai.apiKey) availableProviders.push("openai");
4286
+ if (config.providers.anthropic.apiKey) availableProviders.push("anthropic");
4287
+ if (isReconfig && availableProviders.length > 1) {
4288
+ console.log("");
4289
+ console.log(chalk6.bold.white(" Default Provider"));
4290
+ console.log(chalk6.dim(" Select the default LLM provider (the one used first)."));
4291
+ console.log("");
4292
+ for (let i = 0; i < availableProviders.length; i++) {
4293
+ const marker = availableProviders[i] === config.providers.default ? " (current)" : "";
4294
+ console.log(chalk6.white(` ${i + 1}. ${availableProviders[i]}${marker}`));
4295
+ }
4296
+ console.log("");
4297
+ const choice = await ask(chalk6.white(` Choose [1-${availableProviders.length}] [Enter to keep ${config.providers.default}]: `));
4298
+ const num = parseInt(choice, 10);
4299
+ if (num >= 1 && num <= availableProviders.length) {
4300
+ config.providers.default = availableProviders[num - 1];
4301
+ }
4302
+ } else if (!isReconfig) {
4303
+ config.providers.default = availableProviders[0];
4304
+ console.log(chalk6.dim(` Default provider set to ${config.providers.default}`));
4305
+ }
3394
4306
  hr();
3395
4307
  console.log("");
3396
- console.log(chalk3.white(" Telegram (optional)"));
3397
- console.log(chalk3.dim(" Leave empty to skip. You can add it later."));
4308
+ console.log(chalk6.bold.white(" Telegram (optional)"));
4309
+ if (isReconfig) {
4310
+ console.log(chalk6.dim(' Leave empty to keep current value. Enter "none" to disable.'));
4311
+ } else {
4312
+ console.log(chalk6.dim(" Leave empty to skip. You can add it later."));
4313
+ }
3398
4314
  console.log("");
3399
- const telegramToken = await ask(chalk3.white(" Telegram Bot Token: "));
3400
- if (telegramToken) {
4315
+ const tgMask = isReconfig && config.channels.telegram.botToken ? ` [${maskKey(config.channels.telegram.botToken)}]` : "";
4316
+ const telegramToken = await ask(chalk6.white(` Telegram Bot Token${tgMask}: `));
4317
+ if (isReconfig && telegramToken.toLowerCase() === "none") {
4318
+ config.channels.telegram.enabled = false;
4319
+ config.channels.telegram.botToken = "";
4320
+ } else if (telegramToken) {
3401
4321
  config.channels.telegram.botToken = telegramToken;
3402
4322
  config.channels.telegram.enabled = true;
3403
4323
  }
3404
4324
  hr();
4325
+ console.log("");
4326
+ console.log(chalk6.bold.white(" Token Budget"));
4327
+ console.log("");
4328
+ const budgetPrompt = isReconfig ? chalk6.white(` Daily token budget [${config.tokens.dailyBudget.toLocaleString()}]: `) : chalk6.white(` Daily token budget [${config.tokens.dailyBudget.toLocaleString()}]: `);
4329
+ const budgetStr = await ask(budgetPrompt);
4330
+ if (budgetStr) {
4331
+ const budget = parseInt(budgetStr.replace(/,/g, ""), 10);
4332
+ if (!isNaN(budget) && budget > 0) {
4333
+ config.tokens.dailyBudget = budget;
4334
+ }
4335
+ }
4336
+ hr();
3405
4337
  saveConfig(config);
3406
4338
  const home = getMercuryHome();
3407
4339
  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/`));
4340
+ console.log(chalk6.green(` \u2713 Config saved to ${home}/mercury.yaml`));
4341
+ console.log(chalk6.green(` \u2713 Soul files seeded in ${home}/soul/`));
4342
+ console.log(chalk6.green(` \u2713 Memory stored in ${home}/memory/`));
4343
+ console.log(chalk6.green(` \u2713 Permissions seeded in ${home}/permissions.yaml`));
4344
+ console.log(chalk6.green(` \u2713 Skills directory ready in ${home}/skills/`));
3413
4345
  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"));
4346
+ console.log(chalk6.cyan(` ${config.identity.name} is ready. Run \`mercury start\` to chat.`));
4347
+ console.log(chalk6.dim(" mercury.cosmicstack.org"));
3416
4348
  console.log("");
3417
4349
  }
3418
- async function runAgent() {
4350
+ function autoDaemonize() {
4351
+ const daemon = getDaemonStatus();
4352
+ if (daemon.running) {
4353
+ return;
4354
+ }
4355
+ console.log(chalk6.dim(" Setting up background mode..."));
4356
+ try {
4357
+ if (!isServiceInstalled()) {
4358
+ installService();
4359
+ }
4360
+ } catch {
4361
+ console.log(chalk6.dim(" Service install skipped (can run `mercury service install` later)."));
4362
+ }
4363
+ const ok = tryAutoDaemonize();
4364
+ if (ok) {
4365
+ const status = getDaemonStatus();
4366
+ console.log(chalk6.green(` \u2713 Mercury is running in background (PID: ${status.pid})`));
4367
+ console.log(chalk6.green(" \u2713 Auto-starts on login. Auto-restarts on crash."));
4368
+ console.log(chalk6.dim(" Use `mercury stop` to stop. `mercury restart` to restart."));
4369
+ } else {
4370
+ console.log(chalk6.dim(" Background mode not available. Run `mercury up` to set it up."));
4371
+ }
4372
+ console.log("");
4373
+ }
4374
+ async function runAgent(isDaemon = false) {
3419
4375
  let config = loadConfig();
3420
4376
  config = ensureCreatorField(config);
3421
4377
  const name = config.identity.name;
3422
- banner();
3423
- console.log(chalk3.white(` ${name} is waking up...`));
3424
- console.log("");
4378
+ if (!isDaemon) {
4379
+ banner();
4380
+ console.log(chalk6.white(` ${name} is waking up...`));
4381
+ console.log("");
4382
+ } else {
4383
+ logger.info(`${name} is waking up (daemon mode)...`);
4384
+ }
3425
4385
  const tokenBudget = new TokenBudget(config);
3426
4386
  const providers = new ProviderRegistry(config);
3427
4387
  if (!providers.hasProviders()) {
3428
- console.log(chalk3.red(" No LLM providers available. Run `mercury setup` to configure API keys."));
4388
+ if (isDaemon) {
4389
+ logger.error("No LLM providers available. Run `mercury doctor` to configure API keys.");
4390
+ return;
4391
+ }
4392
+ console.log(chalk6.red(" No LLM providers available. Run `mercury doctor` to configure API keys."));
3429
4393
  process.exit(1);
3430
4394
  }
3431
4395
  const available = providers.listAvailable();
3432
- console.log(chalk3.dim(` Providers: ${available.join(", ")}`));
4396
+ if (!isDaemon) {
4397
+ console.log(chalk6.dim(` Providers: ${available.join(", ")}`));
4398
+ } else {
4399
+ logger.info({ providers: available }, "Providers loaded");
4400
+ }
3433
4401
  const skillLoader = new SkillLoader();
3434
4402
  const skills = skillLoader.discover();
3435
- console.log(chalk3.dim(` Skills: ${skills.length > 0 ? skills.map((s) => s.name).join(", ") : "none installed"}`));
4403
+ if (!isDaemon) {
4404
+ console.log(chalk6.dim(` Skills: ${skills.length > 0 ? skills.map((s) => s.name).join(", ") : "none installed"}`));
4405
+ }
3436
4406
  const scheduler = new Scheduler(config);
3437
4407
  const identity = new Identity();
3438
4408
  const shortTerm = new ShortTermMemory(config);
@@ -3440,6 +4410,13 @@ async function runAgent() {
3440
4410
  const episodic = new EpisodicMemory(config);
3441
4411
  const channels = new ChannelRegistry(config);
3442
4412
  const capabilities = new CapabilityRegistry(skillLoader, scheduler, tokenBudget);
4413
+ capabilities.setChatCommandContext({
4414
+ toolNames: () => capabilities.getToolNames(),
4415
+ skillNames: () => skills.map((s) => s.name),
4416
+ config: () => config,
4417
+ tokenBudget: () => tokenBudget,
4418
+ manual: () => getManual()
4419
+ });
3443
4420
  capabilities.setSendFileHandler(async (filePath) => {
3444
4421
  const msg = channels.getActiveChannels().includes("telegram") ? channels.get("telegram") : channels.get("cli");
3445
4422
  if (msg) {
@@ -3469,21 +4446,29 @@ async function runAgent() {
3469
4446
  }
3470
4447
  const activeCh = channels.getActiveChannels();
3471
4448
  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}`));
4449
+ if (!isDaemon) {
4450
+ console.log(chalk6.dim(` Channels: ${activeCh.join(", ")}`));
4451
+ console.log(chalk6.dim(` Tools: ${toolNames.join(", ")}`));
4452
+ console.log(chalk6.dim(` Permissions: ${getMercuryHome()}/permissions.yaml`));
4453
+ console.log(chalk6.dim(` Schedules: ${getMercuryHome()}/schedules.yaml`));
4454
+ if (config.identity.creator) {
4455
+ console.log(chalk6.dim(` Creator: ${config.identity.creator}`));
4456
+ }
4457
+ hr();
4458
+ console.log("");
4459
+ console.log(chalk6.green(` ${name} is live. Type a message and press Enter.`));
4460
+ console.log(chalk6.dim(" Ctrl+C to exit \xB7 /help for commands"));
4461
+ console.log("");
4462
+ } else {
4463
+ logger.info({ channels: activeCh, tools: toolNames }, "Mercury is live (daemon mode)");
3478
4464
  }
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
4465
  const shutdown = async () => {
3485
- console.log("");
3486
- console.log(chalk3.dim(` ${name} is shutting down...`));
4466
+ if (!isDaemon) {
4467
+ console.log("");
4468
+ console.log(chalk6.dim(` ${name} is shutting down...`));
4469
+ } else {
4470
+ logger.info("Mercury is shutting down (daemon mode)");
4471
+ }
3487
4472
  await agent.shutdown();
3488
4473
  process.exit(0);
3489
4474
  };
@@ -3491,41 +4476,105 @@ async function runAgent() {
3491
4476
  process.on("SIGTERM", shutdown);
3492
4477
  }
3493
4478
  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 () => {
4479
+ program.name("mercury").description("Mercury \u2014 Soul-driven AI agent with permission-hardened tools, token budgets, and multi-channel access.").version("0.2.0").option("-v, --verbose", "Show debug logs").action(async () => {
3495
4480
  if (!isSetupComplete()) {
3496
- await onboarding();
4481
+ await configure();
4482
+ autoDaemonize();
3497
4483
  return;
3498
4484
  }
3499
4485
  await runAgent();
3500
4486
  });
3501
- program.command("start").description("Start Mercury agent").option("-v, --verbose", "Show debug logs").action(async () => {
4487
+ 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) => {
4488
+ if (opts.daemon) {
4489
+ await runWithWatchdog(() => runAgent(true));
4490
+ return;
4491
+ }
4492
+ if (opts.detached) {
4493
+ startBackground();
4494
+ return;
4495
+ }
3502
4496
  if (!isSetupComplete()) {
3503
- await onboarding();
4497
+ await configure();
3504
4498
  return;
3505
4499
  }
3506
4500
  await runAgent();
3507
4501
  });
3508
- program.command("setup").description("Re-run the setup wizard").action(async () => {
3509
- await onboarding();
4502
+ program.command("stop").description("Stop a background Mercury process").action(() => {
4503
+ stopDaemon();
4504
+ });
4505
+ program.command("restart").description("Restart a background Mercury process").action(() => {
4506
+ restartDaemon();
4507
+ });
4508
+ program.command("up").description("Ensure Mercury is running persistently \u2014 installs service if needed, starts daemon").action(async () => {
4509
+ if (!isSetupComplete()) {
4510
+ await configure();
4511
+ }
4512
+ const daemon = getDaemonStatus();
4513
+ if (daemon.running && daemon.pid) {
4514
+ console.log("");
4515
+ console.log(chalk6.green(` Mercury is already running (PID: ${daemon.pid})`));
4516
+ console.log(chalk6.dim(` Logs: ${daemon.logPath}`));
4517
+ console.log("");
4518
+ return;
4519
+ }
4520
+ if (!isServiceInstalled()) {
4521
+ console.log("");
4522
+ console.log(chalk6.cyan(" Installing Mercury as a system service..."));
4523
+ installService();
4524
+ }
4525
+ console.log(chalk6.cyan(" Starting Mercury in background..."));
4526
+ startBackground();
4527
+ });
4528
+ program.command("logs").description("Show recent daemon logs").action(() => {
4529
+ showLogs();
4530
+ });
4531
+ program.command("setup").description("Re-run the setup wizard (reconfigure)").action(async () => {
4532
+ if (isSetupComplete()) {
4533
+ await configure(loadConfig());
4534
+ } else {
4535
+ await configure();
4536
+ }
4537
+ });
4538
+ program.command("doctor").description("Reconfigure Mercury \u2014 change keys, name, settings (Enter to keep current)").action(async () => {
4539
+ if (isSetupComplete()) {
4540
+ await configure(loadConfig());
4541
+ } else {
4542
+ await configure();
4543
+ }
3510
4544
  });
3511
- program.command("status").description("Show current configuration").action(() => {
4545
+ program.command("status").description("Show current configuration and daemon status").action(() => {
3512
4546
  const config = loadConfig();
3513
4547
  const home = getMercuryHome();
3514
4548
  const skillLoader = new SkillLoader();
3515
4549
  const skills = skillLoader.discover();
4550
+ const daemon = getDaemonStatus();
3516
4551
  banner();
3517
- console.log(` Name: ${chalk3.cyan(config.identity.name)}`);
3518
- console.log(` Owner: ${chalk3.white(config.identity.owner || "(not set)")}`);
4552
+ console.log(` Name: ${chalk6.cyan(config.identity.name)}`);
4553
+ console.log(` Owner: ${chalk6.white(config.identity.owner || "(not set)")}`);
3519
4554
  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)}`);
4555
+ console.log(` Creator: ${chalk6.white(config.identity.creator)}`);
4556
+ }
4557
+ console.log(` Provider: ${chalk6.white(config.providers.default)}`);
4558
+ console.log(` Telegram: ${config.channels.telegram.enabled ? chalk6.green("enabled") : chalk6.dim("disabled")}`);
4559
+ console.log(` Skills: ${skills.length > 0 ? chalk6.green(skills.map((s) => s.name).join(", ")) : chalk6.dim("none")}`);
4560
+ console.log(` Budget: ${chalk6.white(config.tokens.dailyBudget.toLocaleString())} tokens/day`);
4561
+ console.log(` Setup: ${isSetupComplete() ? chalk6.green("complete") : chalk6.red("not done")}`);
4562
+ console.log(` Daemon: ${daemon.running ? chalk6.green(`running (PID: ${daemon.pid})`) : chalk6.dim("not running")}`);
4563
+ console.log(` Home: ${chalk6.dim(home)}`);
3528
4564
  console.log("");
3529
4565
  });
4566
+ program.command("help").description("Show capabilities and commands manual").action(() => {
4567
+ console.log(getManual());
4568
+ });
4569
+ var serviceCmd = program.command("service").description("Manage Mercury as a system service (auto-start, crash recovery)");
4570
+ serviceCmd.command("install").description("Install Mercury as a system service (auto-start on boot)").action(() => {
4571
+ installService();
4572
+ });
4573
+ serviceCmd.command("uninstall").description("Uninstall the system service").action(() => {
4574
+ uninstallService();
4575
+ });
4576
+ serviceCmd.command("status").description("Show system service status").action(() => {
4577
+ showServiceStatus();
4578
+ });
3530
4579
  program.parse();
3531
4580
  //# sourceMappingURL=index.js.map