agi-farm 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -170,6 +170,39 @@ Answer the setup prompts and your team will be live in ~2 minutes:
170
170
  | 📤 `agi-farm export` | `agi-farm-export` | Push bundle to GitHub |
171
171
  | 🖥️ `agi-farm dashboard` | `agi-farm-dashboard` | Launch live ops room (SSE, :8080) |
172
172
  | ⚡ `agi-farm dispatch` | `agi-farm-dispatch` | Run auto-dispatcher manually |
173
+ | 🍎 `agi-farm launchagent` | `agi-farm-launchagent` | Install/uninstall macOS LaunchAgent for persistent dashboard |
174
+
175
+ ---
176
+
177
+ ## 🍎 Persistent Dashboard (macOS LaunchAgent)
178
+
179
+ The dashboard can run as a macOS LaunchAgent — it starts on login and auto-restarts if it crashes, independent of the OpenClaw gateway lifecycle.
180
+
181
+ ### Install
182
+
183
+ ```bash
184
+ # Install with defaults (port 8080, localhost)
185
+ agi-farm-launchagent
186
+
187
+ # Custom port and workspace
188
+ agi-farm-launchagent --port 9090 --workspace ~/my-workspace
189
+ ```
190
+
191
+ ### Uninstall
192
+
193
+ ```bash
194
+ agi-farm-launchagent --uninstall
195
+ ```
196
+
197
+ ### Why use this?
198
+
199
+ The plugin lifecycle (`onLoad`) spawns the dashboard as a child process. If the gateway exits, restarts, or doesn't reliably complete the lifecycle, the dashboard dies with it. The LaunchAgent runs the dashboard as an independent OS-level service:
200
+
201
+ - **RunAtLoad** — starts automatically on login
202
+ - **KeepAlive** — restarts if it crashes
203
+ - **Logs** — stdout/stderr saved to `/tmp/openclaw/agi-farm-dashboard.log`
204
+
205
+ > **Linux users**: Use `systemd` with a similar service unit. See the plist template in `templates/` for reference.
173
206
 
174
207
  ---
175
208
 
@@ -2,7 +2,7 @@
2
2
  "id": "agi-farm",
3
3
  "kind": "team-orchestration",
4
4
  "name": "AGI Farm — Multi-Agent Team Builder",
5
- "version": "1.1.0",
5
+ "version": "1.1.1",
6
6
  "description": "Bootstrap complete multi-agent AI teams with auto-dispatcher, live dashboard, and infrastructure. Includes interactive wizard, SOUL.md generation, comms setup, cron registration, and React + SSE ops room.",
7
7
  "author": "oabdelmaksoud",
8
8
  "homepage": "https://github.com/oabdelmaksoud/AGI-FARM-PLUGIN",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agi-farm",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Multi-agent AI team builder for OpenClaw — bootstrap complete teams with auto-dispatcher, dashboard, and infrastructure",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -11,7 +11,8 @@
11
11
  "agi-farm-dashboard": "scripts/dashboard.js",
12
12
  "agi-farm-dispatch": "scripts/dispatch.js",
13
13
  "agi-farm-export": "scripts/export.js",
14
- "agi-farm-rebuild": "scripts/rebuild.js"
14
+ "agi-farm-rebuild": "scripts/rebuild.js",
15
+ "agi-farm-launchagent": "scripts/install-launchagent.js"
15
16
  },
16
17
  "license": "MIT",
17
18
  "author": "oabdelmaksoud",
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * install-launchagent.js
5
+ *
6
+ * Installs a macOS LaunchAgent so the AGI Farm dashboard starts automatically
7
+ * on login and stays alive independent of the OpenClaw gateway lifecycle.
8
+ *
9
+ * Usage:
10
+ * node scripts/install-launchagent.js [--uninstall] [--port 8080] [--host 127.0.0.1]
11
+ *
12
+ * Options:
13
+ * --uninstall Remove the LaunchAgent and stop the dashboard
14
+ * --port PORT Dashboard port (default: 8080)
15
+ * --host HOST Dashboard bind address (default: 127.0.0.1)
16
+ * --workspace OpenClaw workspace path (default: ~/.openclaw/workspace)
17
+ */
18
+
19
+ import fs from 'fs';
20
+ import path from 'path';
21
+ import os from 'os';
22
+ import { execFileSync } from 'child_process';
23
+ import { fileURLToPath } from 'url';
24
+
25
+ const __filename = fileURLToPath(import.meta.url);
26
+ const __dirname = path.dirname(__filename);
27
+
28
+ const LABEL = 'ai.openclaw.agi-farm-dashboard';
29
+ const PLIST_NAME = `${LABEL}.plist`;
30
+ const LAUNCH_AGENTS_DIR = path.join(os.homedir(), 'Library', 'LaunchAgents');
31
+ const PLIST_PATH = path.join(LAUNCH_AGENTS_DIR, PLIST_NAME);
32
+ const LOG_DIR = '/tmp/openclaw';
33
+
34
+ // ── Parse CLI args ──────────────────────────────────────────────────────────
35
+
36
+ function parseArgs() {
37
+ const args = process.argv.slice(2);
38
+ const opts = {
39
+ uninstall: false,
40
+ port: '8080',
41
+ host: '127.0.0.1',
42
+ workspace: path.join(os.homedir(), '.openclaw', 'workspace'),
43
+ };
44
+
45
+ for (let i = 0; i < args.length; i++) {
46
+ if (args[i] === '--uninstall') opts.uninstall = true;
47
+ if (args[i] === '--port' && args[i + 1]) opts.port = args[++i];
48
+ if (args[i] === '--host' && args[i + 1]) opts.host = args[++i];
49
+ if (args[i] === '--workspace' && args[i + 1]) opts.workspace = args[++i];
50
+ }
51
+
52
+ return opts;
53
+ }
54
+
55
+ // ── Helpers ─────────────────────────────────────────────────────────────────
56
+
57
+ function getNodePath() {
58
+ try {
59
+ return execFileSync('which', ['node'], { encoding: 'utf-8' }).trim();
60
+ } catch {
61
+ return '/opt/homebrew/opt/node/bin/node';
62
+ }
63
+ }
64
+
65
+ function getSystemPath() {
66
+ const defaults = '/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin';
67
+ const nodeBin = path.dirname(getNodePath());
68
+ if (defaults.includes(nodeBin)) return defaults;
69
+ return `${nodeBin}:${defaults}`;
70
+ }
71
+
72
+ function getUid() {
73
+ try {
74
+ return execFileSync('id', ['-u'], { encoding: 'utf-8' }).trim();
75
+ } catch {
76
+ return String(process.getuid?.() ?? 501);
77
+ }
78
+ }
79
+
80
+ function bootout() {
81
+ try {
82
+ execFileSync('launchctl', ['bootout', `gui/${getUid()}`, LABEL], { stdio: 'ignore' });
83
+ } catch {
84
+ // already unloaded
85
+ }
86
+ }
87
+
88
+ // ── Uninstall ───────────────────────────────────────────────────────────────
89
+
90
+ function uninstall() {
91
+ console.log(`\u{1F5D1}\uFE0F Uninstalling LaunchAgent: ${LABEL}`);
92
+
93
+ bootout();
94
+
95
+ if (fs.existsSync(PLIST_PATH)) {
96
+ fs.unlinkSync(PLIST_PATH);
97
+ console.log(` Removed ${PLIST_PATH}`);
98
+ } else {
99
+ console.log(' Plist not found (already removed)');
100
+ }
101
+
102
+ console.log('\u2705 LaunchAgent uninstalled. Dashboard will no longer auto-start.');
103
+ }
104
+
105
+ // ── Install ─────────────────────────────────────────────────────────────────
106
+
107
+ function install(opts) {
108
+ console.log(`\u{1F680} Installing LaunchAgent: ${LABEL}`);
109
+
110
+ const pluginDir = path.resolve(__dirname, '..');
111
+ const dashboardScript = path.join(pluginDir, 'server', 'dashboard.js');
112
+
113
+ if (!fs.existsSync(dashboardScript)) {
114
+ console.error(`\u274C Dashboard script not found: ${dashboardScript}`);
115
+ process.exit(1);
116
+ }
117
+
118
+ const nodePath = getNodePath();
119
+ const systemPath = getSystemPath();
120
+
121
+ // Read template
122
+ const templatePath = path.join(pluginDir, 'templates', 'ai.openclaw.agi-farm-dashboard.plist.template');
123
+ let plistContent;
124
+
125
+ if (fs.existsSync(templatePath)) {
126
+ plistContent = fs.readFileSync(templatePath, 'utf-8')
127
+ .replace(/<%= nodePath %>/g, nodePath)
128
+ .replace(/<%= dashboardScript %>/g, dashboardScript)
129
+ .replace(/<%= port %>/g, opts.port)
130
+ .replace(/<%= host %>/g, opts.host)
131
+ .replace(/<%= workspace %>/g, opts.workspace)
132
+ .replace(/<%= systemPath %>/g, systemPath)
133
+ .replace(/<%= pluginDir %>/g, pluginDir)
134
+ .replace(/<%= logDir %>/g, LOG_DIR);
135
+ } else {
136
+ // Fallback: generate plist inline
137
+ plistContent = `<?xml version="1.0" encoding="UTF-8"?>
138
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
139
+ <plist version="1.0">
140
+ <dict>
141
+ <key>Label</key>
142
+ <string>${LABEL}</string>
143
+ <key>ProgramArguments</key>
144
+ <array>
145
+ <string>${nodePath}</string>
146
+ <string>${dashboardScript}</string>
147
+ <string>--no-browser</string>
148
+ <string>--port</string>
149
+ <string>${opts.port}</string>
150
+ </array>
151
+ <key>EnvironmentVariables</key>
152
+ <dict>
153
+ <key>PATH</key>
154
+ <string>${systemPath}</string>
155
+ <key>AGI_FARM_DASHBOARD_PORT</key>
156
+ <string>${opts.port}</string>
157
+ <key>AGI_FARM_DASHBOARD_HOST</key>
158
+ <string>${opts.host}</string>
159
+ <key>AGI_FARM_WORKSPACE</key>
160
+ <string>${opts.workspace}</string>
161
+ </dict>
162
+ <key>RunAtLoad</key>
163
+ <true/>
164
+ <key>KeepAlive</key>
165
+ <true/>
166
+ <key>WorkingDirectory</key>
167
+ <string>${pluginDir}</string>
168
+ <key>StandardOutPath</key>
169
+ <string>${LOG_DIR}/agi-farm-dashboard.log</string>
170
+ <key>StandardErrorPath</key>
171
+ <string>${LOG_DIR}/agi-farm-dashboard.err.log</string>
172
+ </dict>
173
+ </plist>`;
174
+ }
175
+
176
+ // Ensure directories exist
177
+ fs.mkdirSync(LAUNCH_AGENTS_DIR, { recursive: true });
178
+ fs.mkdirSync(LOG_DIR, { recursive: true });
179
+
180
+ // Unload existing if any
181
+ bootout();
182
+
183
+ // Write plist
184
+ fs.writeFileSync(PLIST_PATH, plistContent, 'utf-8');
185
+ console.log(` Wrote ${PLIST_PATH}`);
186
+
187
+ // Load
188
+ const uid = getUid();
189
+ try {
190
+ execFileSync('launchctl', ['bootstrap', `gui/${uid}`, PLIST_PATH], { stdio: 'inherit' });
191
+ console.log(` Bootstrapped into gui/${uid}`);
192
+ } catch {
193
+ console.warn(' \u26A0\uFE0F Bootstrap failed \u2014 trying legacy load...');
194
+ try {
195
+ execFileSync('launchctl', ['load', PLIST_PATH], { stdio: 'inherit' });
196
+ } catch {
197
+ console.error(' \u274C Failed to load LaunchAgent');
198
+ process.exit(1);
199
+ }
200
+ }
201
+
202
+ // Kickstart
203
+ try {
204
+ execFileSync('launchctl', ['kickstart', '-k', `gui/${uid}/${LABEL}`], { stdio: 'ignore' });
205
+ } catch {
206
+ // not critical
207
+ }
208
+
209
+ console.log('');
210
+ console.log(`\u2705 Dashboard LaunchAgent installed!`);
211
+ console.log(` URL: http://${opts.host}:${opts.port}`);
212
+ console.log(` Logs: ${LOG_DIR}/agi-farm-dashboard.log`);
213
+ console.log(` Errors: ${LOG_DIR}/agi-farm-dashboard.err.log`);
214
+ console.log(` Uninstall: node scripts/install-launchagent.js --uninstall`);
215
+ }
216
+
217
+ // ── Main ────────────────────────────────────────────────────────────────────
218
+
219
+ if (process.platform !== 'darwin') {
220
+ console.error('\u274C LaunchAgent is macOS-only. On Linux, use systemd instead.');
221
+ console.error(' See README for systemd instructions.');
222
+ process.exit(1);
223
+ }
224
+
225
+ const opts = parseArgs();
226
+
227
+ if (opts.uninstall) {
228
+ uninstall();
229
+ } else {
230
+ install(opts);
231
+ }
@@ -0,0 +1,42 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Label</key>
6
+ <string>ai.openclaw.agi-farm-dashboard</string>
7
+
8
+ <key>ProgramArguments</key>
9
+ <array>
10
+ <string><%= nodePath %></string>
11
+ <string><%= dashboardScript %></string>
12
+ <string>--no-browser</string>
13
+ <string>--port</string>
14
+ <string><%= port %></string>
15
+ </array>
16
+
17
+ <key>EnvironmentVariables</key>
18
+ <dict>
19
+ <key>PATH</key>
20
+ <string><%= systemPath %></string>
21
+ <key>AGI_FARM_DASHBOARD_PORT</key>
22
+ <string><%= port %></string>
23
+ <key>AGI_FARM_DASHBOARD_HOST</key>
24
+ <string><%= host %></string>
25
+ <key>AGI_FARM_WORKSPACE</key>
26
+ <string><%= workspace %></string>
27
+ </dict>
28
+
29
+ <key>RunAtLoad</key>
30
+ <true/>
31
+ <key>KeepAlive</key>
32
+ <true/>
33
+
34
+ <key>WorkingDirectory</key>
35
+ <string><%= pluginDir %></string>
36
+
37
+ <key>StandardOutPath</key>
38
+ <string><%= logDir %>/agi-farm-dashboard.log</string>
39
+ <key>StandardErrorPath</key>
40
+ <string><%= logDir %>/agi-farm-dashboard.err.log</string>
41
+ </dict>
42
+ </plist>