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
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
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.
|
|
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>
|