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.
- package/README.md +40 -52
- package/dist/api/auth.js +35 -38
- package/dist/api/server.js +157 -1139
- package/dist/config.js +49 -32
- package/dist/copilot/agents.js +72 -1055
- package/dist/copilot/client.js +6 -17
- package/dist/copilot/io-scheduler.js +55 -139
- package/dist/copilot/model-router.js +100 -72
- package/dist/copilot/orchestrator.js +91 -515
- package/dist/copilot/scheduler.js +67 -189
- package/dist/copilot/skills.js +41 -366
- package/dist/copilot/system-message.js +40 -200
- package/dist/copilot/tools.js +191 -2042
- package/dist/daemon.js +54 -201
- package/dist/index.js +15 -133
- package/dist/mcp/config.js +23 -31
- package/dist/mcp/index.js +2 -3
- package/dist/mcp/registry.js +33 -88
- package/dist/notify.js +18 -100
- package/dist/paths.js +13 -24
- package/dist/setup.js +35 -0
- package/dist/store/db.js +111 -297
- package/dist/store/feed.js +29 -97
- package/dist/store/instances.js +56 -121
- package/dist/store/schedules.js +21 -73
- package/dist/store/squads.js +35 -186
- package/dist/store/tasks.js +25 -168
- package/dist/telegram/bot.js +20 -312
- package/dist/telegram/handlers.js +39 -3
- package/dist/watchdog.js +31 -45
- package/dist/wiki/fs.js +38 -155
- package/dist/wiki/search.js +31 -44
- package/package.json +5 -8
- package/web-dist/assets/ChatView-EFFiln1H.js +11 -0
- package/web-dist/assets/FeedView-bN4NMOL7.js +6 -0
- package/web-dist/assets/LoginView-CNtasq3n.js +1 -0
- package/web-dist/assets/McpView-C2CHiwsi.js +1 -0
- package/web-dist/assets/SchedulesView-CyilLban.js +1 -0
- package/web-dist/assets/SettingsView-1wLXKEF4.js +1 -0
- package/web-dist/assets/SkillsView-BLsD-0u0.js +1 -0
- package/web-dist/assets/SquadDetailView-CsCw2ZLp.js +21 -0
- package/web-dist/assets/SquadsView-DQ3vFlyO.js +6 -0
- package/web-dist/assets/WikiView-19M3oqnq.js +21 -0
- package/web-dist/assets/api-WGvTsXaE.js +1 -0
- package/web-dist/assets/index-D7M5O-_l.css +1 -0
- package/web-dist/assets/index-DZOS9syn.js +95 -0
- package/web-dist/assets/plus-BOvyX1BC.js +6 -0
- package/web-dist/assets/trash-2-DHoetkC4.js +6 -0
- package/web-dist/favicon.svg +4 -1
- package/web-dist/index.html +7 -10
- package/dist/api/logout.test.js +0 -129
- package/dist/api/mcp.test.js +0 -285
- package/dist/api/wiki.test.js +0 -283
- package/dist/auth/session-logic.js +0 -79
- package/dist/auth/session-logic.test.js +0 -201
- package/dist/copilot/auto-complete-instance.test.js +0 -104
- package/dist/copilot/cron.js +0 -136
- package/dist/copilot/event-summary.js +0 -286
- package/dist/copilot/instance-deactivate.test.js +0 -119
- package/dist/copilot/model-router.test.js +0 -71
- package/dist/copilot/review-backfill.js +0 -57
- package/dist/copilot/session-timeout.js +0 -112
- package/dist/copilot/session-timeout.test.js +0 -372
- package/dist/copilot/skills.test.js +0 -55
- package/dist/copilot/universes.js +0 -469
- package/dist/instance-watchdog.js +0 -104
- package/dist/instance-watchdog.test.js +0 -183
- package/dist/mcp/client.js +0 -109
- package/dist/mcp/client.test.js +0 -99
- package/dist/mcp/config.test.js +0 -49
- package/dist/mcp/registry.test.js +0 -79
- package/dist/notify.test.js +0 -232
- package/dist/store/feed.test.js +0 -279
- package/dist/store/instances.test.js +0 -310
- package/dist/store/io-schedules.js +0 -63
- package/dist/store/notifications.js +0 -79
- package/dist/store/notifications.test.js +0 -197
- package/dist/store/schedule-runs.js +0 -46
- package/dist/store/squads.test.js +0 -405
- package/dist/store/tasks.test.js +0 -150
- package/dist/store/worktrees.js +0 -83
- package/dist/tui/index.js +0 -286
- package/dist/update.js +0 -81
- package/dist/watchdog.test.js +0 -83
- package/dist/wiki/wiki-squad.test.js +0 -54
- package/web-dist/assets/AgentActivityView-CedxxE6K.js +0 -1
- package/web-dist/assets/ChatView-DMkYQo_V.js +0 -4
- package/web-dist/assets/FeedView-BH4q-31V.js +0 -1
- package/web-dist/assets/InboxView-BVwVP4EW.js +0 -1
- package/web-dist/assets/LoginView-DRPDhnwu.js +0 -1
- package/web-dist/assets/McpView-D8yWz-lq.js +0 -1
- package/web-dist/assets/SchedulesView-BzzyncGF.js +0 -1
- package/web-dist/assets/SettingsTabs.vue_vue_type_script_setup_true_lang-oW3ySu7Y.js +0 -1
- package/web-dist/assets/SkillsView-oxpYuhx7.js +0 -1
- package/web-dist/assets/SquadsView-CaKUIKlq.js +0 -1
- package/web-dist/assets/StatusIndicator.vue_vue_type_script_setup_true_lang-8U15Qp_Q.js +0 -1
- package/web-dist/assets/WikiView-C5jXUlfW.js +0 -1
- package/web-dist/assets/index-BrWzNw-N.css +0 -10
- package/web-dist/assets/index-f67odrrt.js +0 -81
- package/web-dist/icons.svg +0 -24
package/dist/daemon.js
CHANGED
|
@@ -1,208 +1,61 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
69
|
-
const
|
|
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
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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 {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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(
|
|
12
|
+
.description("IO — a personal AI assistant daemon")
|
|
13
|
+
.version(pkg.version);
|
|
24
14
|
program
|
|
25
|
-
.
|
|
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
|
-
|
|
29
|
-
|
|
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
|
|
24
|
+
.description("Configure IO (Telegram, Supabase, etc.)")
|
|
118
25
|
.action(async () => {
|
|
119
|
-
const
|
|
120
|
-
|
|
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
|
package/dist/mcp/config.js
CHANGED
|
@@ -1,37 +1,29 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
12
|
-
|
|
9
|
+
export function saveMcpConfig(servers) {
|
|
10
|
+
writeFileSync(PATHS.mcpConfig, JSON.stringify({ servers }, null, 2) + "\n");
|
|
13
11
|
}
|
|
14
|
-
export function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
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,
|
|
2
|
-
export {
|
|
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
|
package/dist/mcp/registry.js
CHANGED
|
@@ -1,96 +1,41 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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 (
|
|
46
|
-
|
|
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
|
|
28
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
51
29
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
37
|
+
}
|
|
38
|
+
export function removeServerFromRegistry(id) {
|
|
39
|
+
loadedServers = loadedServers.filter((s) => s.id !== id);
|
|
95
40
|
}
|
|
96
41
|
//# sourceMappingURL=registry.js.map
|