neohive 6.0.2 → 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/CHANGELOG.md +269 -77
- package/README.md +66 -63
- package/SECURITY.md +8 -6
- package/cli.js +377 -35
- package/conversation-templates/autonomous-feature.json +54 -4
- package/conversation-templates/code-review.json +41 -3
- package/conversation-templates/debug-squad.json +41 -3
- package/conversation-templates/feature-build.json +41 -3
- package/conversation-templates/research-write.json +41 -3
- package/dashboard.html +3954 -921
- package/dashboard.js +1192 -153
- package/design-system.css +708 -0
- package/design-system.html +264 -0
- package/lib/agents.js +20 -6
- package/lib/audit.js +417 -0
- package/lib/codex-neohive-toml.js +34 -0
- package/lib/compact.js +5 -2
- package/lib/config.js +4 -3
- package/lib/file-io.js +3 -3
- package/lib/github-sync.js +291 -0
- package/lib/hooks.js +173 -0
- package/lib/ide-activity.js +121 -0
- package/lib/resolve-server-data-dir.js +96 -0
- package/logo.svg +1 -0
- package/package.json +12 -3
- package/scripts/check-portable-paths.mjs +74 -0
- package/server.js +1986 -857
- package/templates/debate.json +24 -5
- package/templates/managed.json +48 -9
- package/templates/pair.json +22 -3
- package/templates/review.json +26 -5
- package/templates/team.json +38 -8
- package/tools/channels.js +116 -0
- package/tools/governance.js +471 -0
- package/tools/hooks.js +65 -0
- package/tools/knowledge.js +301 -0
- package/tools/messaging.js +321 -0
- package/tools/safety.js +144 -0
- package/tools/system.js +198 -0
- package/tools/tasks.js +446 -0
- package/tools/workflows.js +286 -0
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.
|
|
24
|
+
Neohive v6.1.0
|
|
13
25
|
The MCP collaboration layer for AI CLI tools.
|
|
14
26
|
|
|
15
27
|
Usage:
|
|
@@ -17,9 +29,15 @@ function printUsage() {
|
|
|
17
29
|
npx neohive init --claude Configure for Claude Code only
|
|
18
30
|
npx neohive init --gemini Configure for Gemini CLI only
|
|
19
31
|
npx neohive init --codex Configure for Codex CLI only
|
|
20
|
-
npx neohive init --
|
|
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
|
|
35
|
+
npx neohive init --all Configure for all supported CLIs
|
|
36
|
+
npx neohive mcp Start MCP stdio server (used internally by IDE configs)
|
|
21
37
|
npx neohive init --ollama Setup Ollama local LLM bridge
|
|
22
38
|
npx neohive init --template T Initialize with a team template
|
|
39
|
+
npx neohive serve Run MCP server in HTTP mode (port 4321)
|
|
40
|
+
npx neohive serve --port 8080 Custom port for HTTP server
|
|
23
41
|
npx neohive dashboard Launch web dashboard (http://localhost:3000)
|
|
24
42
|
npx neohive dashboard --lan Dashboard accessible on LAN
|
|
25
43
|
npx neohive status Show active agents and tasks
|
|
@@ -55,13 +73,18 @@ function detectCLIs() {
|
|
|
55
73
|
detected.push('codex');
|
|
56
74
|
}
|
|
57
75
|
|
|
76
|
+
// Cursor IDE: ~/.cursor/ (app support) exists
|
|
77
|
+
if (fs.existsSync(path.join(home, '.cursor'))) {
|
|
78
|
+
detected.push('cursor');
|
|
79
|
+
}
|
|
80
|
+
|
|
58
81
|
return detected;
|
|
59
82
|
}
|
|
60
83
|
|
|
61
84
|
// Detect Ollama installation
|
|
62
85
|
function detectOllama() {
|
|
63
86
|
try {
|
|
64
|
-
const version = execSync('ollama --version', { encoding: 'utf8', timeout:
|
|
87
|
+
const version = execSync('ollama --version', { encoding: 'utf8', timeout: CLI_CONFIG.OLLAMA_DETECT_TIMEOUT_MS }).trim();
|
|
65
88
|
return { installed: true, version };
|
|
66
89
|
} catch {
|
|
67
90
|
return { installed: false };
|
|
@@ -73,6 +96,16 @@ function dataDir(cwd) {
|
|
|
73
96
|
return path.join(cwd, '.neohive');
|
|
74
97
|
}
|
|
75
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
|
+
|
|
76
109
|
// Configure for Claude Code (.mcp.json in project root)
|
|
77
110
|
function setupClaude(serverPath, cwd) {
|
|
78
111
|
const mcpConfigPath = path.join(cwd, '.mcp.json');
|
|
@@ -90,9 +123,9 @@ function setupClaude(serverPath, cwd) {
|
|
|
90
123
|
}
|
|
91
124
|
|
|
92
125
|
mcpConfig.mcpServers['neohive'] = {
|
|
93
|
-
command:
|
|
126
|
+
command: mcpNodeCommand(),
|
|
94
127
|
args: [serverPath],
|
|
95
|
-
timeout:
|
|
128
|
+
timeout: CLI_CONFIG.MCP_TOOL_TIMEOUT_S,
|
|
96
129
|
};
|
|
97
130
|
|
|
98
131
|
fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + '\n');
|
|
@@ -122,14 +155,199 @@ function setupGemini(serverPath, cwd) {
|
|
|
122
155
|
}
|
|
123
156
|
|
|
124
157
|
settings.mcpServers['neohive'] = {
|
|
125
|
-
command:
|
|
158
|
+
command: mcpNodeCommand(),
|
|
126
159
|
args: [serverPath],
|
|
127
|
-
timeout:
|
|
160
|
+
timeout: CLI_CONFIG.MCP_TOOL_TIMEOUT_S,
|
|
128
161
|
trust: true,
|
|
129
162
|
};
|
|
130
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
|
+
|
|
131
170
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
132
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
|
+
`;
|
|
133
351
|
}
|
|
134
352
|
|
|
135
353
|
// Configure for Codex CLI (uses .codex/config.toml)
|
|
@@ -152,21 +370,56 @@ function setupCodex(serverPath, cwd) {
|
|
|
152
370
|
fs.copyFileSync(configPath, configPath + '.backup');
|
|
153
371
|
}
|
|
154
372
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
[mcp_servers.neohive]
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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);
|
|
166
384
|
|
|
167
385
|
console.log(' [ok] Codex CLI: .codex/config.toml updated');
|
|
168
386
|
}
|
|
169
387
|
|
|
388
|
+
// Configure for Cursor IDE — absolute NEOHIVE_DATA_DIR so Node never sees unexpanded ${workspaceFolder}.
|
|
389
|
+
// If the IDE omits env on spawn, server startup still resolves the hive via lib/resolve-server-data-dir.js
|
|
390
|
+
// (walks cwd ancestors for the same MCP files).
|
|
391
|
+
function setupCursor(serverPath, cwd) {
|
|
392
|
+
const cursorDir = path.join(cwd, '.cursor');
|
|
393
|
+
const mcpConfigPath = path.join(cursorDir, 'mcp.json');
|
|
394
|
+
const abDataDir = path.join(path.resolve(cwd), '.neohive').replace(/\\/g, '/');
|
|
395
|
+
|
|
396
|
+
if (!fs.existsSync(cursorDir)) {
|
|
397
|
+
fs.mkdirSync(cursorDir, { recursive: true });
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
let mcpConfig = { mcpServers: {} };
|
|
401
|
+
if (fs.existsSync(mcpConfigPath)) {
|
|
402
|
+
try {
|
|
403
|
+
mcpConfig = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf8'));
|
|
404
|
+
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
405
|
+
} catch {
|
|
406
|
+
const backup = mcpConfigPath + '.backup';
|
|
407
|
+
fs.copyFileSync(mcpConfigPath, backup);
|
|
408
|
+
console.log(' [warn] Existing .cursor/mcp.json was invalid — backed up to mcp.json.backup');
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
mcpConfig.mcpServers['neohive'] = {
|
|
413
|
+
command: mcpNodeCommand(),
|
|
414
|
+
args: [serverPath],
|
|
415
|
+
env: { NEOHIVE_DATA_DIR: abDataDir },
|
|
416
|
+
timeout: CLI_CONFIG.MCP_TOOL_TIMEOUT_S,
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + '\n');
|
|
420
|
+
console.log(' [ok] Cursor IDE: .cursor/mcp.json updated');
|
|
421
|
+
}
|
|
422
|
+
|
|
170
423
|
// Setup Ollama agent bridge script
|
|
171
424
|
function setupOllama(serverPath, cwd) {
|
|
172
425
|
const dir = dataDir(cwd);
|
|
@@ -272,10 +525,10 @@ async function processMessages() {
|
|
|
272
525
|
|
|
273
526
|
// Main loop
|
|
274
527
|
register();
|
|
275
|
-
const hb = setInterval(heartbeat,
|
|
528
|
+
const hb = setInterval(heartbeat, CLI_CONFIG.OLLAMA_HEARTBEAT_MS);
|
|
276
529
|
hb.unref();
|
|
277
530
|
console.log('[' + name + '] Listening for messages... (Ctrl+C to stop)');
|
|
278
|
-
setInterval(processMessages,
|
|
531
|
+
setInterval(processMessages, CLI_CONFIG.OLLAMA_POLL_MS);
|
|
279
532
|
|
|
280
533
|
// Cleanup on exit
|
|
281
534
|
process.on('SIGINT', function() { console.log('\\n[' + name + '] Shutting down.'); process.exit(0); });
|
|
@@ -314,8 +567,14 @@ function init() {
|
|
|
314
567
|
targets = ['gemini'];
|
|
315
568
|
} else if (flag === '--codex') {
|
|
316
569
|
targets = ['codex'];
|
|
570
|
+
} else if (flag === '--cursor') {
|
|
571
|
+
targets = ['cursor'];
|
|
572
|
+
} else if (flag === '--vscode') {
|
|
573
|
+
targets = ['vscode'];
|
|
574
|
+
} else if (flag === '--antigravity') {
|
|
575
|
+
targets = ['antigravity'];
|
|
317
576
|
} else if (flag === '--all') {
|
|
318
|
-
targets = ['claude', 'gemini', 'codex'];
|
|
577
|
+
targets = ['claude', 'gemini', 'codex', 'cursor', 'vscode', 'antigravity'];
|
|
319
578
|
} else if (flag === '--ollama') {
|
|
320
579
|
const ollama = detectOllama();
|
|
321
580
|
if (!ollama.installed) {
|
|
@@ -344,14 +603,17 @@ function init() {
|
|
|
344
603
|
|
|
345
604
|
for (const target of targets) {
|
|
346
605
|
switch (target) {
|
|
347
|
-
case 'claude':
|
|
348
|
-
case 'gemini':
|
|
349
|
-
case 'codex':
|
|
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;
|
|
350
612
|
}
|
|
351
613
|
}
|
|
352
614
|
|
|
353
615
|
// Add .neohive/ and MCP config files to .gitignore
|
|
354
|
-
const gitignoreEntries = ['.neohive/', '.mcp.json', '.codex/', '.gemini/'];
|
|
616
|
+
const gitignoreEntries = ['.neohive/', '.mcp.json', '.cursor/mcp.json', '.codex/', '.gemini/'];
|
|
355
617
|
if (fs.existsSync(gitignorePath)) {
|
|
356
618
|
let content = fs.readFileSync(gitignorePath, 'utf8');
|
|
357
619
|
const missing = gitignoreEntries.filter(e => !content.includes(e));
|
|
@@ -367,8 +629,16 @@ function init() {
|
|
|
367
629
|
console.log(' [ok] .gitignore created');
|
|
368
630
|
}
|
|
369
631
|
|
|
632
|
+
const configuredCursor = targets.includes('cursor');
|
|
370
633
|
console.log('');
|
|
371
|
-
console.log(
|
|
634
|
+
console.log(
|
|
635
|
+
configuredCursor
|
|
636
|
+
? ' Neohive is ready! Restart Cursor (or reload MCP tools) and restart any terminal CLIs you use.'
|
|
637
|
+
: ' Neohive is ready! Restart your CLI to pick up the MCP tools.'
|
|
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.');
|
|
372
642
|
console.log('');
|
|
373
643
|
|
|
374
644
|
// Show template if --template was provided
|
|
@@ -383,11 +653,15 @@ function init() {
|
|
|
383
653
|
if (templateFlag) {
|
|
384
654
|
showTemplate(templateFlag);
|
|
385
655
|
} else {
|
|
386
|
-
|
|
656
|
+
if (configuredCursor) {
|
|
657
|
+
console.log(' Cursor: use register + listen() from chat (neohive MCP). Terminal CLIs: one session per agent.');
|
|
658
|
+
} else {
|
|
659
|
+
console.log(' Open two terminals and start a conversation between agents.');
|
|
660
|
+
}
|
|
387
661
|
console.log(' Tip: Use "npx neohive init --template pair" for ready-made prompts.');
|
|
388
662
|
console.log('');
|
|
389
663
|
console.log(' \x1b[1m Monitor:\x1b[0m');
|
|
390
|
-
console.log(' npx neohive dashboard');
|
|
664
|
+
console.log(' npx neohive dashboard → http://localhost:3000 (set NEOHIVE_PORT to change)');
|
|
391
665
|
console.log(' npx neohive status');
|
|
392
666
|
console.log(' npx neohive doctor');
|
|
393
667
|
console.log('');
|
|
@@ -502,6 +776,17 @@ function showTemplate(templateName) {
|
|
|
502
776
|
}
|
|
503
777
|
}
|
|
504
778
|
|
|
779
|
+
function serve() {
|
|
780
|
+
// Parse --port flag
|
|
781
|
+
const portIdx = process.argv.indexOf('--port');
|
|
782
|
+
if (portIdx !== -1 && process.argv[portIdx + 1]) {
|
|
783
|
+
process.env.NEOHIVE_SERVER_PORT = process.argv[portIdx + 1];
|
|
784
|
+
}
|
|
785
|
+
// Signal server.js to use HTTP transport
|
|
786
|
+
process.env.NEOHIVE_TRANSPORT = 'http';
|
|
787
|
+
require('./server.js');
|
|
788
|
+
}
|
|
789
|
+
|
|
505
790
|
function dashboard() {
|
|
506
791
|
if (process.argv.includes('--lan')) {
|
|
507
792
|
process.env.NEOHIVE_LAN = 'true';
|
|
@@ -544,6 +829,14 @@ function cliMsg() {
|
|
|
544
829
|
process.exit(1);
|
|
545
830
|
}
|
|
546
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
|
+
}
|
|
547
840
|
const dir = resolveDataDirCli();
|
|
548
841
|
if (!fs.existsSync(dir)) {
|
|
549
842
|
console.error(' No .neohive/ directory found. Run "npx neohive init" first.');
|
|
@@ -559,12 +852,16 @@ function cliMsg() {
|
|
|
559
852
|
timestamp: new Date().toISOString(),
|
|
560
853
|
};
|
|
561
854
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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
|
+
}
|
|
568
865
|
}
|
|
569
866
|
|
|
570
867
|
function cliStatus() {
|
|
@@ -847,6 +1144,44 @@ function uninstall() {
|
|
|
847
1144
|
}
|
|
848
1145
|
}
|
|
849
1146
|
|
|
1147
|
+
// 7. Remove from Cursor IDE project config (.cursor/mcp.json in cwd)
|
|
1148
|
+
const cursorMcpPath = path.join(cwd, '.cursor', 'mcp.json');
|
|
1149
|
+
if (fs.existsSync(cursorMcpPath)) {
|
|
1150
|
+
try {
|
|
1151
|
+
const mcpConfig = JSON.parse(fs.readFileSync(cursorMcpPath, 'utf8'));
|
|
1152
|
+
if (mcpConfig.mcpServers && mcpConfig.mcpServers['neohive']) {
|
|
1153
|
+
delete mcpConfig.mcpServers['neohive'];
|
|
1154
|
+
fs.writeFileSync(cursorMcpPath, JSON.stringify(mcpConfig, null, 2) + '\n');
|
|
1155
|
+
removed.push('Cursor IDE (project): ' + cursorMcpPath);
|
|
1156
|
+
} else {
|
|
1157
|
+
notFound.push('Cursor IDE (project): no neohive entry in .cursor/mcp.json');
|
|
1158
|
+
}
|
|
1159
|
+
} catch (e) {
|
|
1160
|
+
console.log(' [warn] Could not parse ' + cursorMcpPath + ': ' + e.message);
|
|
1161
|
+
}
|
|
1162
|
+
} else {
|
|
1163
|
+
notFound.push('Cursor IDE (project): .cursor/mcp.json not found');
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// 8. Remove from Cursor IDE user config (~/.cursor/mcp.json)
|
|
1167
|
+
const cursorGlobalPath = path.join(home, '.cursor', 'mcp.json');
|
|
1168
|
+
if (fs.existsSync(cursorGlobalPath)) {
|
|
1169
|
+
try {
|
|
1170
|
+
const mcpConfig = JSON.parse(fs.readFileSync(cursorGlobalPath, 'utf8'));
|
|
1171
|
+
if (mcpConfig.mcpServers && mcpConfig.mcpServers['neohive']) {
|
|
1172
|
+
delete mcpConfig.mcpServers['neohive'];
|
|
1173
|
+
fs.writeFileSync(cursorGlobalPath, JSON.stringify(mcpConfig, null, 2) + '\n');
|
|
1174
|
+
removed.push('Cursor IDE (global): ' + cursorGlobalPath);
|
|
1175
|
+
} else {
|
|
1176
|
+
notFound.push('Cursor IDE (global): no neohive entry');
|
|
1177
|
+
}
|
|
1178
|
+
} catch (e) {
|
|
1179
|
+
console.log(' [warn] Could not parse ' + cursorGlobalPath + ': ' + e.message);
|
|
1180
|
+
}
|
|
1181
|
+
} else {
|
|
1182
|
+
notFound.push('Cursor IDE (global): ~/.cursor/mcp.json not found');
|
|
1183
|
+
}
|
|
1184
|
+
|
|
850
1185
|
// Print summary
|
|
851
1186
|
if (removed.length > 0) {
|
|
852
1187
|
console.log(' Removed neohive from:');
|
|
@@ -865,7 +1200,7 @@ function uninstall() {
|
|
|
865
1200
|
}
|
|
866
1201
|
}
|
|
867
1202
|
|
|
868
|
-
//
|
|
1203
|
+
// 9. Check for data directory
|
|
869
1204
|
const dataPath = path.join(cwd, '.neohive');
|
|
870
1205
|
if (fs.existsSync(dataPath)) {
|
|
871
1206
|
console.log('');
|
|
@@ -887,6 +1222,13 @@ switch (command) {
|
|
|
887
1222
|
case 'templates':
|
|
888
1223
|
listTemplates();
|
|
889
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;
|
|
1229
|
+
case 'serve':
|
|
1230
|
+
serve();
|
|
1231
|
+
break;
|
|
890
1232
|
case 'dashboard':
|
|
891
1233
|
dashboard();
|
|
892
1234
|
break;
|
|
@@ -2,11 +2,61 @@
|
|
|
2
2
|
"id": "autonomous-feature",
|
|
3
3
|
"name": "Autonomous Feature Build",
|
|
4
4
|
"description": "Full autonomous feature build: architecture, parallel backend+frontend, integration testing. Zero human intervention.",
|
|
5
|
+
"category": "development",
|
|
6
|
+
"conversation_mode": "direct",
|
|
5
7
|
"agents": [
|
|
6
|
-
{
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
{
|
|
9
|
+
"name": "Architect",
|
|
10
|
+
"role": "lead",
|
|
11
|
+
"display_name": "Software Architect",
|
|
12
|
+
"skills": ["design", "interfaces", "architecture", "planning"],
|
|
13
|
+
"responsibilities": [
|
|
14
|
+
"Design feature architecture with clear interfaces",
|
|
15
|
+
"Define contracts between backend and frontend",
|
|
16
|
+
"Create implementation plan"
|
|
17
|
+
],
|
|
18
|
+
"tools_focus": ["create_workflow", "advance_workflow", "kb_write", "share_file"],
|
|
19
|
+
"prompt": "You are a software architect. Design the feature architecture with clear interfaces. Call verify_and_advance() when done. Use get_work() to stay in the proactive loop."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "Backend",
|
|
23
|
+
"role": "backend",
|
|
24
|
+
"display_name": "Backend Developer",
|
|
25
|
+
"skills": ["api", "database", "server", "backend", "nodejs"],
|
|
26
|
+
"responsibilities": [
|
|
27
|
+
"Implement server-side logic, APIs, and data models",
|
|
28
|
+
"Write unit tests for backend code",
|
|
29
|
+
"Lock files before editing"
|
|
30
|
+
],
|
|
31
|
+
"tools_focus": ["lock_file", "unlock_file", "update_task", "advance_workflow", "share_file"],
|
|
32
|
+
"prompt": "You are a backend developer. Implement server-side logic, APIs, and data models. Write unit tests. Use lock_file() before editing. Call verify_and_advance() when done. Never wait for approval."
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "Frontend",
|
|
36
|
+
"role": "frontend",
|
|
37
|
+
"display_name": "Frontend Developer",
|
|
38
|
+
"skills": ["ui", "css", "components", "frontend", "html"],
|
|
39
|
+
"responsibilities": [
|
|
40
|
+
"Implement UI components, pages, and client-side logic",
|
|
41
|
+
"Write component tests",
|
|
42
|
+
"Lock files before editing"
|
|
43
|
+
],
|
|
44
|
+
"tools_focus": ["lock_file", "unlock_file", "update_task", "advance_workflow", "share_file"],
|
|
45
|
+
"prompt": "You are a frontend developer. Implement UI components, pages, and client-side logic. Write component tests. Use lock_file() before editing. Call verify_and_advance() when done. Never wait for approval."
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "Tester",
|
|
49
|
+
"role": "quality",
|
|
50
|
+
"display_name": "QA Engineer",
|
|
51
|
+
"skills": ["testing", "qa", "integration", "verification"],
|
|
52
|
+
"responsibilities": [
|
|
53
|
+
"Write and run integration tests",
|
|
54
|
+
"Verify all components work together",
|
|
55
|
+
"Submit review results via submit_review()"
|
|
56
|
+
],
|
|
57
|
+
"tools_focus": ["request_review", "submit_review", "advance_workflow", "get_reputation"],
|
|
58
|
+
"prompt": "You are a QA engineer. Write and run integration tests. Verify all components work together. Call verify_and_advance() when done."
|
|
59
|
+
}
|
|
10
60
|
],
|
|
11
61
|
"workflow": {
|
|
12
62
|
"name": "Autonomous Feature",
|