neohive 6.0.3 → 6.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.js CHANGED
@@ -4,12 +4,24 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const os = require('os');
6
6
  const { execSync } = require('child_process');
7
+ const { upsertNeohiveMcpInToml } = require('./lib/codex-neohive-toml');
8
+
9
+ // ─────────────────────────────────────────────────────────────────────────────
10
+ // CLI_CONFIG — centralized constants for the Neohive CLI
11
+ // ─────────────────────────────────────────────────────────────────────────────
12
+ const CLI_CONFIG = {
13
+ MCP_TOOL_TIMEOUT_S: 300, // MCP tool timeout written to IDE config files (seconds)
14
+ OLLAMA_DETECT_TIMEOUT_MS: 5000, // timeout for 'ollama --version' probe
15
+ OLLAMA_HEARTBEAT_MS: 10000, // how often Ollama agent writes heartbeat
16
+ OLLAMA_POLL_MS: 2000, // how often Ollama agent polls for messages
17
+ MSG_MAX_CHARS: 10000, // max length of --msg text argument
18
+ };
7
19
 
8
20
  const command = process.argv[2];
9
21
 
10
22
  function printUsage() {
11
23
  console.log(`
12
- Neohive v6.0.0
24
+ Neohive v6.1.0
13
25
  The MCP collaboration layer for AI CLI tools.
14
26
 
15
27
  Usage:
@@ -18,7 +30,10 @@ function printUsage() {
18
30
  npx neohive init --gemini Configure for Gemini CLI only
19
31
  npx neohive init --codex Configure for Codex CLI only
20
32
  npx neohive init --cursor Configure for Cursor IDE only (.cursor/mcp.json)
33
+ npx neohive init --vscode Configure for VS Code GitHub Copilot
34
+ npx neohive init --antigravity Configure for Antigravity IDE
21
35
  npx neohive init --all Configure for all supported CLIs
36
+ npx neohive mcp Start MCP stdio server (used internally by IDE configs)
22
37
  npx neohive init --ollama Setup Ollama local LLM bridge
23
38
  npx neohive init --template T Initialize with a team template
24
39
  npx neohive serve Run MCP server in HTTP mode (port 4321)
@@ -69,7 +84,7 @@ function detectCLIs() {
69
84
  // Detect Ollama installation
70
85
  function detectOllama() {
71
86
  try {
72
- const version = execSync('ollama --version', { encoding: 'utf8', timeout: 5000 }).trim();
87
+ const version = execSync('ollama --version', { encoding: 'utf8', timeout: CLI_CONFIG.OLLAMA_DETECT_TIMEOUT_MS }).trim();
73
88
  return { installed: true, version };
74
89
  } catch {
75
90
  return { installed: false };
@@ -81,6 +96,16 @@ function dataDir(cwd) {
81
96
  return path.join(cwd, '.neohive');
82
97
  }
83
98
 
99
+ // Absolute Node binary for MCP configs — CLIs often spawn without Volta/nvm PATH, so plain "node" fails.
100
+ function mcpNodeCommand() {
101
+ return process.execPath;
102
+ }
103
+
104
+ // MCP stdio "command" for npx — do not use /usr/bin/env (not portable on Windows).
105
+ function mcpNpxCommand() {
106
+ return process.platform === 'win32' ? 'npx.cmd' : 'npx';
107
+ }
108
+
84
109
  // Configure for Claude Code (.mcp.json in project root)
85
110
  function setupClaude(serverPath, cwd) {
86
111
  const mcpConfigPath = path.join(cwd, '.mcp.json');
@@ -98,9 +123,9 @@ function setupClaude(serverPath, cwd) {
98
123
  }
99
124
 
100
125
  mcpConfig.mcpServers['neohive'] = {
101
- command: 'node',
126
+ command: mcpNodeCommand(),
102
127
  args: [serverPath],
103
- timeout: 300,
128
+ timeout: CLI_CONFIG.MCP_TOOL_TIMEOUT_S,
104
129
  };
105
130
 
106
131
  fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + '\n');
@@ -130,14 +155,199 @@ function setupGemini(serverPath, cwd) {
130
155
  }
131
156
 
132
157
  settings.mcpServers['neohive'] = {
133
- command: 'node',
158
+ command: mcpNodeCommand(),
134
159
  args: [serverPath],
135
- timeout: 300,
160
+ timeout: CLI_CONFIG.MCP_TOOL_TIMEOUT_S,
136
161
  trust: true,
137
162
  };
138
163
 
164
+ if (!settings.context) settings.context = {};
165
+ if (!settings.context.files) settings.context.files = [];
166
+ if (!settings.context.files.includes('GEMINI.md')) {
167
+ settings.context.files.push('GEMINI.md');
168
+ }
169
+
139
170
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
140
171
  console.log(' [ok] Gemini CLI: .gemini/settings.json updated');
172
+
173
+ // Write GEMINI.md agent rules if not already present
174
+ const geminiMdPath = path.join(cwd, 'GEMINI.md');
175
+ if (!fs.existsSync(geminiMdPath)) {
176
+ fs.writeFileSync(geminiMdPath, geminiMdTemplate());
177
+ console.log(' [ok] Gemini CLI: GEMINI.md created with agent rules');
178
+ } else {
179
+ console.log(' [skip] GEMINI.md already exists — not overwriting');
180
+ }
181
+ }
182
+
183
+ function geminiMdTemplate() {
184
+ return `# Neohive Agent — Gemini CLI
185
+
186
+ You are a Neohive team agent. Follow these rules exactly, every session, no exceptions.
187
+
188
+ ## First thing to do — always
189
+
190
+ 1. Call \`register\` with your assigned name (e.g. \`register(name="Gemini")\`)
191
+ 2. Call \`get_briefing\` to load project context and current work
192
+ 3. Call \`listen\` to wait for messages from the Coordinator
193
+
194
+ Do NOT explore the codebase, ask questions, or take initiative before completing these 3 steps.
195
+
196
+ ## Core rules
197
+
198
+ - **After every action** — call \`listen()\`. This is how you receive your next task.
199
+ - **Before starting a task** — call \`update_task(id, status="in_progress")\`
200
+ - **After finishing a task** — call \`update_task(id, status="done")\`, then report to Coordinator
201
+ - **Before editing a file** — call \`lock_file(path)\`. Call \`unlock_file(path)\` when done.
202
+ - **Check tasks first** — call \`list_tasks()\` before starting anything new. Never work on another agent's task.
203
+ - **Keep messages short** — 2–3 paragraphs max. Lead with what changed, then files, then decisions.
204
+
205
+ ## Workflow
206
+
207
+ \`\`\`
208
+ register → get_briefing → listen → [receive task] → update_task(in_progress)
209
+ → do work → update_task(done) → send_message(Coordinator, summary) → listen
210
+ \`\`\`
211
+
212
+ Repeat the last 5 steps for every task. Never exit the listen loop.
213
+
214
+ ## Available MCP tools
215
+
216
+ **Messaging:** \`register\`, \`send_message\`, \`broadcast\`, \`listen\`, \`check_messages\`, \`get_history\`, \`handoff\`
217
+ **Tasks:** \`create_task\`, \`update_task\`, \`list_tasks\`
218
+ **Workflows:** \`create_workflow\`, \`advance_workflow\`, \`workflow_status\`
219
+ **Workspaces:** \`workspace_write\`, \`workspace_read\`, \`workspace_list\`
220
+ **Branching:** \`fork_conversation\`, \`switch_branch\`, \`list_branches\`
221
+
222
+ ## What NOT to do
223
+
224
+ - Do not self-assign tasks
225
+ - Do not modify files without a task assigned to you
226
+ - Do not skip \`listen()\` after responding
227
+ - Do not send long messages — be concise
228
+ - Do not ask the Coordinator for permission before starting an assigned task — just do it
229
+ `;
230
+ }
231
+
232
+ // Configure for VS Code GitHub Copilot (.vscode/mcp.json + copilot instructions)
233
+ function setupVSCode(cwd) {
234
+ const vscodeDir = path.join(cwd, '.vscode');
235
+ const mcpPath = path.join(vscodeDir, 'mcp.json');
236
+ if (!fs.existsSync(vscodeDir)) fs.mkdirSync(vscodeDir, { recursive: true });
237
+
238
+ let config = { servers: {} };
239
+ if (fs.existsSync(mcpPath)) {
240
+ try {
241
+ config = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
242
+ if (!config.servers) config.servers = {};
243
+ } catch {
244
+ fs.copyFileSync(mcpPath, mcpPath + '.backup');
245
+ console.log(' [warn] Existing .vscode/mcp.json was invalid — backed up');
246
+ }
247
+ }
248
+
249
+ config.servers['neohive'] = {
250
+ type: 'stdio',
251
+ command: mcpNpxCommand(),
252
+ args: ['-y', 'neohive', 'mcp'],
253
+ env: {
254
+ NEOHIVE_DATA_DIR: '${workspaceFolder}/.neohive',
255
+ },
256
+ cwd: '${workspaceFolder}',
257
+ };
258
+
259
+ fs.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + '\n');
260
+ console.log(' [ok] VS Code: .vscode/mcp.json updated');
261
+
262
+ // Write copilot instructions
263
+ const githubDir = path.join(cwd, '.github');
264
+ const instructionsPath = path.join(githubDir, 'copilot-instructions.md');
265
+ if (!fs.existsSync(githubDir)) fs.mkdirSync(githubDir, { recursive: true });
266
+ if (!fs.existsSync(instructionsPath)) {
267
+ fs.writeFileSync(instructionsPath, neohiveAgentRules('Copilot'));
268
+ console.log(' [ok] VS Code: .github/copilot-instructions.md created');
269
+ }
270
+ }
271
+
272
+ // Configure for Antigravity (~/.gemini/antigravity/mcp_config.json + skill)
273
+ function setupAntigravity(cwd) {
274
+ const antigravityDir = path.join(os.homedir(), '.gemini', 'antigravity');
275
+ const mcpPath = path.join(antigravityDir, 'mcp_config.json');
276
+ if (!fs.existsSync(antigravityDir)) fs.mkdirSync(antigravityDir, { recursive: true });
277
+
278
+ let config = { mcpServers: {} };
279
+ if (fs.existsSync(mcpPath)) {
280
+ try {
281
+ // Strip JS-style comments before parsing (Antigravity writes JSONC)
282
+ const raw = fs.readFileSync(mcpPath, 'utf8').replace(/\/\/[^\n]*/g, '');
283
+ config = JSON.parse(raw);
284
+ if (!config.mcpServers) config.mcpServers = {};
285
+ } catch {
286
+ fs.copyFileSync(mcpPath, mcpPath + '.backup');
287
+ console.log(' [warn] Existing mcp_config.json was invalid — backed up');
288
+ }
289
+ }
290
+
291
+ const abDataDir = path.join(path.resolve(cwd), '.neohive').replace(/\\/g, '/');
292
+
293
+ config.mcpServers['neohive'] = {
294
+ command: 'npx',
295
+ args: ['-y', 'neohive', 'mcp'],
296
+ cwd: cwd,
297
+ env: { NEOHIVE_DATA_DIR: abDataDir },
298
+ };
299
+
300
+ fs.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + '\n');
301
+ console.log(' [ok] Antigravity: ~/.gemini/antigravity/mcp_config.json updated');
302
+
303
+ // Write skill
304
+ const skillDir = path.join(cwd, '.agent', 'skills', 'neohive');
305
+ if (!fs.existsSync(skillDir)) fs.mkdirSync(skillDir, { recursive: true });
306
+ const skillPath = path.join(skillDir, 'SKILL.md');
307
+ if (!fs.existsSync(skillPath)) {
308
+ fs.writeFileSync(skillPath, neohiveAgentRules('Gemini'));
309
+ console.log(' [ok] Antigravity: .agent/skills/neohive/SKILL.md created');
310
+ }
311
+ }
312
+
313
+ function neohiveAgentRules(defaultName) {
314
+ return `# Neohive Agent
315
+
316
+ You are a Neohive team agent. Follow these rules every session.
317
+
318
+ ## On session start — always do this first
319
+
320
+ 1. Call \`register\` with your assigned name (e.g. \`register(name="${defaultName}")\`)
321
+ 2. Call \`get_briefing\` to load project context and active work
322
+ 3. Call \`listen\` to wait for messages from the Coordinator
323
+
324
+ Do NOT explore the codebase or take initiative before completing these 3 steps.
325
+
326
+ ## Core rules
327
+
328
+ - **After every action** — call \`listen()\`. This is how you receive your next task.
329
+ - **Before starting a task** — call \`update_task(id, status="in_progress")\`
330
+ - **After finishing** — call \`update_task(id, status="done")\`, report to Coordinator
331
+ - **Before editing a file** — call \`lock_file(path)\`. Call \`unlock_file(path)\` when done.
332
+ - **Check tasks first** — call \`list_tasks()\` before starting anything. Never take another agent's task.
333
+ - **Keep messages short** — 2–3 paragraphs max. Lead with what changed, then files, then decisions.
334
+
335
+ ## Workflow loop
336
+
337
+ \`\`\`
338
+ register → get_briefing → listen → [receive task] → update_task(in_progress)
339
+ → do work → update_task(done) → send_message(Coordinator, summary) → listen
340
+ \`\`\`
341
+
342
+ Never exit the listen loop.
343
+
344
+ ## Available MCP tools (neohive server)
345
+
346
+ **Messaging:** \`register\`, \`send_message\`, \`broadcast\`, \`listen\`, \`check_messages\`, \`get_history\`
347
+ **Tasks:** \`create_task\`, \`update_task\`, \`list_tasks\`
348
+ **Workflows:** \`create_workflow\`, \`advance_workflow\`, \`workflow_status\`
349
+ **Workspaces:** \`workspace_write\`, \`workspace_read\`, \`workspace_list\`
350
+ `;
141
351
  }
142
352
 
143
353
  // Configure for Codex CLI (uses .codex/config.toml)
@@ -160,17 +370,17 @@ function setupCodex(serverPath, cwd) {
160
370
  fs.copyFileSync(configPath, configPath + '.backup');
161
371
  }
162
372
 
163
- // Only add if not already present
164
- if (!config.includes('[mcp_servers.neohive]')) {
165
- const tomlBlock = `
166
- [mcp_servers.neohive]
167
- command = "node"
168
- args = [${JSON.stringify(serverPath)}]
169
- timeout = 300
170
- `;
171
- config += tomlBlock;
172
- fs.writeFileSync(configPath, config);
173
- }
373
+ const abDataDir = path.join(path.resolve(cwd), '.neohive').replace(/\\/g, '/');
374
+ const envSection =
375
+ `[mcp_servers.neohive.env]\nNEOHIVE_DATA_DIR = ${JSON.stringify(abDataDir)}\n`;
376
+ const hadNeohive = config.includes('[mcp_servers.neohive]');
377
+ config = upsertNeohiveMcpInToml(config, {
378
+ command: mcpNodeCommand(),
379
+ serverPath,
380
+ timeout: CLI_CONFIG.MCP_TOOL_TIMEOUT_S,
381
+ envSection: hadNeohive ? undefined : envSection,
382
+ });
383
+ fs.writeFileSync(configPath, config);
174
384
 
175
385
  console.log(' [ok] Codex CLI: .codex/config.toml updated');
176
386
  }
@@ -200,10 +410,10 @@ function setupCursor(serverPath, cwd) {
200
410
  }
201
411
 
202
412
  mcpConfig.mcpServers['neohive'] = {
203
- command: 'node',
413
+ command: mcpNodeCommand(),
204
414
  args: [serverPath],
205
415
  env: { NEOHIVE_DATA_DIR: abDataDir },
206
- timeout: 300,
416
+ timeout: CLI_CONFIG.MCP_TOOL_TIMEOUT_S,
207
417
  };
208
418
 
209
419
  fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + '\n');
@@ -315,10 +525,10 @@ async function processMessages() {
315
525
 
316
526
  // Main loop
317
527
  register();
318
- const hb = setInterval(heartbeat, 10000);
528
+ const hb = setInterval(heartbeat, CLI_CONFIG.OLLAMA_HEARTBEAT_MS);
319
529
  hb.unref();
320
530
  console.log('[' + name + '] Listening for messages... (Ctrl+C to stop)');
321
- setInterval(processMessages, 2000);
531
+ setInterval(processMessages, CLI_CONFIG.OLLAMA_POLL_MS);
322
532
 
323
533
  // Cleanup on exit
324
534
  process.on('SIGINT', function() { console.log('\\n[' + name + '] Shutting down.'); process.exit(0); });
@@ -359,8 +569,12 @@ function init() {
359
569
  targets = ['codex'];
360
570
  } else if (flag === '--cursor') {
361
571
  targets = ['cursor'];
572
+ } else if (flag === '--vscode') {
573
+ targets = ['vscode'];
574
+ } else if (flag === '--antigravity') {
575
+ targets = ['antigravity'];
362
576
  } else if (flag === '--all') {
363
- targets = ['claude', 'gemini', 'codex', 'cursor'];
577
+ targets = ['claude', 'gemini', 'codex', 'cursor', 'vscode', 'antigravity'];
364
578
  } else if (flag === '--ollama') {
365
579
  const ollama = detectOllama();
366
580
  if (!ollama.installed) {
@@ -389,10 +603,12 @@ function init() {
389
603
 
390
604
  for (const target of targets) {
391
605
  switch (target) {
392
- case 'claude': setupClaude(serverPath, cwd); break;
393
- case 'gemini': setupGemini(serverPath, cwd); break;
394
- case 'codex': setupCodex(serverPath, cwd); break;
395
- case 'cursor': setupCursor(serverPath, cwd); break;
606
+ case 'claude': setupClaude(serverPath, cwd); break;
607
+ case 'gemini': setupGemini(serverPath, cwd); break;
608
+ case 'codex': setupCodex(serverPath, cwd); break;
609
+ case 'cursor': setupCursor(serverPath, cwd); break;
610
+ case 'vscode': setupVSCode(cwd); break;
611
+ case 'antigravity': setupAntigravity(cwd); break;
396
612
  }
397
613
  }
398
614
 
@@ -420,6 +636,9 @@ function init() {
420
636
  ? ' Neohive is ready! Restart Cursor (or reload MCP tools) and restart any terminal CLIs you use.'
421
637
  : ' Neohive is ready! Restart your CLI to pick up the MCP tools.'
422
638
  );
639
+ console.log(' MCP server command is your current Node binary (works when the IDE has no Volta/nvm in PATH):');
640
+ console.log(' ' + mcpNodeCommand());
641
+ console.log(' Re-run `npx neohive init` after switching machines or Node versions.');
423
642
  console.log('');
424
643
 
425
644
  // Show template if --template was provided
@@ -442,7 +661,7 @@ function init() {
442
661
  console.log(' Tip: Use "npx neohive init --template pair" for ready-made prompts.');
443
662
  console.log('');
444
663
  console.log(' \x1b[1m Monitor:\x1b[0m');
445
- console.log(' npx neohive dashboard');
664
+ console.log(' npx neohive dashboard → http://localhost:3000 (set NEOHIVE_PORT to change)');
446
665
  console.log(' npx neohive status');
447
666
  console.log(' npx neohive doctor');
448
667
  console.log('');
@@ -610,6 +829,14 @@ function cliMsg() {
610
829
  process.exit(1);
611
830
  }
612
831
  const text = textParts.join(' ');
832
+ if (text.length > CLI_CONFIG.MSG_MAX_CHARS) {
833
+ console.error(` Message text exceeds maximum length of ${CLI_CONFIG.MSG_MAX_CHARS} characters.`);
834
+ process.exit(1);
835
+ }
836
+ if (!text.trim().length) {
837
+ console.error(' Message text cannot be empty or whitespace only.');
838
+ process.exit(1);
839
+ }
613
840
  const dir = resolveDataDirCli();
614
841
  if (!fs.existsSync(dir)) {
615
842
  console.error(' No .neohive/ directory found. Run "npx neohive init" first.');
@@ -625,12 +852,16 @@ function cliMsg() {
625
852
  timestamp: new Date().toISOString(),
626
853
  };
627
854
 
628
- const messagesFile = path.join(dir, 'messages.jsonl');
629
- const historyFile = path.join(dir, 'history.jsonl');
630
- fs.appendFileSync(messagesFile, JSON.stringify(msg) + '\n');
631
- fs.appendFileSync(historyFile, JSON.stringify(msg) + '\n');
632
-
633
- console.log(' Message sent to ' + recipient + ': ' + text);
855
+ try {
856
+ const messagesFile = path.join(dir, 'messages.jsonl');
857
+ const historyFile = path.join(dir, 'history.jsonl');
858
+ fs.appendFileSync(messagesFile, JSON.stringify(msg) + '\n');
859
+ fs.appendFileSync(historyFile, JSON.stringify(msg) + '\n');
860
+ console.log(' Message sent to ' + recipient + ': ' + text);
861
+ } catch (e) {
862
+ console.error(' Failed to send message: ' + e.message);
863
+ process.exit(1);
864
+ }
634
865
  }
635
866
 
636
867
  function cliStatus() {
@@ -991,6 +1222,10 @@ switch (command) {
991
1222
  case 'templates':
992
1223
  listTemplates();
993
1224
  break;
1225
+ case 'mcp':
1226
+ // Start stdio MCP server — used as the command in all MCP configs: npx neohive mcp
1227
+ require('./server.js');
1228
+ break;
994
1229
  case 'serve':
995
1230
  serve();
996
1231
  break;