@sesamespace/hivemind 0.5.4 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/.github/workflows/memory-release.yml +89 -0
  2. package/config/default.toml +12 -0
  3. package/dist/{chunk-YHRGEWAZ.js → chunk-4U4GKI3X.js} +121 -34
  4. package/dist/chunk-4U4GKI3X.js.map +1 -0
  5. package/{packages/cli/src/commands/start.ts → dist/chunk-62S2NGMN.js} +39 -38
  6. package/dist/chunk-62S2NGMN.js.map +1 -0
  7. package/dist/{chunk-CGSXJVSS.js → chunk-7RTJSPMX.js} +2 -2
  8. package/dist/chunk-IJRAVHQC.js +327 -0
  9. package/dist/chunk-IJRAVHQC.js.map +1 -0
  10. package/dist/{chunk-MHDLSAJ3.js → chunk-JXLLCKMG.js} +102 -5
  11. package/dist/chunk-JXLLCKMG.js.map +1 -0
  12. package/dist/{chunk-SJI2KAIN.js → chunk-LJHJGDKY.js} +8 -2
  13. package/dist/chunk-LJHJGDKY.js.map +1 -0
  14. package/dist/{chunk-LNV373IF.js → chunk-LSOEUOTO.js} +2 -2
  15. package/dist/{chunk-S3RVZBPZ.js → chunk-PFN3BITA.js} +322 -2
  16. package/dist/chunk-PFN3BITA.js.map +1 -0
  17. package/dist/commands/fleet.js +3 -3
  18. package/dist/commands/init.js +1 -1
  19. package/dist/commands/service.js +1 -1
  20. package/dist/commands/start.js +3 -3
  21. package/dist/commands/upgrade.js +1 -1
  22. package/dist/commands/watchdog.js +10 -0
  23. package/dist/commands/watchdog.js.map +1 -0
  24. package/dist/index.js +6 -2
  25. package/dist/main.js +22 -12
  26. package/dist/main.js.map +1 -1
  27. package/dist/start.js +1 -1
  28. package/package.json +4 -3
  29. package/PLANNING.md +0 -383
  30. package/TASKS.md +0 -60
  31. package/dist/chunk-MHDLSAJ3.js.map +0 -1
  32. package/dist/chunk-PPQGQHXJ.js +0 -151
  33. package/dist/chunk-PPQGQHXJ.js.map +0 -1
  34. package/dist/chunk-S3RVZBPZ.js.map +0 -1
  35. package/dist/chunk-SJI2KAIN.js.map +0 -1
  36. package/dist/chunk-YHRGEWAZ.js.map +0 -1
  37. package/install.sh +0 -120
  38. package/npm-package.json +0 -26
  39. package/packages/cli/package.json +0 -23
  40. package/packages/cli/src/commands/fleet.ts +0 -206
  41. package/packages/cli/src/commands/init.ts +0 -252
  42. package/packages/cli/src/commands/service.ts +0 -159
  43. package/packages/cli/src/commands/upgrade.ts +0 -158
  44. package/packages/cli/src/main.ts +0 -64
  45. package/packages/cli/tsconfig.json +0 -8
  46. package/packages/memory/Cargo.lock +0 -6480
  47. package/packages/memory/Cargo.toml +0 -21
  48. package/packages/memory/src/context.rs +0 -179
  49. package/packages/memory/src/embeddings.rs +0 -51
  50. package/packages/memory/src/main.rs +0 -626
  51. package/packages/memory/src/promotion.rs +0 -637
  52. package/packages/memory/src/scoring.rs +0 -131
  53. package/packages/memory/src/store.rs +0 -460
  54. package/packages/memory/src/tasks.rs +0 -321
  55. package/packages/runtime/package.json +0 -24
  56. package/packages/runtime/src/__tests__/fleet-integration.test.ts +0 -235
  57. package/packages/runtime/src/__tests__/fleet.test.ts +0 -207
  58. package/packages/runtime/src/__tests__/integration.test.ts +0 -434
  59. package/packages/runtime/src/agent.ts +0 -255
  60. package/packages/runtime/src/config.ts +0 -130
  61. package/packages/runtime/src/context.ts +0 -192
  62. package/packages/runtime/src/fleet/fleet-manager.ts +0 -399
  63. package/packages/runtime/src/fleet/memory-sync.ts +0 -362
  64. package/packages/runtime/src/fleet/primary-client.ts +0 -285
  65. package/packages/runtime/src/fleet/worker-protocol.ts +0 -158
  66. package/packages/runtime/src/fleet/worker-server.ts +0 -246
  67. package/packages/runtime/src/index.ts +0 -57
  68. package/packages/runtime/src/llm-client.ts +0 -65
  69. package/packages/runtime/src/memory-client.ts +0 -309
  70. package/packages/runtime/src/pipeline.ts +0 -187
  71. package/packages/runtime/src/prompt.ts +0 -173
  72. package/packages/runtime/src/sesame.ts +0 -226
  73. package/packages/runtime/src/start.ts +0 -20
  74. package/packages/runtime/src/task-engine.ts +0 -113
  75. package/packages/runtime/src/worker.ts +0 -339
  76. package/packages/runtime/tsconfig.json +0 -8
  77. package/pnpm-workspace.yaml +0 -2
  78. package/run-aidan.sh +0 -23
  79. package/scripts/bootstrap.sh +0 -196
  80. package/scripts/build-npm.sh +0 -92
  81. package/scripts/com.hivemind.agent.plist +0 -44
  82. package/scripts/com.hivemind.memory.plist +0 -31
  83. package/tsconfig.json +0 -22
  84. package/tsup.config.ts +0 -27
  85. /package/dist/{chunk-CGSXJVSS.js.map → chunk-7RTJSPMX.js.map} +0 -0
  86. /package/dist/{chunk-LNV373IF.js.map → chunk-LSOEUOTO.js.map} +0 -0
@@ -1,206 +0,0 @@
1
- /**
2
- * CLI commands for fleet management.
3
- *
4
- * Usage:
5
- * hivemind fleet status — Show fleet dashboard
6
- * hivemind fleet add-worker <url> — Register a worker by URL
7
- * hivemind fleet remove-worker <worker-id> — Remove a worker from the fleet
8
- * hivemind fleet assign <worker-id> <context> — Assign a context to a worker
9
- * hivemind fleet migrate <context> <worker-id> — Migrate a context to another worker
10
- * hivemind fleet discover <url1> [url2] ... — Probe URLs for workers
11
- */
12
-
13
- import { FleetManager } from "@hivemind/runtime";
14
- import type { FleetDashboard, WorkerSummary } from "@hivemind/runtime";
15
-
16
- function formatUptime(seconds: number | null): string {
17
- if (seconds == null) return "—";
18
- if (seconds < 60) return `${seconds}s`;
19
- if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
20
- const h = Math.floor(seconds / 3600);
21
- const m = Math.floor((seconds % 3600) / 60);
22
- return `${h}h ${m}m`;
23
- }
24
-
25
- function formatHealth(status: string): string {
26
- switch (status) {
27
- case "healthy": return "OK";
28
- case "degraded": return "DEGRADED";
29
- case "unreachable": return "DOWN";
30
- default: return status.toUpperCase();
31
- }
32
- }
33
-
34
- function printDashboard(dashboard: FleetDashboard): void {
35
- console.log("\n=== Hivemind Fleet ===\n");
36
- console.log(`Workers: ${dashboard.total_workers} (${dashboard.healthy} healthy, ${dashboard.degraded} degraded, ${dashboard.unreachable} unreachable)`);
37
- console.log(`Contexts assigned: ${dashboard.total_contexts}`);
38
-
39
- if (dashboard.unassigned_contexts.length > 0) {
40
- console.log(`Unassigned contexts: ${dashboard.unassigned_contexts.join(", ")}`);
41
- }
42
-
43
- console.log("");
44
-
45
- if (dashboard.workers.length === 0) {
46
- console.log("No workers registered. Use 'fleet add-worker <url>' to add one.");
47
- return;
48
- }
49
-
50
- // Table header
51
- const cols = { id: 12, url: 30, health: 8, activity: 10, contexts: 25, task: 20, uptime: 8 };
52
- const header = [
53
- "ID".padEnd(cols.id),
54
- "URL".padEnd(cols.url),
55
- "HEALTH".padEnd(cols.health),
56
- "ACTIVITY".padEnd(cols.activity),
57
- "CONTEXTS".padEnd(cols.contexts),
58
- "TASK".padEnd(cols.task),
59
- "UPTIME".padEnd(cols.uptime),
60
- ].join(" ");
61
- console.log(header);
62
- console.log("-".repeat(header.length));
63
-
64
- for (const w of dashboard.workers) {
65
- const row = [
66
- w.id.padEnd(cols.id),
67
- w.url.slice(0, cols.url).padEnd(cols.url),
68
- formatHealth(w.health).padEnd(cols.health),
69
- w.activity.padEnd(cols.activity),
70
- (w.contexts.join(", ") || "—").slice(0, cols.contexts).padEnd(cols.contexts),
71
- (w.current_task ?? "—").slice(0, cols.task).padEnd(cols.task),
72
- formatUptime(w.uptime_seconds).padEnd(cols.uptime),
73
- ].join(" ");
74
- console.log(row);
75
- }
76
-
77
- console.log(`\nGenerated: ${dashboard.generated_at}`);
78
- }
79
-
80
- export async function runFleetCommand(args: string[]): Promise<void> {
81
- const subcommand = args[0];
82
-
83
- if (!subcommand) {
84
- printUsage();
85
- process.exit(1);
86
- }
87
-
88
- const fleet = new FleetManager();
89
-
90
- switch (subcommand) {
91
- case "status": {
92
- const dashboard = await fleet.getDashboard();
93
- printDashboard(dashboard);
94
- break;
95
- }
96
-
97
- case "add-worker": {
98
- const url = args[1];
99
- if (!url) {
100
- console.error("Usage: hivemind fleet add-worker <url>");
101
- process.exit(1);
102
- }
103
- try {
104
- const worker = await fleet.addWorker(url);
105
- console.log(`Worker registered: ${worker.id}`);
106
- console.log(` URL: ${worker.url}`);
107
- console.log(` Max contexts: ${worker.capabilities.max_contexts}`);
108
- console.log(` Ollama: ${worker.capabilities.has_ollama ? "yes" : "no"}`);
109
- console.log(` Memory daemon: ${worker.capabilities.has_memory_daemon ? "yes" : "no"}`);
110
- } catch (err) {
111
- const msg = err instanceof Error ? err.message : String(err);
112
- console.error(`Failed to add worker: ${msg}`);
113
- process.exit(1);
114
- }
115
- break;
116
- }
117
-
118
- case "remove-worker": {
119
- const workerId = args[1];
120
- if (!workerId) {
121
- console.error("Usage: hivemind fleet remove-worker <worker-id>");
122
- process.exit(1);
123
- }
124
- const removed = await fleet.removeWorker(workerId);
125
- if (removed) {
126
- console.log(`Worker ${workerId} removed.`);
127
- } else {
128
- console.error(`Worker ${workerId} not found.`);
129
- process.exit(1);
130
- }
131
- break;
132
- }
133
-
134
- case "assign": {
135
- const workerId = args[1];
136
- const context = args[2];
137
- if (!workerId || !context) {
138
- console.error("Usage: hivemind fleet assign <worker-id> <context>");
139
- process.exit(1);
140
- }
141
- const result = await fleet.assignContext(workerId, context);
142
- if (result.accepted) {
143
- console.log(`Context '${context}' assigned to ${workerId}.`);
144
- } else {
145
- console.error(`Assignment rejected: ${result.reason}`);
146
- process.exit(1);
147
- }
148
- break;
149
- }
150
-
151
- case "migrate": {
152
- const context = args[1];
153
- const toWorker = args[2];
154
- if (!context || !toWorker) {
155
- console.error("Usage: hivemind fleet migrate <context> <worker-id>");
156
- process.exit(1);
157
- }
158
- const result = await fleet.migrateContext(context, toWorker);
159
- if (result.success) {
160
- console.log(`Context '${context}' migrated: ${result.from_worker} -> ${result.to_worker}`);
161
- } else {
162
- console.error(`Migration failed: ${result.reason}`);
163
- process.exit(1);
164
- }
165
- break;
166
- }
167
-
168
- case "discover": {
169
- const urls = args.slice(1);
170
- if (urls.length === 0) {
171
- console.error("Usage: hivemind fleet discover <url1> [url2] ...");
172
- process.exit(1);
173
- }
174
- console.log(`Probing ${urls.length} URL(s)...`);
175
- const found = await fleet.discoverWorkers(urls);
176
- if (found.length === 0) {
177
- console.log("No workers found.");
178
- } else {
179
- console.log(`Found ${found.length} worker(s):`);
180
- for (const url of found) {
181
- console.log(` ${url}`);
182
- }
183
- }
184
- break;
185
- }
186
-
187
- default:
188
- console.error(`Unknown fleet subcommand: ${subcommand}`);
189
- printUsage();
190
- process.exit(1);
191
- }
192
- }
193
-
194
- function printUsage(): void {
195
- console.log(`
196
- Usage: hivemind fleet <command> [args]
197
-
198
- Commands:
199
- status Show fleet dashboard
200
- add-worker <url> Register a worker by URL
201
- remove-worker <worker-id> Remove a worker from the fleet
202
- assign <worker-id> <context> Assign a context to a worker
203
- migrate <context> <worker-id> Migrate a context to another worker
204
- discover <url1> [url2] ... Probe URLs for workers
205
- `.trim());
206
- }
@@ -1,252 +0,0 @@
1
- import { resolve, dirname } from "path";
2
- import { existsSync, writeFileSync, mkdirSync, readFileSync, copyFileSync, realpathSync } from "fs";
3
- import { createInterface } from "readline";
4
- import { fileURLToPath } from "url";
5
- import { SesameClient } from "@sesamespace/sdk";
6
- import { homedir } from "os";
7
-
8
- const HIVEMIND_DIR = resolve(process.env.HIVEMIND_HOME || resolve(homedir(), "hivemind"));
9
- const CONFIG_DIR = resolve(HIVEMIND_DIR, "config");
10
- const WORKSPACE_DIR = resolve(HIVEMIND_DIR, "workspace");
11
- const ENV_FILE = resolve(HIVEMIND_DIR, ".env");
12
- const LOCAL_TOML = resolve(CONFIG_DIR, "local.toml");
13
-
14
- const VAULT_CONFIG_NAME = "hivemind-config";
15
-
16
- interface ProvisioningConfig {
17
- agentName: string;
18
- agentHandle: string;
19
- agentId: string;
20
- personality?: string;
21
- llmApiKey?: string;
22
- llmBaseUrl?: string;
23
- llmModel?: string;
24
- fleetRole?: string;
25
- channels: Array<{ id: string; name: string | null; kind: string }>;
26
- }
27
-
28
- async function prompt(question: string): Promise<string> {
29
- const rl = createInterface({ input: process.stdin, output: process.stdout });
30
- return new Promise((resolve) => {
31
- rl.question(question, (answer) => {
32
- rl.close();
33
- resolve(answer.trim());
34
- });
35
- });
36
- }
37
-
38
- export async function runInitCommand(args: string[]): Promise<void> {
39
- const nonInteractive = args.includes("--yes") || args.includes("-y") || args.includes("--non-interactive");
40
- const filteredArgs = args.filter((a) => !["--yes", "-y", "--non-interactive", "--help", "-h"].includes(a));
41
- let sesameApiKey = filteredArgs[0];
42
-
43
- if (args.includes("--help") || args.includes("-h")) {
44
- printHelp();
45
- return;
46
- }
47
-
48
- console.log(`
49
- ╦ ╦╦╦ ╦╔═╗╔╦╗╦╔╗╔╔╦╗
50
- ╠═╣║╚╗╔╝║╣ ║║║║║║║ ║║
51
- ╩ ╩╩ ╚╝ ╚═╝╩ ╩╩╝╚╝═╩╝
52
- Agent Initialization
53
- `);
54
-
55
- // --- Step 1: Get Sesame API key ---
56
- if (!sesameApiKey) {
57
- sesameApiKey = await prompt(" Sesame API key: ");
58
- }
59
- if (!sesameApiKey) {
60
- console.error("Error: Sesame API key is required");
61
- process.exit(1);
62
- }
63
-
64
- // --- Step 2: Connect to Sesame and fetch manifest ---
65
- console.log("\n→ Connecting to Sesame...");
66
- const sdk = new SesameClient({
67
- apiUrl: "https://api.sesame.space",
68
- wsUrl: "wss://ws.sesame.space",
69
- apiKey: sesameApiKey,
70
- });
71
-
72
- let config: ProvisioningConfig;
73
- try {
74
- const manifest = await sdk.getManifest();
75
- console.log(` ✓ Authenticated as ${manifest.agent.handle} (${manifest.agent.id})`);
76
- console.log(` ✓ Workspace: ${manifest.workspace.name}`);
77
- console.log(` ✓ Channels: ${manifest.channels.length}`);
78
- for (const ch of manifest.channels) {
79
- console.log(` - ${ch.name || ch.id} (${ch.kind})`);
80
- }
81
-
82
- config = {
83
- agentName: manifest.agent.handle,
84
- agentHandle: manifest.agent.handle,
85
- agentId: manifest.agent.id,
86
- channels: manifest.channels.map((ch) => ({
87
- id: ch.id,
88
- name: ch.name,
89
- kind: ch.kind,
90
- })),
91
- };
92
-
93
- // --- Step 3: Check vault for config ---
94
- console.log("\n→ Checking vault for provisioning config...");
95
- try {
96
- const vaultResp = await sdk.listVaultItems() as any;
97
- const items = vaultResp.items || vaultResp.data || [];
98
- const configItem = items.find((i: any) => i.name === VAULT_CONFIG_NAME);
99
-
100
- if (configItem) {
101
- console.log(` ✓ Found ${VAULT_CONFIG_NAME} vault item`);
102
- const revealResp = await sdk.revealItem(configItem.id) as any;
103
- const fields = revealResp.fields || revealResp.data || {};
104
-
105
- config.llmApiKey = fields.llm_api_key || fields.openrouter_api_key;
106
- config.llmBaseUrl = fields.llm_base_url;
107
- config.llmModel = fields.llm_model;
108
- config.personality = fields.agent_personality || fields.personality;
109
- config.fleetRole = fields.fleet_role;
110
-
111
- if (config.llmApiKey) console.log(" ✓ LLM API key loaded from vault");
112
- if (config.personality) console.log(` ✓ Personality: ${config.personality.slice(0, 60)}...`);
113
- if (config.fleetRole) console.log(` ✓ Fleet role: ${config.fleetRole}`);
114
- } else {
115
- console.log(" ! No hivemind-config vault item found");
116
- console.log(" ! Will prompt for LLM API key instead");
117
- }
118
- } catch (err) {
119
- console.log(` ! Could not read vault: ${(err as Error).message}`);
120
- }
121
- } catch (err) {
122
- console.error(`\n ✗ Failed to connect to Sesame: ${(err as Error).message}`);
123
- console.error(" Check your API key and try again.");
124
- process.exit(1);
125
- } finally {
126
- sdk.disconnect();
127
- }
128
-
129
- // --- Step 4: Prompt for anything missing ---
130
- if (!config.llmApiKey) {
131
- console.log(" ! No LLM API key found in vault — set LLM_API_KEY in .env after init");
132
- }
133
-
134
- // --- Step 5: Write config files ---
135
- console.log("\n→ Writing configuration...");
136
-
137
- mkdirSync(CONFIG_DIR, { recursive: true });
138
- mkdirSync(WORKSPACE_DIR, { recursive: true });
139
-
140
- // Copy default.toml from installed package if not present
141
- const defaultToml = resolve(CONFIG_DIR, "default.toml");
142
- if (!existsSync(defaultToml)) {
143
- // Resolve from the hivemind binary location
144
- // process.argv[1] may be a symlink, so resolve it first
145
- const realBin = realpathSync(process.argv[1]);
146
- // realBin is <pkg>/dist/main.js, so ../config/ gets us to <pkg>/config/
147
- const packageConfigDir = resolve(dirname(realBin), "..", "config");
148
- const packageDefault = resolve(packageConfigDir, "default.toml");
149
- if (existsSync(packageDefault)) {
150
- copyFileSync(packageDefault, defaultToml);
151
- console.log(` ✓ ${defaultToml}`);
152
- // Also copy team charter if available
153
- const packageCharter = resolve(packageConfigDir, "TEAM-CHARTER.md");
154
- const localCharter = resolve(CONFIG_DIR, "TEAM-CHARTER.md");
155
- if (existsSync(packageCharter) && !existsSync(localCharter)) {
156
- copyFileSync(packageCharter, localCharter);
157
- console.log(` ✓ ${localCharter}`);
158
- }
159
- } else {
160
- console.log(` ! default.toml not found in package — you may need to copy it manually`);
161
- }
162
- }
163
-
164
- // Write workspace identity files
165
- const soulPath = resolve(WORKSPACE_DIR, "SOUL.md");
166
- if (!existsSync(soulPath)) {
167
- const personality = config.personality || "A helpful, capable agent.";
168
- writeFileSync(soulPath, `# SOUL.md — Who You Are
169
-
170
- ${personality}
171
-
172
- ---
173
-
174
- _This file defines your personality and values. Edit it to evolve who you are._
175
- `);
176
- console.log(` ✓ ${soulPath}`);
177
- }
178
-
179
- const identityPath = resolve(WORKSPACE_DIR, "IDENTITY.md");
180
- if (!existsSync(identityPath)) {
181
- writeFileSync(identityPath, `# IDENTITY.md
182
-
183
- - **Name:** ${config.agentName}
184
- - **Handle:** ${config.agentHandle}
185
- - **Agent ID:** ${config.agentId}
186
- `);
187
- console.log(` ✓ ${identityPath}`);
188
- }
189
-
190
- // Write local.toml (overrides)
191
- const localToml = `# Generated by hivemind init — ${new Date().toISOString()}
192
- # Overrides config/default.toml with agent-specific settings
193
-
194
- [agent]
195
- name = "${config.agentName}"
196
- ${config.personality ? `personality = "${config.personality.replace(/"/g, '\\"')}"` : "# personality = (using default)"}
197
- workspace = "workspace"
198
-
199
- ${config.llmModel ? `[llm]\nmodel = "${config.llmModel}"` : "# [llm] using defaults"}
200
- ${config.llmBaseUrl ? `# base_url = "${config.llmBaseUrl}"` : ""}
201
-
202
- [sesame]
203
- api_key = "${sesameApiKey}"
204
- `;
205
-
206
- writeFileSync(LOCAL_TOML, localToml);
207
- console.log(` ✓ ${LOCAL_TOML}`);
208
-
209
- // Write .env
210
- const envContent = `# Hivemind Agent — ${config.agentName}
211
- # Generated by hivemind init — ${new Date().toISOString()}
212
- SESAME_API_KEY=${sesameApiKey}
213
- LLM_API_KEY=${config.llmApiKey || ""}
214
- AGENT_NAME=${config.agentName}
215
- `;
216
-
217
- writeFileSync(ENV_FILE, envContent, { mode: 0o600 });
218
- console.log(` ✓ ${ENV_FILE} (chmod 600)`);
219
-
220
- // --- Done ---
221
- console.log(`
222
- ✓ Hivemind initialized for ${config.agentName}!
223
-
224
- To start the agent:
225
- hivemind start
226
-
227
- To install as a service:
228
- hivemind service install
229
-
230
- Agent ID: ${config.agentId}
231
- Channels: ${config.channels.map((c) => c.name || c.id).join(", ")}
232
- Fleet role: ${config.fleetRole || "standalone"}
233
- `);
234
- }
235
-
236
- function printHelp(): void {
237
- console.log(`hivemind init — Initialize a Hivemind agent from Sesame
238
-
239
- Usage: hivemind init [sesame-api-key]
240
-
241
- The API key can also be passed as the first argument.
242
-
243
- What it does:
244
- 1. Connects to Sesame and fetches agent identity
245
- 2. Reads provisioning config from Sesame vault (if available)
246
- 3. Prompts for any missing configuration
247
- 4. Writes config/local.toml and .env
248
-
249
- Options:
250
- -h, --help Show this help
251
- `);
252
- }
@@ -1,159 +0,0 @@
1
- import { resolve } from "path";
2
- import { writeFileSync, existsSync, unlinkSync, mkdirSync } from "fs";
3
- import { execSync } from "child_process";
4
- import { homedir } from "os";
5
-
6
- const LAUNCH_AGENTS_DIR = resolve(homedir(), "Library/LaunchAgents");
7
- const AGENT_LABEL = "com.hivemind.agent";
8
-
9
- function getHivemindHome(): string {
10
- return process.env.HIVEMIND_HOME || resolve(homedir(), "hivemind");
11
- }
12
-
13
- function getHivemindBin(): string {
14
- // Try to find the hivemind binary
15
- try {
16
- const which = execSync("which hivemind", { encoding: "utf-8" }).trim();
17
- if (which) return which;
18
- } catch {}
19
- // Fallback to process.argv[1] (the script being run)
20
- return process.argv[1] || "hivemind";
21
- }
22
-
23
- function generatePlist(hivemindHome: string, hivemindBin: string): string {
24
- return `<?xml version="1.0" encoding="UTF-8"?>
25
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
26
- <plist version="1.0">
27
- <dict>
28
- <key>Label</key>
29
- <string>${AGENT_LABEL}</string>
30
- <key>ProgramArguments</key>
31
- <array>
32
- <string>${hivemindBin}</string>
33
- <string>start</string>
34
- </array>
35
- <key>WorkingDirectory</key>
36
- <string>${hivemindHome}</string>
37
- <key>EnvironmentVariables</key>
38
- <dict>
39
- <key>PATH</key>
40
- <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
41
- <key>HIVEMIND_HOME</key>
42
- <string>${hivemindHome}</string>
43
- </dict>
44
- <key>RunAtLoad</key>
45
- <true/>
46
- <key>KeepAlive</key>
47
- <true/>
48
- <key>StandardOutPath</key>
49
- <string>/tmp/hivemind-agent.log</string>
50
- <key>StandardErrorPath</key>
51
- <string>/tmp/hivemind-error.log</string>
52
- <key>ThrottleInterval</key>
53
- <integer>5</integer>
54
- </dict>
55
- </plist>`;
56
- }
57
-
58
- export async function runServiceCommand(args: string[]): Promise<void> {
59
- const subcommand = args[0];
60
-
61
- switch (subcommand) {
62
- case "install":
63
- await installService();
64
- break;
65
- case "uninstall":
66
- await uninstallService();
67
- break;
68
- case "status":
69
- showStatus();
70
- break;
71
- case "logs":
72
- showLogs(args[1]);
73
- break;
74
- default:
75
- printHelp();
76
- if (subcommand) {
77
- console.error(`Unknown subcommand: ${subcommand}`);
78
- process.exit(1);
79
- }
80
- break;
81
- }
82
- }
83
-
84
- async function installService(): Promise<void> {
85
- const hivemindHome = getHivemindHome();
86
- const hivemindBin = getHivemindBin();
87
-
88
- console.log(`\n→ Installing launchd service for ${hivemindHome}\n`);
89
- console.log(` Binary: ${hivemindBin}`);
90
-
91
- mkdirSync(LAUNCH_AGENTS_DIR, { recursive: true });
92
-
93
- const plistContent = generatePlist(hivemindHome, hivemindBin);
94
- const destPath = resolve(LAUNCH_AGENTS_DIR, `${AGENT_LABEL}.plist`);
95
-
96
- // Unload if already loaded
97
- try { execSync(`launchctl unload ${destPath} 2>/dev/null`); } catch {}
98
-
99
- writeFileSync(destPath, plistContent);
100
- execSync(`launchctl load ${destPath}`);
101
-
102
- console.log(` ✓ ${AGENT_LABEL} installed and started`);
103
- console.log("\n Service will auto-start on boot.");
104
- console.log(" Logs: /tmp/hivemind-agent.log, /tmp/hivemind-error.log\n");
105
- }
106
-
107
- async function uninstallService(): Promise<void> {
108
- console.log("\n→ Uninstalling launchd service\n");
109
-
110
- const plistPath = resolve(LAUNCH_AGENTS_DIR, `${AGENT_LABEL}.plist`);
111
- if (existsSync(plistPath)) {
112
- try { execSync(`launchctl unload ${plistPath} 2>/dev/null`); } catch {}
113
- unlinkSync(plistPath);
114
- console.log(` ✓ ${AGENT_LABEL} uninstalled`);
115
- } else {
116
- console.log(` - ${AGENT_LABEL} not installed`);
117
- }
118
- console.log("");
119
- }
120
-
121
- function showStatus(): void {
122
- console.log("\n→ Service status\n");
123
- try {
124
- const out = execSync(`launchctl list ${AGENT_LABEL} 2>/dev/null`, { encoding: "utf-8" });
125
- const pidMatch = out.match(/"PID"\s*=\s*(\d+)/);
126
- const pid = pidMatch ? pidMatch[1] : "unknown";
127
- console.log(` ✓ ${AGENT_LABEL}: running (PID ${pid})`);
128
- } catch {
129
- console.log(` - ${AGENT_LABEL}: not running`);
130
- }
131
- console.log("");
132
- }
133
-
134
- function showLogs(which?: string): void {
135
- const logFile = which === "error"
136
- ? "/tmp/hivemind-error.log"
137
- : "/tmp/hivemind-agent.log";
138
- try {
139
- execSync(`tail -30 ${logFile}`, { stdio: "inherit" });
140
- } catch {
141
- console.error(`No log file at ${logFile}`);
142
- }
143
- }
144
-
145
- function printHelp(): void {
146
- console.log(`hivemind service — Manage launchd service
147
-
148
- Usage: hivemind service <subcommand>
149
-
150
- Subcommands:
151
- install Install and start launchd service (survives reboots)
152
- uninstall Stop and remove launchd service
153
- status Show service status
154
- logs [agent|error] Show recent logs
155
-
156
- Service:
157
- com.hivemind.agent — Agent runtime (Node.js, Sesame)
158
- `);
159
- }