heyio 0.42.0 → 1.0.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.
Files changed (100) hide show
  1. package/README.md +40 -52
  2. package/dist/api/auth.js +35 -38
  3. package/dist/api/server.js +157 -1139
  4. package/dist/config.js +49 -32
  5. package/dist/copilot/agents.js +72 -1055
  6. package/dist/copilot/client.js +6 -17
  7. package/dist/copilot/io-scheduler.js +55 -139
  8. package/dist/copilot/model-router.js +100 -72
  9. package/dist/copilot/orchestrator.js +91 -515
  10. package/dist/copilot/scheduler.js +67 -189
  11. package/dist/copilot/skills.js +41 -366
  12. package/dist/copilot/system-message.js +40 -200
  13. package/dist/copilot/tools.js +191 -2042
  14. package/dist/daemon.js +54 -201
  15. package/dist/index.js +15 -133
  16. package/dist/mcp/config.js +23 -31
  17. package/dist/mcp/index.js +2 -3
  18. package/dist/mcp/registry.js +33 -88
  19. package/dist/notify.js +18 -100
  20. package/dist/paths.js +13 -24
  21. package/dist/setup.js +35 -0
  22. package/dist/store/db.js +111 -297
  23. package/dist/store/feed.js +29 -97
  24. package/dist/store/instances.js +56 -121
  25. package/dist/store/schedules.js +21 -73
  26. package/dist/store/squads.js +35 -186
  27. package/dist/store/tasks.js +25 -168
  28. package/dist/telegram/bot.js +20 -312
  29. package/dist/telegram/handlers.js +39 -3
  30. package/dist/watchdog.js +31 -45
  31. package/dist/wiki/fs.js +38 -155
  32. package/dist/wiki/search.js +31 -44
  33. package/package.json +5 -8
  34. package/web-dist/assets/ChatView-EFFiln1H.js +11 -0
  35. package/web-dist/assets/FeedView-bN4NMOL7.js +6 -0
  36. package/web-dist/assets/LoginView-CNtasq3n.js +1 -0
  37. package/web-dist/assets/McpView-C2CHiwsi.js +1 -0
  38. package/web-dist/assets/SchedulesView-CyilLban.js +1 -0
  39. package/web-dist/assets/SettingsView-1wLXKEF4.js +1 -0
  40. package/web-dist/assets/SkillsView-BLsD-0u0.js +1 -0
  41. package/web-dist/assets/SquadDetailView-CsCw2ZLp.js +21 -0
  42. package/web-dist/assets/SquadsView-DQ3vFlyO.js +6 -0
  43. package/web-dist/assets/WikiView-19M3oqnq.js +21 -0
  44. package/web-dist/assets/api-WGvTsXaE.js +1 -0
  45. package/web-dist/assets/index-D7M5O-_l.css +1 -0
  46. package/web-dist/assets/index-DZOS9syn.js +95 -0
  47. package/web-dist/assets/plus-BOvyX1BC.js +6 -0
  48. package/web-dist/assets/trash-2-DHoetkC4.js +6 -0
  49. package/web-dist/favicon.svg +4 -1
  50. package/web-dist/index.html +7 -10
  51. package/dist/api/logout.test.js +0 -129
  52. package/dist/api/mcp.test.js +0 -285
  53. package/dist/api/wiki.test.js +0 -283
  54. package/dist/auth/session-logic.js +0 -79
  55. package/dist/auth/session-logic.test.js +0 -201
  56. package/dist/copilot/auto-complete-instance.test.js +0 -104
  57. package/dist/copilot/cron.js +0 -136
  58. package/dist/copilot/event-summary.js +0 -286
  59. package/dist/copilot/instance-deactivate.test.js +0 -119
  60. package/dist/copilot/model-router.test.js +0 -71
  61. package/dist/copilot/review-backfill.js +0 -57
  62. package/dist/copilot/session-timeout.js +0 -112
  63. package/dist/copilot/session-timeout.test.js +0 -372
  64. package/dist/copilot/skills.test.js +0 -55
  65. package/dist/copilot/universes.js +0 -469
  66. package/dist/instance-watchdog.js +0 -104
  67. package/dist/instance-watchdog.test.js +0 -183
  68. package/dist/mcp/client.js +0 -109
  69. package/dist/mcp/client.test.js +0 -99
  70. package/dist/mcp/config.test.js +0 -49
  71. package/dist/mcp/registry.test.js +0 -79
  72. package/dist/notify.test.js +0 -232
  73. package/dist/store/feed.test.js +0 -279
  74. package/dist/store/instances.test.js +0 -310
  75. package/dist/store/io-schedules.js +0 -63
  76. package/dist/store/notifications.js +0 -79
  77. package/dist/store/notifications.test.js +0 -197
  78. package/dist/store/schedule-runs.js +0 -46
  79. package/dist/store/squads.test.js +0 -405
  80. package/dist/store/tasks.test.js +0 -150
  81. package/dist/store/worktrees.js +0 -83
  82. package/dist/tui/index.js +0 -286
  83. package/dist/update.js +0 -81
  84. package/dist/watchdog.test.js +0 -83
  85. package/dist/wiki/wiki-squad.test.js +0 -54
  86. package/web-dist/assets/AgentActivityView-CedxxE6K.js +0 -1
  87. package/web-dist/assets/ChatView-DMkYQo_V.js +0 -4
  88. package/web-dist/assets/FeedView-BH4q-31V.js +0 -1
  89. package/web-dist/assets/InboxView-BVwVP4EW.js +0 -1
  90. package/web-dist/assets/LoginView-DRPDhnwu.js +0 -1
  91. package/web-dist/assets/McpView-D8yWz-lq.js +0 -1
  92. package/web-dist/assets/SchedulesView-BzzyncGF.js +0 -1
  93. package/web-dist/assets/SettingsTabs.vue_vue_type_script_setup_true_lang-oW3ySu7Y.js +0 -1
  94. package/web-dist/assets/SkillsView-oxpYuhx7.js +0 -1
  95. package/web-dist/assets/SquadsView-CaKUIKlq.js +0 -1
  96. package/web-dist/assets/StatusIndicator.vue_vue_type_script_setup_true_lang-8U15Qp_Q.js +0 -1
  97. package/web-dist/assets/WikiView-C5jXUlfW.js +0 -1
  98. package/web-dist/assets/index-BrWzNw-N.css +0 -10
  99. package/web-dist/assets/index-f67odrrt.js +0 -81
  100. package/web-dist/icons.svg +0 -24
package/dist/daemon.js CHANGED
@@ -1,208 +1,61 @@
1
- import { getClient, stopClient } from "./copilot/client.js";
2
- import { initOrchestrator, sendToOrchestrator, shutdownOrchestrator } from "./copilot/orchestrator.js";
3
- import { startApiServer, setMessageHandler as setApiHandler, broadcastNotificationToSSE } from "./api/server.js";
4
- import { createBot, startBot, stopBot, sendProactiveMessage, sendBackgroundNotification, setMessageHandler as setTelegramHandler } from "./telegram/bot.js";
5
- import { setTelegramSender, setTuiSender, setSseBroadcaster } from "./notify.js";
6
- import { pruneOldScheduleRuns } from "./store/schedule-runs.js";
7
- import { pruneOldFeedEntries } from "./store/feed.js";
8
- import { printBackgroundNotification } from "./tui/index.js";
1
+ import { loadConfig } from "./config.js";
9
2
  import { getDb, closeDb } from "./store/db.js";
10
- import { clearStaleTasks } from "./store/tasks.js";
11
- import { reconcileAgentStatuses, reconcileSquadStatuses } from "./store/squads.js";
12
- import { backfillReviewVerdicts } from "./copilot/review-backfill.js";
13
- import { startScheduler, stopScheduler } from "./copilot/scheduler.js";
14
- import { startIoScheduler, stopIoScheduler } from "./copilot/io-scheduler.js";
15
- import { config } from "./config.js";
16
- import { startWatchdog } from "./watchdog.js";
17
- import { ensureWikiStructure } from "./wiki/fs.js";
18
- import { autoUpdate } from "./update.js";
19
- import { readdirSync, statSync, rmSync } from "fs";
20
- import { join } from "path";
21
- import { SESSIONS_DIR } from "./paths.js";
22
- const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
23
- function pruneOldSessions() {
24
- try {
25
- const sessionDir = join(SESSIONS_DIR, "session-state");
26
- let entries;
27
- try {
28
- entries = readdirSync(sessionDir);
29
- }
30
- catch {
31
- return;
32
- }
33
- const cutoff = Date.now() - SEVEN_DAYS_MS;
34
- let pruned = 0;
35
- for (const entry of entries) {
36
- const fullPath = join(sessionDir, entry);
37
- try {
38
- const stat = statSync(fullPath);
39
- if (stat.isDirectory() && stat.mtimeMs < cutoff) {
40
- rmSync(fullPath, { recursive: true, force: true });
41
- pruned++;
42
- }
43
- }
44
- catch { /* skip */ }
45
- }
46
- if (pruned > 0) {
47
- console.log(`[io] Pruned ${pruned} orphaned session folder(s)`);
48
- }
49
- }
50
- catch (err) {
51
- console.error("[io] Session pruning failed (non-fatal):", err instanceof Error ? err.message : err);
52
- }
53
- }
54
- export async function startDaemon() {
55
- console.log("[io] Starting IO daemon...");
56
- // Auto-update on startup — exit after update; systemd will restart us
57
- const updated = await autoUpdate();
58
- if (updated) {
59
- console.log("[io] Exiting for systemd restart with updated version...");
60
- process.exit(0);
61
- }
62
- if (config.selfEditEnabled) {
63
- console.log("[io] ⚠ Self-edit mode enabled");
64
- }
3
+ import { PATHS } from "./paths.js";
4
+ import { mkdirSync, existsSync } from "node:fs";
5
+ export async function startDaemon(opts) {
6
+ console.log("[io] Starting daemon...");
7
+ // Ensure directories exist
8
+ for (const dir of [PATHS.home, PATHS.wiki, PATHS.wikiPages, PATHS.skills, PATHS.sessions]) {
9
+ if (!existsSync(dir))
10
+ mkdirSync(dir, { recursive: true });
11
+ }
12
+ const config = loadConfig();
65
13
  // Initialize database
66
- getDb();
67
- console.log("[io] Database initialized");
68
- // Initialize wiki
69
- const wikiIsNew = ensureWikiStructure();
70
- if (wikiIsNew) {
71
- console.log("[io] Created wiki at ~/.io/wiki/");
72
- }
73
- // Clear stale tasks from previous run, and reset any agent/squad rows left
74
- // in 'working' or 'error' state — the in-memory Copilot sessions backing
75
- // those rows did not survive the restart, so the persisted status is lying.
76
- clearStaleTasks();
77
- const resetAgents = reconcileAgentStatuses();
78
- const resetSquads = reconcileSquadStatuses();
79
- if (resetAgents > 0 || resetSquads > 0) {
80
- console.log(`[io] Reconciled stale statuses on startup: ${resetAgents} agent(s), ${resetSquads} squad(s) → idle`);
81
- }
82
- // Backfill any historical peer-review rows whose recorded verdict (approved
83
- // 0/1) does not match what the current parser would extract from the prose.
84
- // Earlier daemon builds had a brittle first-line-only parser that flipped
85
- // many APPROVED reviews into REJECTED (issue #50).
86
- try {
87
- const fixed = backfillReviewVerdicts();
88
- if (fixed > 0) {
89
- console.log(`[io] Backfilled ${fixed} peer-review verdict(s) using current parser`);
90
- }
91
- }
92
- catch (err) {
93
- console.error("[io] Review-verdict backfill failed:", err instanceof Error ? err.message : err);
94
- }
95
- // Prune old sessions
96
- pruneOldSessions();
97
- // Start Copilot SDK client
98
- console.log("[io] Starting Copilot SDK client...");
14
+ const db = getDb();
15
+ console.log("[io] Database initialized.");
16
+ // Initialize Copilot client & orchestrator
17
+ const { getClient } = await import("./copilot/client.js");
99
18
  const client = await getClient();
100
- console.log("[io] Copilot SDK client ready");
101
- // Initialize orchestrator
102
- console.log("[io] Creating orchestrator session...");
103
- await initOrchestrator(client);
104
- console.log("[io] Orchestrator session ready");
105
- // Wire up API message handler
106
- setApiHandler(async (text, connectionId, callback, attachments) => {
107
- await sendToOrchestrator(text, { type: "tui", connectionId }, callback, attachments);
108
- });
109
- // Start HTTP API
110
- await startApiServer();
111
- // Wire up Telegram handler
112
- if (config.telegramEnabled) {
113
- setTelegramHandler(async (text, chatId, messageId, callback, attachments) => {
114
- await sendToOrchestrator(text, { type: "telegram", chatId, messageId }, callback, attachments);
115
- });
116
- createBot();
117
- await startBot();
118
- }
119
- else {
120
- console.log("[io] Telegram not configured — skipping bot. Set telegramBotToken in ~/.io/config.json");
121
- }
122
- // Start the squad scheduler (background cron-style stand-ups).
123
- startScheduler();
124
- // Start the IO-level scheduler (squad-independent recurring tasks).
19
+ console.log("[io] Copilot client connected.");
20
+ const { initOrchestrator } = await import("./copilot/orchestrator.js");
21
+ await initOrchestrator(client, { selfEdit: opts.selfEdit });
22
+ console.log("[io] Orchestrator session ready.");
23
+ // Start HTTP API server
24
+ const { startApiServer } = await import("./api/server.js");
25
+ await startApiServer(config);
26
+ console.log(`[io] API server listening on port ${config.port}.`);
27
+ // Start Telegram bot (if configured)
28
+ if (config.telegramEnabled && config.telegramBotToken) {
29
+ const { startBot } = await import("./telegram/bot.js");
30
+ await startBot(config);
31
+ console.log("[io] Telegram bot started.");
32
+ }
33
+ // Start schedulers
34
+ const { startSquadScheduler } = await import("./copilot/scheduler.js");
35
+ const { startIoScheduler } = await import("./copilot/io-scheduler.js");
36
+ startSquadScheduler();
125
37
  startIoScheduler();
126
- // Background-notification dispatch surfaces (issue #78)
127
- setSseBroadcaster((p) => broadcastNotificationToSSE(p));
128
- setTuiSender((opts) => printBackgroundNotification(opts));
129
- setTelegramSender((opts) => sendBackgroundNotification(opts));
130
- // Daily cleanup — prune schedule runs and notifications older than 30 days
131
- const PRUNE_INTERVAL_MS = 24 * 60 * 60 * 1000;
132
- const PRUNE_RETENTION_DAYS = 30;
133
- // Start event loop watchdog
134
- let stopWatchdog;
38
+ console.log("[io] Schedulers active.");
39
+ // Start watchdog
135
40
  if (config.watchdogEnabled) {
136
- stopWatchdog = startWatchdog();
137
- console.error("[io] Event loop watchdog started");
138
- }
139
- const pruneTimer = setInterval(() => {
140
- try {
141
- const runsDeleted = pruneOldScheduleRuns(PRUNE_RETENTION_DAYS);
142
- const notificationsDeleted = pruneOldFeedEntries(PRUNE_RETENTION_DAYS);
143
- if (runsDeleted > 0 || notificationsDeleted > 0) {
144
- console.log(`[prune] Cleaned up ${runsDeleted} schedule runs and ${notificationsDeleted} feed entries older than ${PRUNE_RETENTION_DAYS} days`);
145
- }
146
- }
147
- catch (err) {
148
- console.error("[prune] Error during cleanup:", err);
149
- }
150
- }, PRUNE_INTERVAL_MS);
151
- pruneTimer.unref();
152
- // Run once on startup after a brief delay
153
- const pruneStartup = setTimeout(() => {
154
- try {
155
- pruneOldScheduleRuns(PRUNE_RETENTION_DAYS);
156
- pruneOldFeedEntries(PRUNE_RETENTION_DAYS);
157
- }
158
- catch { /* best effort */ }
159
- }, 5000);
160
- pruneStartup.unref?.();
161
- console.log("[io] IO is fully operational.");
162
- // Notify Telegram if restarting
163
- if (config.telegramEnabled && process.env.IO_RESTARTED === "1") {
164
- await sendProactiveMessage("I'm back online 🟢").catch(() => { });
165
- delete process.env.IO_RESTARTED;
166
- }
167
- }
168
- // Graceful shutdown
169
- let shuttingDown = false;
170
- async function shutdown() {
171
- if (shuttingDown) {
172
- console.log("\n[io] Forced exit.");
173
- process.exit(1);
174
- }
175
- shuttingDown = true;
176
- console.log("\n[io] Shutting down... (Ctrl+C again to force)");
177
- const forceTimer = setTimeout(() => {
178
- console.log("[io] Shutdown timed out — forcing exit.");
179
- process.exit(1);
180
- }, 5000);
181
- forceTimer.unref();
182
- if (config.telegramEnabled) {
183
- try {
184
- await stopBot();
185
- }
186
- catch { /* best effort */ }
187
- }
188
- stopScheduler();
189
- stopIoScheduler();
190
- await shutdownOrchestrator();
191
- try {
192
- await stopClient();
193
- }
194
- catch { /* best effort */ }
195
- closeDb();
196
- console.log("[io] Goodbye.");
197
- process.exit(0);
41
+ const { startWatchdog } = await import("./watchdog.js");
42
+ startWatchdog();
43
+ console.log("[io] Watchdog active.");
44
+ }
45
+ console.log("[io] Daemon running. Press Ctrl+C to stop.");
46
+ // Graceful shutdown
47
+ let shuttingDown = false;
48
+ const shutdown = async () => {
49
+ if (shuttingDown) {
50
+ console.log("[io] Forcing exit...");
51
+ process.exit(1);
52
+ }
53
+ shuttingDown = true;
54
+ console.log("\n[io] Shutting down...");
55
+ closeDb();
56
+ process.exit(0);
57
+ };
58
+ process.on("SIGINT", shutdown);
59
+ process.on("SIGTERM", shutdown);
198
60
  }
199
- process.on("SIGINT", shutdown);
200
- process.on("SIGTERM", shutdown);
201
- process.on("unhandledRejection", (reason) => {
202
- console.error("[io] Unhandled rejection (kept alive):", reason);
203
- });
204
- process.on("uncaughtException", (err) => {
205
- console.error("[io] Uncaught exception — shutting down:", err);
206
- process.exit(1);
207
- });
208
61
  //# sourceMappingURL=daemon.js.map
package/dist/index.js CHANGED
@@ -1,148 +1,30 @@
1
1
  #!/usr/bin/env node
2
- const [major] = process.versions.node.split(".").map(Number);
3
- if (major < 22) {
4
- console.error(`IO requires Node.js 22 or later (current: ${process.version}). Please upgrade: https://nodejs.org`);
5
- process.exit(1);
6
- }
7
2
  import { Command } from "commander";
8
- import { startDaemon } from "./daemon.js";
9
- import { startTui, setMessageHandler as setTuiHandler } from "./tui/index.js";
10
- import { sendToOrchestrator, initOrchestrator } from "./copilot/orchestrator.js";
11
- import { getClient } from "./copilot/client.js";
12
- import { getDb } from "./store/db.js";
13
- import { ensureWikiStructure } from "./wiki/fs.js";
14
- import { startApiServer, setMessageHandler as setApiHandler } from "./api/server.js";
15
- import { listSkills, installSkill, removeSkill, searchSkillsRegistry } from "./copilot/skills.js";
16
- import { config, saveConfig } from "./config.js";
17
- import { createInterface } from "readline";
18
- import { IO_VERSION } from "./paths.js";
3
+ import { readFileSync } from "node:fs";
4
+ import { fileURLToPath } from "node:url";
5
+ import { dirname, join } from "node:path";
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
19
9
  const program = new Command();
20
10
  program
21
11
  .name("io")
22
- .description("IO — personal AI assistant daemon")
23
- .version(IO_VERSION);
12
+ .description("IO — a personal AI assistant daemon")
13
+ .version(pkg.version);
24
14
  program
25
- .option("--daemon", "Run as a background daemon")
15
+ .command("daemon", { isDefault: true })
16
+ .description("Run IO as a background daemon (Telegram + HTTP API)")
26
17
  .option("--self-edit", "Allow IO to modify its own source code")
27
18
  .action(async (opts) => {
28
- if (opts.selfEdit) {
29
- config.selfEditEnabled = true;
30
- }
31
- if (opts.daemon) {
32
- await startDaemon();
33
- }
34
- else {
35
- // TUI mode — start minimal services and launch interactive chat
36
- console.log("[io] Starting IO in interactive mode...");
37
- getDb();
38
- ensureWikiStructure();
39
- const client = await getClient();
40
- await initOrchestrator(client);
41
- // Wire up API handler for TUI bridge
42
- setApiHandler(async (text, connectionId, callback) => {
43
- await sendToOrchestrator(text, { type: "tui", connectionId }, callback);
44
- });
45
- await startApiServer();
46
- // Wire up TUI handler
47
- setTuiHandler(async (text, callback) => {
48
- await sendToOrchestrator(text, { type: "tui", connectionId: "tui-main" }, callback);
49
- });
50
- await startTui();
51
- }
19
+ const { startDaemon } = await import("./daemon.js");
20
+ await startDaemon({ selfEdit: opts.selfEdit ?? false });
52
21
  });
53
- // Skill management commands
54
- const skillCmd = program.command("skill").description("Manage IO skills");
55
- skillCmd
56
- .command("list")
57
- .description("List installed skills")
58
- .action(() => {
59
- const skills = listSkills();
60
- if (skills.length === 0) {
61
- console.log("No skills installed.");
62
- return;
63
- }
64
- for (const s of skills) {
65
- console.log(` ${s.name} (${s.slug})`);
66
- if (s.description)
67
- console.log(` ${s.description}`);
68
- }
69
- });
70
- skillCmd
71
- .command("add <repo-url>")
72
- .description("Install a skill from a git repository")
73
- .action(async (repoUrl) => {
74
- try {
75
- console.log(`Installing skill from ${repoUrl}...`);
76
- const result = await installSkill(repoUrl);
77
- const skills = Array.isArray(result) ? result : [result];
78
- for (const skill of skills) {
79
- console.log(`✓ Installed: ${skill.name} (${skill.slug})`);
80
- }
81
- }
82
- catch (err) {
83
- console.error(`✗ ${err instanceof Error ? err.message : String(err)}`);
84
- process.exit(1);
85
- }
86
- });
87
- skillCmd
88
- .command("remove <slug>")
89
- .description("Remove an installed skill")
90
- .action((slug) => {
91
- const removed = removeSkill(slug);
92
- if (removed) {
93
- console.log(`✓ Removed: ${slug}`);
94
- }
95
- else {
96
- console.log(`Skill not found: ${slug}`);
97
- }
98
- });
99
- skillCmd
100
- .command("search <query>")
101
- .description("Search the skills registry")
102
- .action(async (query) => {
103
- const results = await searchSkillsRegistry(query);
104
- if (results.length === 0) {
105
- console.log("No skills found.");
106
- return;
107
- }
108
- for (const r of results) {
109
- console.log(` ${r.name}`);
110
- console.log(` ${r.description}`);
111
- console.log(` ${r.repoUrl}`);
112
- }
113
- });
114
- // Setup command
115
22
  program
116
23
  .command("setup")
117
- .description("Configure IO (Telegram bot token, user ID, etc.)")
24
+ .description("Configure IO (Telegram, Supabase, etc.)")
118
25
  .action(async () => {
119
- const rl = createInterface({ input: process.stdin, output: process.stdout });
120
- const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
121
- console.log("\n🔧 IO Setup\n");
122
- const token = await ask(`Telegram Bot Token${config.telegramBotToken ? " (press Enter to keep current)" : ""}: `);
123
- if (token.trim()) {
124
- config.telegramBotToken = token.trim();
125
- }
126
- const userId = await ask(`Telegram User ID${config.authorizedUserId ? ` (current: ${config.authorizedUserId})` : ""}: `);
127
- if (userId.trim()) {
128
- const parsed = parseInt(userId.trim(), 10);
129
- if (!isNaN(parsed))
130
- config.authorizedUserId = parsed;
131
- }
132
- config.telegramEnabled = !!(config.telegramBotToken && config.authorizedUserId);
133
- saveConfig({
134
- telegramBotToken: config.telegramBotToken,
135
- authorizedUserId: config.authorizedUserId,
136
- telegramEnabled: config.telegramEnabled,
137
- });
138
- console.log("\n✓ Configuration saved to ~/.io/config.json");
139
- if (config.telegramEnabled) {
140
- console.log(" Telegram: enabled");
141
- }
142
- else {
143
- console.log(" Telegram: disabled (need both bot token and user ID)");
144
- }
145
- rl.close();
26
+ const { runSetup } = await import("./setup.js");
27
+ await runSetup();
146
28
  });
147
29
  program.parse();
148
30
  //# sourceMappingURL=index.js.map
@@ -1,37 +1,29 @@
1
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
2
- import { dirname } from "path";
3
- import { IO_HOME } from "../paths.js";
4
- import { join } from "path";
5
- export const MCP_CONFIG_PATH = join(IO_HOME, "mcp.json");
6
- // Mutable override for tests — mirrors the setDbPathForTests pattern.
7
- let _configPath = MCP_CONFIG_PATH;
8
- export function setMcpConfigPathForTests(path) {
9
- _configPath = path;
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { PATHS } from "../paths.js";
3
+ export function loadMcpConfig() {
4
+ if (!existsSync(PATHS.mcpConfig))
5
+ return [];
6
+ const raw = JSON.parse(readFileSync(PATHS.mcpConfig, "utf-8"));
7
+ return Array.isArray(raw.servers) ? raw.servers : [];
10
8
  }
11
- export function resetMcpConfigPath() {
12
- _configPath = MCP_CONFIG_PATH;
9
+ export function saveMcpConfig(servers) {
10
+ writeFileSync(PATHS.mcpConfig, JSON.stringify({ servers }, null, 2) + "\n");
13
11
  }
14
- export function loadMcpConfig() {
15
- if (!existsSync(_configPath)) {
16
- return { servers: [] };
17
- }
18
- try {
19
- const raw = readFileSync(_configPath, "utf-8");
20
- const parsed = JSON.parse(raw);
21
- if (!parsed.servers || !Array.isArray(parsed.servers)) {
22
- return { servers: [] };
23
- }
24
- return parsed;
25
- }
26
- catch {
27
- return { servers: [] };
28
- }
12
+ export function addMcpServer(server) {
13
+ const servers = loadMcpConfig();
14
+ servers.push(server);
15
+ saveMcpConfig(servers);
16
+ }
17
+ export function removeMcpServer(id) {
18
+ const servers = loadMcpConfig().filter((s) => s.id !== id);
19
+ saveMcpConfig(servers);
29
20
  }
30
- export function saveMcpConfig(config) {
31
- const dir = dirname(_configPath);
32
- if (!existsSync(dir)) {
33
- mkdirSync(dir, { recursive: true });
21
+ export function toggleMcpServer(id, enabled) {
22
+ const servers = loadMcpConfig();
23
+ const server = servers.find((s) => s.id === id);
24
+ if (server) {
25
+ server.enabled = enabled;
26
+ saveMcpConfig(servers);
34
27
  }
35
- writeFileSync(_configPath, JSON.stringify(config, null, 2), "utf-8");
36
28
  }
37
29
  //# sourceMappingURL=config.js.map
package/dist/mcp/index.js CHANGED
@@ -1,4 +1,3 @@
1
- export { loadMcpConfig, saveMcpConfig, MCP_CONFIG_PATH } from "./config.js";
2
- export { McpConnectionManager } from "./client.js";
3
- export { createMcpTools } from "./registry.js";
1
+ export { loadMcpConfig, saveMcpConfig, addMcpServer, removeMcpServer, toggleMcpServer } from "./config.js";
2
+ export { initMcpRegistry, getMcpServersForSession, listServers } from "./registry.js";
4
3
  //# sourceMappingURL=index.js.map
@@ -1,96 +1,41 @@
1
- import { defineTool } from "@github/copilot-sdk";
2
- import { z } from "zod";
3
- /**
4
- * Sanitize a name for use as a tool identifier (alphanumeric + underscore only).
5
- */
6
- function sanitizeName(name) {
7
- return name.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
1
+ import { loadMcpConfig } from "./config.js";
2
+ // Module-level array of MCP tools loaded at startup
3
+ let loadedServers = [];
4
+ export function initMcpRegistry() {
5
+ loadedServers = loadMcpConfig().filter((s) => s.enabled);
8
6
  }
9
- /**
10
- * Convert an MCP JSON Schema inputSchema to a Zod schema.
11
- * Falls back to z.object({}) for passthrough if schema is missing or unparseable.
12
- */
13
- function jsonSchemaToZod(inputSchema) {
14
- if (!inputSchema || !inputSchema.properties) {
15
- return z.object({}).passthrough();
16
- }
17
- const properties = inputSchema.properties;
18
- const required = new Set(inputSchema.required ?? []);
19
- const shape = {};
20
- for (const [key, prop] of Object.entries(properties)) {
21
- let field;
22
- switch (prop.type) {
23
- case "string":
24
- field = z.string();
25
- break;
26
- case "number":
27
- case "integer":
28
- field = z.number();
29
- break;
30
- case "boolean":
31
- field = z.boolean();
32
- break;
33
- case "array":
34
- field = z.array(z.unknown());
35
- break;
36
- case "object":
37
- field = z.record(z.string(), z.unknown());
38
- break;
39
- default:
40
- field = z.unknown();
41
- }
42
- if (prop.description) {
43
- field = field.describe(prop.description);
7
+ export function getMcpServersForSession() {
8
+ if (loadedServers.length === 0)
9
+ return undefined;
10
+ const result = {};
11
+ for (const server of loadedServers) {
12
+ if (server.type === "stdio" && server.command) {
13
+ result[server.name] = {
14
+ type: "local",
15
+ command: server.command,
16
+ args: server.args ?? [],
17
+ tools: ["*"],
18
+ };
44
19
  }
45
- if (!required.has(key)) {
46
- field = field.optional();
20
+ else if (server.type === "http" && server.url) {
21
+ result[server.name] = {
22
+ type: "http",
23
+ url: server.url,
24
+ headers: server.headers,
25
+ };
47
26
  }
48
- shape[key] = field;
49
27
  }
50
- return z.object(shape).passthrough();
28
+ return Object.keys(result).length > 0 ? result : undefined;
51
29
  }
52
- /**
53
- * Create Copilot SDK tools from all enabled MCP servers.
54
- * Connects to each server, lists its tools, and wraps them.
55
- */
56
- export async function createMcpTools(manager, config) {
57
- const entries = [];
58
- const enabledServers = config.servers.filter((s) => s.enabled !== false);
59
- for (const serverConfig of enabledServers) {
60
- try {
61
- const tools = await manager.listTools(serverConfig);
62
- for (const mcpTool of tools) {
63
- const toolName = `mcp_${sanitizeName(serverConfig.name)}_${sanitizeName(mcpTool.name)}`;
64
- const description = mcpTool.description
65
- ? `[MCP: ${serverConfig.name}] ${mcpTool.description}`
66
- : `[MCP: ${serverConfig.name}] ${mcpTool.name}`;
67
- const parameters = jsonSchemaToZod(mcpTool.inputSchema);
68
- const tool = defineTool(toolName, {
69
- description,
70
- skipPermission: true,
71
- parameters: parameters,
72
- handler: async (args) => {
73
- try {
74
- const result = await manager.callTool(serverConfig, mcpTool.name, args);
75
- return typeof result === "string" ? result : JSON.stringify(result);
76
- }
77
- catch (err) {
78
- return `MCP tool error (${serverConfig.name}/${mcpTool.name}): ${err instanceof Error ? err.message : String(err)}`;
79
- }
80
- },
81
- });
82
- entries.push({
83
- serverName: serverConfig.name,
84
- serverConfig,
85
- mcpToolName: mcpTool.name,
86
- tool: tool,
87
- });
88
- }
89
- }
90
- catch (err) {
91
- console.error(`[mcp] Failed to connect to server "${serverConfig.name}":`, err instanceof Error ? err.message : err);
92
- }
30
+ export function listServers() {
31
+ return loadMcpConfig();
32
+ }
33
+ export function addServerToRegistry(server) {
34
+ if (server.enabled) {
35
+ loadedServers.push(server);
93
36
  }
94
- return entries;
37
+ }
38
+ export function removeServerFromRegistry(id) {
39
+ loadedServers = loadedServers.filter((s) => s.id !== id);
95
40
  }
96
41
  //# sourceMappingURL=registry.js.map