multiagents 0.1.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/cli/session.ts ADDED
@@ -0,0 +1,278 @@
1
+ // ============================================================================
2
+ // multiagents — Session Management Commands
3
+ // ============================================================================
4
+
5
+ import { DEFAULT_BROKER_PORT, BROKER_HOSTNAME, SESSION_DIR, SESSION_FILE } from "../shared/constants.ts";
6
+ import { BrokerClient } from "../shared/broker-client.ts";
7
+ import { getGitRoot, formatTime, timeSince, slugify } from "../shared/utils.ts";
8
+ import type { SessionFile } from "../shared/types.ts";
9
+ import * as readline from "node:readline";
10
+ import * as path from "node:path";
11
+ import * as fs from "node:fs";
12
+
13
+ const BROKER_PORT = parseInt(process.env.MULTIAGENTS_PORT ?? String(DEFAULT_BROKER_PORT), 10);
14
+ const BROKER_URL = `http://${BROKER_HOSTNAME}:${BROKER_PORT}`;
15
+
16
+ function prompt(question: string): Promise<string> {
17
+ return new Promise((resolve) => {
18
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
19
+ rl.question(`${question}: `, (answer) => {
20
+ rl.close();
21
+ resolve(answer.trim());
22
+ });
23
+ });
24
+ }
25
+
26
+ function readLocalSession(): SessionFile | null {
27
+ try {
28
+ const text = fs.readFileSync(path.resolve(process.cwd(), SESSION_FILE), "utf-8");
29
+ return JSON.parse(text) as SessionFile;
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+
35
+ export async function sessionCommand(args: string[]): Promise<void> {
36
+ const sub = args[0];
37
+ const client = new BrokerClient(BROKER_URL);
38
+
39
+ switch (sub) {
40
+ case "create":
41
+ await create(client, args.slice(1).join(" ") || undefined);
42
+ break;
43
+ case "list":
44
+ await list(client);
45
+ break;
46
+ case "resume":
47
+ await resume(client, args[1]);
48
+ break;
49
+ case "pause":
50
+ await pause(client, args[1]);
51
+ break;
52
+ case "archive":
53
+ await archive(client, args[1]);
54
+ break;
55
+ case "delete":
56
+ await deleteSession(client, args[1]);
57
+ break;
58
+ case "export":
59
+ await exportSession(client, args[1]);
60
+ break;
61
+ default:
62
+ console.log(`Usage: multiagents session <create|list|resume|pause|archive|delete|export> [args]`);
63
+ process.exit(1);
64
+ }
65
+ }
66
+
67
+ async function create(client: BrokerClient, name?: string): Promise<void> {
68
+ if (!name) {
69
+ console.error("Usage: multiagents session create <name>");
70
+ process.exit(1);
71
+ }
72
+
73
+ const projectDir = process.cwd();
74
+ const gitRoot = await getGitRoot(projectDir);
75
+ const sessionId = slugify(name);
76
+
77
+ const session = await client.createSession({
78
+ id: sessionId,
79
+ name,
80
+ project_dir: projectDir,
81
+ git_root: gitRoot,
82
+ });
83
+
84
+ // Write local session file
85
+ const sessionDir = path.join(projectDir, SESSION_DIR);
86
+ if (!fs.existsSync(sessionDir)) fs.mkdirSync(sessionDir, { recursive: true });
87
+ const sessionFile: SessionFile = {
88
+ session_id: session.id,
89
+ created_at: new Date().toISOString(),
90
+ broker_port: BROKER_PORT,
91
+ };
92
+ await Bun.write(path.join(projectDir, SESSION_FILE), JSON.stringify(sessionFile, null, 2));
93
+
94
+ console.log(`Session created: ${session.name} (${session.id})`);
95
+ console.log(`Wrote ${SESSION_FILE}`);
96
+ }
97
+
98
+ async function list(client: BrokerClient): Promise<void> {
99
+ const sessions = await client.listSessions();
100
+
101
+ if (sessions.length === 0) {
102
+ console.log("No sessions found.");
103
+ return;
104
+ }
105
+
106
+ const local = readLocalSession();
107
+ console.log("\n Sessions:\n");
108
+
109
+ for (const s of sessions) {
110
+ const active = local?.session_id === s.id ? " \x1b[36m(current)\x1b[0m" : "";
111
+ const statusColor = s.status === "active" ? "\x1b[32m" : s.status === "paused" ? "\x1b[33m" : "\x1b[90m";
112
+ console.log(` ${s.id}${active}`);
113
+ console.log(` Name: ${s.name}`);
114
+ console.log(` Status: ${statusColor}${s.status}\x1b[0m`);
115
+ console.log(` Dir: ${s.project_dir}`);
116
+ console.log(` Active: ${timeSince(s.last_active_at)}`);
117
+ console.log(` Created: ${formatTime(s.created_at)}`);
118
+
119
+ try {
120
+ const slots = await client.listSlots(s.id);
121
+ const connected = slots.filter((sl) => sl.status === "connected").length;
122
+ console.log(` Agents: ${connected}/${slots.length} connected`);
123
+ } catch { /* broker may not support slots yet */ }
124
+
125
+ console.log();
126
+ }
127
+ }
128
+
129
+ async function resume(client: BrokerClient, sessionId?: string): Promise<void> {
130
+ const id = sessionId ?? readLocalSession()?.session_id;
131
+ if (!id) {
132
+ console.error("No session specified and no local session found.");
133
+ process.exit(1);
134
+ }
135
+
136
+ await client.updateSession({ id, status: "active", pause_reason: null, paused_at: null });
137
+ console.log(`Session "${id}" resumed.`);
138
+
139
+ // Check for disconnected slots and offer to relaunch
140
+ try {
141
+ const slots = await client.listSlots(id);
142
+ const disconnected = slots.filter((s) => s.status === "disconnected");
143
+ if (disconnected.length > 0) {
144
+ console.log(`\n${disconnected.length} agent(s) disconnected:`);
145
+ for (const s of disconnected) {
146
+ console.log(` Slot ${s.id}: ${s.display_name ?? s.agent_type} (${s.agent_type}) - ${s.role ?? "no role"}`);
147
+ }
148
+ const answer = await prompt("\nRelaunch disconnected agents? (y/n)");
149
+ if (answer.toLowerCase() === "y") {
150
+ await relaunchAgents(disconnected, id);
151
+ }
152
+ }
153
+ } catch { /* ok */ }
154
+ }
155
+
156
+ async function relaunchAgents(slots: Array<{ id: number; agent_type: string; display_name: string | null; role: string | null }>, sessionId: string): Promise<void> {
157
+ const platform = process.platform;
158
+
159
+ for (const slot of slots) {
160
+ const cmd = slot.agent_type; // claude, codex, gemini
161
+ const envVars = `MULTIAGENTS_SESSION=${sessionId} MULTIAGENTS_ROLE=${slot.role ?? ""} MULTIAGENTS_NAME=${slot.display_name ?? ""}`;
162
+ try {
163
+ if (platform === "darwin") {
164
+ // macOS: open a new Terminal tab with session env vars
165
+ Bun.spawnSync([
166
+ "osascript", "-e",
167
+ `tell application "Terminal" to do script "${envVars} ${cmd}"`,
168
+ ]);
169
+ } else {
170
+ // Linux: try gnome-terminal with session env vars
171
+ Bun.spawn(["gnome-terminal", "--", "env", `MULTIAGENTS_SESSION=${sessionId}`, `MULTIAGENTS_ROLE=${slot.role ?? ""}`, `MULTIAGENTS_NAME=${slot.display_name ?? ""}`, cmd], { stdio: ["ignore", "ignore", "ignore"] });
172
+ }
173
+ console.log(` Launched ${slot.display_name ?? slot.agent_type} in new terminal`);
174
+ } catch (e) {
175
+ console.error(` Failed to launch ${slot.agent_type}: ${e instanceof Error ? e.message : String(e)}`);
176
+ }
177
+ }
178
+ }
179
+
180
+ async function pause(client: BrokerClient, sessionId?: string): Promise<void> {
181
+ const id = sessionId ?? readLocalSession()?.session_id;
182
+ if (!id) {
183
+ console.error("No session specified and no local session found.");
184
+ process.exit(1);
185
+ }
186
+
187
+ await client.updateSession({
188
+ id,
189
+ status: "paused",
190
+ pause_reason: "Paused via CLI",
191
+ paused_at: Date.now(),
192
+ });
193
+
194
+ // Pause all slots
195
+ try {
196
+ const slots = await client.listSlots(id);
197
+ for (const s of slots) {
198
+ await client.updateSlot({ id: s.id, paused: true, paused_at: Date.now() });
199
+ }
200
+ } catch { /* ok */ }
201
+
202
+ console.log(`Session "${id}" paused.`);
203
+ }
204
+
205
+ async function archive(client: BrokerClient, sessionId?: string): Promise<void> {
206
+ if (!sessionId) {
207
+ console.error("Usage: multiagents session archive <session-id>");
208
+ process.exit(1);
209
+ }
210
+ await client.updateSession({ id: sessionId, status: "archived" });
211
+ console.log(`Session "${sessionId}" archived.`);
212
+ }
213
+
214
+ async function deleteSession(client: BrokerClient, sessionId?: string): Promise<void> {
215
+ if (!sessionId) {
216
+ console.error("Usage: multiagents session delete <session-id>");
217
+ process.exit(1);
218
+ }
219
+
220
+ const answer = await prompt(`Delete session "${sessionId}" and all data? This cannot be undone. (yes/no)`);
221
+ if (answer !== "yes") {
222
+ console.log("Cancelled.");
223
+ return;
224
+ }
225
+
226
+ // Archive first (soft delete via broker — actual delete if broker supports it)
227
+ try {
228
+ await client.updateSession({ id: sessionId, status: "archived" });
229
+ } catch { /* ok */ }
230
+
231
+ // Clean local session file if it matches
232
+ const local = readLocalSession();
233
+ if (local?.session_id === sessionId) {
234
+ const sessionFilePath = path.resolve(process.cwd(), SESSION_FILE);
235
+ if (fs.existsSync(sessionFilePath)) fs.unlinkSync(sessionFilePath);
236
+ }
237
+
238
+ console.log(`Session "${sessionId}" deleted.`);
239
+ }
240
+
241
+ async function exportSession(client: BrokerClient, sessionId?: string): Promise<void> {
242
+ const id = sessionId ?? readLocalSession()?.session_id;
243
+ if (!id) {
244
+ console.error("Usage: multiagents session export <session-id>");
245
+ process.exit(1);
246
+ }
247
+
248
+ const session = await client.getSession(id);
249
+ const messages = await client.getMessageLog(id, { limit: 10000 });
250
+ const slots = await client.listSlots(id);
251
+
252
+ // Build slot lookup
253
+ const slotMap = new Map(slots.map((s) => [s.id, s]));
254
+
255
+ // Format as markdown
256
+ let md = `# Session: ${session.name}\n\n`;
257
+ md += `- **ID:** ${session.id}\n`;
258
+ md += `- **Status:** ${session.status}\n`;
259
+ md += `- **Directory:** ${session.project_dir}\n`;
260
+ md += `- **Created:** ${new Date(session.created_at).toISOString()}\n\n`;
261
+
262
+ md += `## Agents\n\n`;
263
+ for (const s of slots) {
264
+ md += `- **Slot ${s.id}:** ${s.display_name ?? "unnamed"} (${s.agent_type}) — ${s.role ?? "no role"}\n`;
265
+ }
266
+
267
+ md += `\n## Message Log\n\n`;
268
+ for (const m of messages) {
269
+ const fromSlot = m.from_slot_id !== null ? slotMap.get(m.from_slot_id) : null;
270
+ const fromName = fromSlot?.display_name ?? m.from_id;
271
+ const time = formatTime(m.sent_at);
272
+ md += `**[${time}] ${fromName}** (${m.msg_type}):\n${m.text}\n\n---\n\n`;
273
+ }
274
+
275
+ const filename = `session-${id}-export.md`;
276
+ await Bun.write(filename, md);
277
+ console.log(`Exported ${messages.length} messages to ${filename}`);
278
+ }
package/cli/setup.ts ADDED
@@ -0,0 +1,257 @@
1
+ // ============================================================================
2
+ // multiagents — Interactive Setup Wizard
3
+ // ============================================================================
4
+
5
+ import { DEFAULT_BROKER_PORT, BROKER_HOSTNAME, SESSION_DIR, SESSION_FILE } from "../shared/constants.ts";
6
+ import { BrokerClient } from "../shared/broker-client.ts";
7
+ import { expandHome, getGitRoot, slugify } from "../shared/utils.ts";
8
+ import type { AgentType, SessionFile } from "../shared/types.ts";
9
+ import * as readline from "node:readline";
10
+ import * as path from "node:path";
11
+ import * as fs from "node:fs";
12
+
13
+ const BROKER_PORT = parseInt(process.env.MULTIAGENTS_PORT ?? String(DEFAULT_BROKER_PORT), 10);
14
+ const BROKER_URL = `http://${BROKER_HOSTNAME}:${BROKER_PORT}`;
15
+
16
+ // --- Helpers ---
17
+
18
+ function prompt(question: string, defaultValue?: string): Promise<string> {
19
+ return new Promise((resolve) => {
20
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
21
+ const suffix = defaultValue ? ` [${defaultValue}]` : "";
22
+ rl.question(`${question}${suffix}: `, (answer) => {
23
+ rl.close();
24
+ resolve(answer.trim() || defaultValue || "");
25
+ });
26
+ });
27
+ }
28
+
29
+ function detectAgent(name: string): { available: boolean; version?: string } {
30
+ try {
31
+ const which = Bun.spawnSync(["which", name]);
32
+ if (which.exitCode !== 0) return { available: false };
33
+
34
+ const ver = Bun.spawnSync([name, "--version"]);
35
+ const version = new TextDecoder().decode(ver.stdout).trim().split("\n")[0];
36
+ return { available: true, version: version || undefined };
37
+ } catch {
38
+ return { available: false };
39
+ }
40
+ }
41
+
42
+ export async function setup(): Promise<void> {
43
+ // 1. Header banner
44
+ console.log(`
45
+ \x1b[1m\x1b[36m multiagents\x1b[0m
46
+ \x1b[90m Interactive Setup Wizard\x1b[0m
47
+ \x1b[90m ─────────────────────────────────\x1b[0m
48
+ `);
49
+
50
+ // 2. Detect installed agents
51
+ console.log("\x1b[1mDetecting installed agents...\x1b[0m\n");
52
+ const agents: { type: AgentType; name: string; cmd: string; info: ReturnType<typeof detectAgent> }[] = [
53
+ { type: "claude", name: "Claude Code", cmd: "claude", info: detectAgent("claude") },
54
+ { type: "codex", name: "Codex CLI", cmd: "codex", info: detectAgent("codex") },
55
+ { type: "gemini", name: "Gemini CLI", cmd: "gemini", info: detectAgent("gemini") },
56
+ ];
57
+
58
+ for (const a of agents) {
59
+ const icon = a.info.available ? "\x1b[32m✔\x1b[0m" : "\x1b[31m✗\x1b[0m";
60
+ const ver = a.info.version ? ` \x1b[90m(${a.info.version})\x1b[0m` : "";
61
+ console.log(` ${icon} ${a.name}${ver}`);
62
+ }
63
+
64
+ const available = agents.filter((a) => a.info.available);
65
+ if (available.length === 0) {
66
+ console.error("\n\x1b[31mNo supported agents found. Install at least one agent CLI first.\x1b[0m");
67
+ process.exit(1);
68
+ }
69
+
70
+ // 3. Prompt user to select agents
71
+ console.log("\n\x1b[1mSelect agents to orchestrate:\x1b[0m\n");
72
+ for (let i = 0; i < available.length; i++) {
73
+ console.log(` ${i + 1}. ${available[i].name} (${available[i].cmd})`);
74
+ }
75
+ console.log(` a. All available agents`);
76
+
77
+ const selection = await prompt("\nEnter numbers separated by commas, or 'a' for all", "a");
78
+ let selected: typeof available;
79
+ if (selection === "a" || selection === "A") {
80
+ selected = available;
81
+ } else {
82
+ const indices = selection.split(",").map((s) => parseInt(s.trim(), 10) - 1);
83
+ selected = indices
84
+ .filter((i) => i >= 0 && i < available.length)
85
+ .map((i) => available[i]);
86
+ }
87
+
88
+ if (selected.length === 0) {
89
+ console.error("\n\x1b[31mNo agents selected.\x1b[0m");
90
+ process.exit(1);
91
+ }
92
+ console.log(`\nSelected: ${selected.map((a) => a.name).join(", ")}`);
93
+
94
+ // 4. Prompt for working directory
95
+ const projectDir = await prompt("\nProject directory", process.cwd());
96
+ const resolvedDir = path.resolve(expandHome(projectDir));
97
+ if (!fs.existsSync(resolvedDir)) {
98
+ console.error(`\n\x1b[31mDirectory does not exist: ${resolvedDir}\x1b[0m`);
99
+ process.exit(1);
100
+ }
101
+
102
+ // 5. Prompt for session name
103
+ const dirName = path.basename(resolvedDir);
104
+ const sessionName = await prompt("Session name", dirName);
105
+ const sessionId = slugify(sessionName);
106
+
107
+ // 6. Configure each selected agent's MCP server
108
+ console.log("\n\x1b[1mConfiguring MCP servers...\x1b[0m\n");
109
+
110
+ const cliPath = path.resolve(import.meta.dir, "..");
111
+
112
+ for (const agent of selected) {
113
+ try {
114
+ switch (agent.type) {
115
+ case "claude":
116
+ await configureClaudeMcp(resolvedDir, cliPath);
117
+ break;
118
+ case "codex":
119
+ await configureCodexMcp(resolvedDir, cliPath);
120
+ break;
121
+ case "gemini":
122
+ await configureGeminiMcp(cliPath);
123
+ break;
124
+ }
125
+ console.log(` \x1b[32m✔\x1b[0m ${agent.name} MCP configured`);
126
+ } catch (e) {
127
+ console.error(` \x1b[31m✗\x1b[0m ${agent.name}: ${e instanceof Error ? e.message : String(e)}`);
128
+ }
129
+ }
130
+
131
+ // 7. Start broker
132
+ console.log("\n\x1b[1mStarting broker...\x1b[0m");
133
+ const client = new BrokerClient(BROKER_URL);
134
+ let brokerAlive = await client.isAlive();
135
+ if (!brokerAlive) {
136
+ const proc = Bun.spawn(["bun", path.resolve(cliPath, "broker.ts")], {
137
+ stdio: ["ignore", "ignore", "ignore"],
138
+ });
139
+ proc.unref();
140
+ for (let i = 0; i < 30; i++) {
141
+ if (await client.isAlive()) { brokerAlive = true; break; }
142
+ await Bun.sleep(200);
143
+ }
144
+ }
145
+ if (!brokerAlive) {
146
+ console.error(" \x1b[31m✗\x1b[0m Broker failed to start");
147
+ process.exit(1);
148
+ }
149
+ console.log(` \x1b[32m✔\x1b[0m Broker running on ${BROKER_URL}`);
150
+
151
+ // 8. Create session
152
+ console.log("\n\x1b[1mCreating session...\x1b[0m");
153
+ const gitRoot = await getGitRoot(resolvedDir);
154
+ try {
155
+ await client.createSession({
156
+ id: sessionId,
157
+ name: sessionName,
158
+ project_dir: resolvedDir,
159
+ git_root: gitRoot,
160
+ });
161
+ console.log(` \x1b[32m✔\x1b[0m Session "${sessionName}" (${sessionId}) created`);
162
+ } catch (e) {
163
+ const msg = e instanceof Error ? e.message : String(e);
164
+ if (msg.includes("409") || msg.includes("UNIQUE") || msg.includes("already")) {
165
+ console.log(` \x1b[33m!\x1b[0m Session "${sessionId}" already exists, reusing`);
166
+ } else {
167
+ throw e;
168
+ }
169
+ }
170
+
171
+ // 9. Write session.json
172
+ const sessionDir = path.join(resolvedDir, SESSION_DIR);
173
+ if (!fs.existsSync(sessionDir)) fs.mkdirSync(sessionDir, { recursive: true });
174
+
175
+ const sessionFile: SessionFile = {
176
+ session_id: sessionId,
177
+ created_at: new Date().toISOString(),
178
+ broker_port: BROKER_PORT,
179
+ };
180
+ await Bun.write(path.join(resolvedDir, SESSION_FILE), JSON.stringify(sessionFile, null, 2));
181
+ console.log(` \x1b[32m✔\x1b[0m Wrote ${SESSION_FILE}`);
182
+
183
+ // 10. Print next steps
184
+ console.log(`
185
+ \x1b[1m\x1b[32mSetup complete!\x1b[0m
186
+
187
+ \x1b[1mNext steps:\x1b[0m
188
+ 1. Open your agents in the project directory:
189
+ \x1b[90mcd ${resolvedDir}\x1b[0m
190
+ ${selected.map((a) => ` \x1b[90m${a.cmd}\x1b[0m`).join("\n")}
191
+
192
+ 2. Each agent will auto-connect to the session via MCP.
193
+
194
+ 3. Monitor with the dashboard:
195
+ \x1b[90mmultiagents dashboard\x1b[0m
196
+
197
+ 4. Or use the orchestrator for automated coordination:
198
+ \x1b[90mmultiagents orchestrator\x1b[0m
199
+ `);
200
+ }
201
+
202
+ // --- Agent MCP configuration ---
203
+
204
+ async function configureClaudeMcp(projectDir: string, cliPath: string): Promise<void> {
205
+ // Write project-level .mcp.json
206
+ const mcpPath = path.join(projectDir, ".mcp.json");
207
+ let config: Record<string, unknown> = {};
208
+ try {
209
+ const existing = await Bun.file(mcpPath).text();
210
+ config = JSON.parse(existing);
211
+ } catch { /* file doesn't exist */ }
212
+
213
+ const mcpServers = (config.mcpServers as Record<string, unknown>) ?? {};
214
+ mcpServers["multiagents"] = {
215
+ command: "bun",
216
+ args: [path.resolve(cliPath, "cli.ts"), "mcp-server", "--agent-type", "claude"],
217
+ };
218
+ config.mcpServers = mcpServers;
219
+ await Bun.write(mcpPath, JSON.stringify(config, null, 2));
220
+ }
221
+
222
+ async function configureCodexMcp(projectDir: string, cliPath: string): Promise<void> {
223
+ // Write .codex/config.toml
224
+ const codexDir = path.join(projectDir, ".codex");
225
+ if (!fs.existsSync(codexDir)) fs.mkdirSync(codexDir, { recursive: true });
226
+
227
+ const tomlPath = path.join(codexDir, "config.toml");
228
+ let existing = "";
229
+ try { existing = await Bun.file(tomlPath).text(); } catch { /* ok */ }
230
+
231
+ // Remove any existing multiagents section
232
+ existing = existing.replace(/\[mcp_servers\.multiagents\][\s\S]*?(?=\n\[|$)/, "").trim();
233
+
234
+ const entry = `\n\n[mcp_servers.multiagents]\ncommand = "bun"\nargs = [${JSON.stringify(path.resolve(cliPath, "cli.ts"))}, "mcp-server", "--agent-type", "codex"]\n`;
235
+ await Bun.write(tomlPath, existing + entry);
236
+ }
237
+
238
+ async function configureGeminiMcp(cliPath: string): Promise<void> {
239
+ // Write ~/.gemini/settings.json
240
+ const geminiDir = expandHome("~/.gemini");
241
+ if (!fs.existsSync(geminiDir)) fs.mkdirSync(geminiDir, { recursive: true });
242
+
243
+ const settingsPath = path.join(geminiDir, "settings.json");
244
+ let config: Record<string, unknown> = {};
245
+ try {
246
+ const existing = await Bun.file(settingsPath).text();
247
+ config = JSON.parse(existing);
248
+ } catch { /* ok */ }
249
+
250
+ const mcpServers = (config.mcpServers as Record<string, unknown>) ?? {};
251
+ mcpServers["multiagents"] = {
252
+ command: "bun",
253
+ args: [path.resolve(cliPath, "cli.ts"), "mcp-server", "--agent-type", "gemini"],
254
+ };
255
+ config.mcpServers = mcpServers;
256
+ await Bun.write(settingsPath, JSON.stringify(config, null, 2));
257
+ }
package/cli.ts ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env bun
2
+ // ============================================================================
3
+ // multiagents — CLI Entry Point
4
+ // ============================================================================
5
+ // Routes to the new modular CLI. Maintains backward compatibility with
6
+ // the original cli.ts commands (status, peers, send, kill-broker).
7
+ // ============================================================================
8
+
9
+ import { runCli } from "./cli/commands.ts";
10
+
11
+ const args = process.argv.slice(2);
12
+
13
+ // Backward compatibility: old cli.ts accepted these directly
14
+ // The new router handles them natively, so just pass through.
15
+ // "kill-broker" is explicitly aliased in commands.ts.
16
+
17
+ await runCli(args);
package/index.ts ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * multiagents
3
+ *
4
+ * Multi-agent orchestration platform for Claude Code, Codex CLI, and Gemini CLI.
5
+ *
6
+ * Entry points:
7
+ * - server.ts — MCP server (one per agent instance)
8
+ * - broker.ts — Shared broker daemon (one per machine)
9
+ * - orchestrator/orchestrator-server.ts — Orchestrator MCP (for Claude Desktop)
10
+ * - cli.ts — CLI tool (setup, dashboard, session mgmt)
11
+ *
12
+ * See README.md for setup and usage.
13
+ */
14
+
15
+ export type {
16
+ PeerId,
17
+ AgentType,
18
+ MessageType,
19
+ SessionStatus,
20
+ Peer,
21
+ Message,
22
+ Session,
23
+ Slot,
24
+ FileLock,
25
+ FileOwnership,
26
+ Guardrail,
27
+ GuardrailState,
28
+ BufferedMessage,
29
+ SessionFile,
30
+ AgentLaunchConfig,
31
+ TeamConfig,
32
+ } from "./shared/types.ts";
33
+
34
+ export { BrokerClient } from "./shared/broker-client.ts";
35
+
36
+ export {
37
+ DEFAULT_BROKER_PORT,
38
+ DEFAULT_GUARDRAILS,
39
+ POLL_INTERVALS,
40
+ SESSION_FILE,
41
+ } from "./shared/constants.ts";
package/noop-mcp.ts ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * No-op MCP server — responds to the JSON-RPC initialize handshake instantly,
4
+ * reports zero tools, then stays alive reading stdin until the client disconnects.
5
+ *
6
+ * Used to neutralize unwanted global MCP server entries in Codex config without
7
+ * causing 10s handshake timeouts (like /usr/bin/true does) or deserialization
8
+ * errors (like `echo disabled` does).
9
+ */
10
+
11
+ const decoder = new TextDecoder();
12
+ let buffer = "";
13
+
14
+ process.stdin.on("data", (chunk: Buffer) => {
15
+ buffer += decoder.decode(chunk, { stream: true });
16
+
17
+ // Process complete lines (JSON-RPC uses newline-delimited JSON)
18
+ const lines = buffer.split("\n");
19
+ buffer = lines.pop() ?? "";
20
+
21
+ for (const line of lines) {
22
+ const trimmed = line.trim();
23
+ if (!trimmed) continue;
24
+
25
+ try {
26
+ const msg = JSON.parse(trimmed);
27
+
28
+ if (msg.method === "initialize") {
29
+ const response = JSON.stringify({
30
+ jsonrpc: "2.0",
31
+ id: msg.id,
32
+ result: {
33
+ protocolVersion: "2024-11-05",
34
+ capabilities: {},
35
+ serverInfo: { name: "noop", version: "0.0.0" },
36
+ },
37
+ });
38
+ process.stdout.write(response + "\n");
39
+ } else if (msg.method === "notifications/initialized") {
40
+ // Client acknowledged — nothing to do
41
+ } else if (msg.method === "tools/list") {
42
+ const response = JSON.stringify({
43
+ jsonrpc: "2.0",
44
+ id: msg.id,
45
+ result: { tools: [] },
46
+ });
47
+ process.stdout.write(response + "\n");
48
+ } else if (msg.id !== undefined) {
49
+ // Unknown request — respond with empty result
50
+ const response = JSON.stringify({
51
+ jsonrpc: "2.0",
52
+ id: msg.id,
53
+ result: {},
54
+ });
55
+ process.stdout.write(response + "\n");
56
+ }
57
+ } catch {
58
+ // Not JSON — ignore
59
+ }
60
+ }
61
+ });
62
+
63
+ process.stdin.on("end", () => process.exit(0));