@openclaw-cloud/agent-controller 0.2.0 → 0.2.2
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/CLAUDE.md +9 -8
- package/bin/agent-controller.js +6 -0
- package/dist/commands/install.d.ts +1 -0
- package/dist/commands/install.js +54 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/uninstall.d.ts +1 -0
- package/dist/commands/uninstall.js +22 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/index.js +0 -3
- package/dist/index.js.map +1 -1
- package/dist/platform/linux.d.ts +8 -0
- package/dist/platform/linux.js +96 -0
- package/dist/platform/linux.js.map +1 -0
- package/dist/platform/macos.d.ts +8 -0
- package/dist/platform/macos.js +109 -0
- package/dist/platform/macos.js.map +1 -0
- package/dist/platform/windows.d.ts +8 -0
- package/dist/platform/windows.js +77 -0
- package/dist/platform/windows.js.map +1 -0
- package/package.json +1 -1
- package/src/commands/install.ts +64 -0
- package/src/commands/uninstall.ts +19 -0
- package/src/index.ts +0 -4
- package/src/platform/linux.ts +108 -0
- package/src/platform/macos.ts +122 -0
- package/src/platform/windows.ts +92 -0
package/CLAUDE.md
CHANGED
|
@@ -91,7 +91,7 @@ src/
|
|
|
91
91
|
|
|
92
92
|
### Command dispatch flow
|
|
93
93
|
```
|
|
94
|
-
Centrifugo WS message → connection.ts (handler map) → handlers/<type>.ts → AgentResponse →
|
|
94
|
+
Centrifugo WS message → connection.ts (handler map) → handlers/<type>.ts → AgentResponse → POST /api/internal/agent-response
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
Handler map in `connection.ts`:
|
|
@@ -132,13 +132,16 @@ Agent-controller is a Node.js process that runs inside every agent container (K8
|
|
|
132
132
|
### Communication Flow
|
|
133
133
|
```
|
|
134
134
|
Backend → Centrifugo WS (port 8000) → agent-controller → local agent process
|
|
135
|
-
agent-controller →
|
|
135
|
+
agent-controller → Backend HTTP API → /api/internal/agent-response (command responses)
|
|
136
|
+
agent-controller → Backend HTTP API → /api/internal/agent-heartbeat (heartbeat)
|
|
136
137
|
```
|
|
137
138
|
|
|
139
|
+
Centrifugo WS is **read-only** from agent-controller's perspective — all outbound traffic goes through the backend HTTP API, not directly to Centrifugo.
|
|
140
|
+
|
|
138
141
|
### Key Components
|
|
139
|
-
- **WebSocket client** — Connects to Centrifugo, subscribes to agent channel
|
|
140
|
-
- **Heartbeat publisher** — Sends periodic heartbeats via
|
|
141
|
-
- **Command handlers** — Processes commands from backend
|
|
142
|
+
- **WebSocket client** — Connects to Centrifugo, subscribes to agent channel (read-only)
|
|
143
|
+
- **Heartbeat publisher** — Sends periodic heartbeats via `POST /api/internal/agent-heartbeat`
|
|
144
|
+
- **Command handlers** — Processes commands from backend, sends responses via `POST /api/internal/agent-response`
|
|
142
145
|
- **Board handler** — Kanban board integration (claim cards, move cards, report status)
|
|
143
146
|
- **JWT auth** — Fetches JWT from backend's `/api/internal/centrifugo-token` endpoint, auto-refreshes before expiry (24h TTL)
|
|
144
147
|
|
|
@@ -146,9 +149,7 @@ agent-controller → Centrifugo HTTP API (port 9000) → Backend (heartbeat proc
|
|
|
146
149
|
- `AGENT_ID` — UUID of this agent
|
|
147
150
|
- `AGENT_TOKEN` — Internal auth token
|
|
148
151
|
- `CENTRIFUGO_URL` — WebSocket URL (port 8000, converted from http→ws)
|
|
149
|
-
- `
|
|
150
|
-
- `CENTRIFUGO_API_KEY` — API key for HTTP API auth
|
|
151
|
-
- `API_BASE_URL` — Backend API URL for JWT fetch
|
|
152
|
+
- `API_BASE_URL` — Backend API URL (JWT fetch, agent responses, heartbeat)
|
|
152
153
|
|
|
153
154
|
### Deployment
|
|
154
155
|
|
package/bin/agent-controller.js
CHANGED
|
@@ -14,6 +14,12 @@ if (command === '--version' || command === '-v') {
|
|
|
14
14
|
} else if (command === 'self-update') {
|
|
15
15
|
const { selfUpdate } = await import('../dist/commands/self-update.js');
|
|
16
16
|
await selfUpdate(pkg.version);
|
|
17
|
+
} else if (command === 'install') {
|
|
18
|
+
const { install } = await import('../dist/commands/install.js');
|
|
19
|
+
await install();
|
|
20
|
+
} else if (command === 'uninstall') {
|
|
21
|
+
const { uninstall } = await import('../dist/commands/uninstall.js');
|
|
22
|
+
await uninstall();
|
|
17
23
|
} else {
|
|
18
24
|
const { main } = await import('../dist/index.js');
|
|
19
25
|
main();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function install(): Promise<void>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
async function prompt(rl, question) {
|
|
3
|
+
return new Promise((resolve) => {
|
|
4
|
+
rl.question(question, (answer) => {
|
|
5
|
+
resolve(answer.trim());
|
|
6
|
+
});
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
async function getOrPrompt(rl, envKey, question) {
|
|
10
|
+
const fromEnv = process.env[envKey];
|
|
11
|
+
if (fromEnv) {
|
|
12
|
+
const display = envKey.includes('TOKEN') ? '***' : fromEnv;
|
|
13
|
+
console.log(` ${envKey}: ${display} (from environment)`);
|
|
14
|
+
return fromEnv;
|
|
15
|
+
}
|
|
16
|
+
return prompt(rl, question);
|
|
17
|
+
}
|
|
18
|
+
export async function install() {
|
|
19
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
20
|
+
console.log('OpenClaw Agent Controller — Installation Wizard');
|
|
21
|
+
console.log('================================================');
|
|
22
|
+
console.log('');
|
|
23
|
+
const centrifugoUrl = await getOrPrompt(rl, 'CENTRIFUGO_URL', 'CENTRIFUGO_URL (e.g. wss://ws.openclaw-cloud.io): ');
|
|
24
|
+
const agentToken = await getOrPrompt(rl, 'AGENT_TOKEN', 'AGENT_TOKEN: ');
|
|
25
|
+
const agentId = await getOrPrompt(rl, 'AGENT_ID', 'AGENT_ID: ');
|
|
26
|
+
const backendInternalUrl = await getOrPrompt(rl, 'BACKEND_INTERNAL_URL', 'BACKEND_INTERNAL_URL (e.g. https://api.openclaw-cloud.io): ');
|
|
27
|
+
rl.close();
|
|
28
|
+
if (!centrifugoUrl || !agentToken || !agentId || !backendInternalUrl) {
|
|
29
|
+
console.error('All fields are required. Aborting.');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
const config = { centrifugoUrl, agentToken, agentId, backendInternalUrl };
|
|
33
|
+
const platform = process.platform;
|
|
34
|
+
console.log('');
|
|
35
|
+
console.log(`Detected platform: ${platform}`);
|
|
36
|
+
console.log('');
|
|
37
|
+
if (platform === 'darwin') {
|
|
38
|
+
const { installMacOS } = await import('../platform/macos.js');
|
|
39
|
+
await installMacOS(config);
|
|
40
|
+
}
|
|
41
|
+
else if (platform === 'linux') {
|
|
42
|
+
const { installLinux } = await import('../platform/linux.js');
|
|
43
|
+
await installLinux(config);
|
|
44
|
+
}
|
|
45
|
+
else if (platform === 'win32') {
|
|
46
|
+
const { installWindows } = await import('../platform/windows.js');
|
|
47
|
+
await installWindows(config);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
console.error(`Unsupported platform: ${platform}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=install.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function uninstall(): Promise<void>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export async function uninstall() {
|
|
2
|
+
const platform = process.platform;
|
|
3
|
+
console.log(`Uninstalling Agent Controller on platform: ${platform}`);
|
|
4
|
+
console.log('');
|
|
5
|
+
if (platform === 'darwin') {
|
|
6
|
+
const { uninstallMacOS } = await import('../platform/macos.js');
|
|
7
|
+
await uninstallMacOS();
|
|
8
|
+
}
|
|
9
|
+
else if (platform === 'linux') {
|
|
10
|
+
const { uninstallLinux } = await import('../platform/linux.js');
|
|
11
|
+
await uninstallLinux();
|
|
12
|
+
}
|
|
13
|
+
else if (platform === 'win32') {
|
|
14
|
+
const { uninstallWindows } = await import('../platform/windows.js');
|
|
15
|
+
await uninstallWindows();
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
console.error(`Unsupported platform: ${platform}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=uninstall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uninstall.js","sourceRoot":"","sources":["../../src/commands/uninstall.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,OAAO,CAAC,GAAG,CAAC,8CAA8C,QAAQ,EAAE,CAAC,CAAC;IACtE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC;QAChE,MAAM,cAAc,EAAE,CAAC;IACzB,CAAC;SAAM,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChC,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC;QAChE,MAAM,cAAc,EAAE,CAAC;IACzB,CAAC;SAAM,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChC,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;QACpE,MAAM,gBAAgB,EAAE,CAAC;IAC3B,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CAAC,yBAAyB,QAAQ,EAAE,CAAC,CAAC;QACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -19,9 +19,6 @@ export function main() {
|
|
|
19
19
|
const token = requireEnv('AGENT_TOKEN');
|
|
20
20
|
const agentId = requireEnv('AGENT_ID');
|
|
21
21
|
const backendUrl = requireEnv('BACKEND_INTERNAL_URL');
|
|
22
|
-
// Read for backward compatibility but no longer used directly
|
|
23
|
-
void process.env.CENTRIFUGO_INTERNAL_URL;
|
|
24
|
-
void process.env.CENTRIFUGO_API_KEY;
|
|
25
22
|
if (DEBUG)
|
|
26
23
|
console.log('[debug] Debug mode enabled');
|
|
27
24
|
console.log(`Starting agent-controller for agent: ${agentId}`);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,SAAS,UAAU,CAAC,IAAY;IAC9B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,0CAA0C,IAAI,EAAE,CAAC,CAAC;QAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,IAAI;IAClB,MAAM,MAAM,GAAG,UAAU,CAAC,gBAAgB,CAAC,CAAC;IAC5C,0CAA0C;IAC1C,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,QAAQ,CAAC,GAAG,uBAAuB,CAAC;IAC7G,MAAM,KAAK,GAAG,UAAU,CAAC,aAAa,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,UAAU,CAAC,sBAAsB,CAAC,CAAC;IACtD,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface PlatformConfig {
|
|
2
|
+
centrifugoUrl: string;
|
|
3
|
+
agentToken: string;
|
|
4
|
+
agentId: string;
|
|
5
|
+
backendInternalUrl: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function installLinux(config: PlatformConfig): Promise<void>;
|
|
8
|
+
export declare function uninstallLinux(): Promise<void>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
function execAsync(cmd) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
exec(cmd, (err, stdout) => {
|
|
8
|
+
if (err)
|
|
9
|
+
reject(err);
|
|
10
|
+
else
|
|
11
|
+
resolve(stdout.toString().trim());
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
async function which(bin) {
|
|
16
|
+
try {
|
|
17
|
+
return await execAsync(`which "${bin}"`);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return '';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function installLinux(config) {
|
|
24
|
+
const home = homedir();
|
|
25
|
+
const systemdDir = join(home, '.config', 'systemd', 'user');
|
|
26
|
+
const servicePath = join(systemdDir, 'agent-controller.service');
|
|
27
|
+
const nodePath = process.execPath;
|
|
28
|
+
let agentControllerPath = await which('agent-controller');
|
|
29
|
+
if (!agentControllerPath) {
|
|
30
|
+
agentControllerPath = join(nodePath, '..', 'agent-controller');
|
|
31
|
+
}
|
|
32
|
+
mkdirSync(systemdDir, { recursive: true });
|
|
33
|
+
const service = `[Unit]
|
|
34
|
+
Description=OpenClaw Agent Controller
|
|
35
|
+
After=network.target
|
|
36
|
+
|
|
37
|
+
[Service]
|
|
38
|
+
Type=simple
|
|
39
|
+
Restart=always
|
|
40
|
+
RestartSec=10
|
|
41
|
+
Environment=CENTRIFUGO_URL=${config.centrifugoUrl}
|
|
42
|
+
Environment=AGENT_TOKEN=${config.agentToken}
|
|
43
|
+
Environment=AGENT_ID=${config.agentId}
|
|
44
|
+
Environment=BACKEND_INTERNAL_URL=${config.backendInternalUrl}
|
|
45
|
+
ExecStart="${nodePath}" "${agentControllerPath}"
|
|
46
|
+
|
|
47
|
+
[Install]
|
|
48
|
+
WantedBy=default.target
|
|
49
|
+
`;
|
|
50
|
+
writeFileSync(servicePath, service, 'utf8');
|
|
51
|
+
console.log(`Written: ${servicePath}`);
|
|
52
|
+
try {
|
|
53
|
+
await execAsync('systemctl --user daemon-reload');
|
|
54
|
+
console.log('systemd daemon reloaded.');
|
|
55
|
+
await execAsync('systemctl --user enable --now agent-controller');
|
|
56
|
+
console.log('Service enabled and started.');
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
console.warn('systemctl command failed:', err instanceof Error ? err.message : err);
|
|
60
|
+
}
|
|
61
|
+
console.log('');
|
|
62
|
+
console.log('Agent Controller installed as a systemd user service.');
|
|
63
|
+
console.log('');
|
|
64
|
+
console.log('Useful commands:');
|
|
65
|
+
console.log(' Start: systemctl --user start agent-controller');
|
|
66
|
+
console.log(' Stop: systemctl --user stop agent-controller');
|
|
67
|
+
console.log(' Status: systemctl --user status agent-controller');
|
|
68
|
+
console.log(' Logs: journalctl --user -u agent-controller -f');
|
|
69
|
+
console.log(' Uninstall: agent-controller uninstall');
|
|
70
|
+
}
|
|
71
|
+
export async function uninstallLinux() {
|
|
72
|
+
try {
|
|
73
|
+
await execAsync('systemctl --user disable --now agent-controller');
|
|
74
|
+
console.log('Service disabled and stopped.');
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
console.warn('systemctl disable failed (may not be enabled):', err instanceof Error ? err.message : err);
|
|
78
|
+
}
|
|
79
|
+
const servicePath = join(homedir(), '.config', 'systemd', 'user', 'agent-controller.service');
|
|
80
|
+
const { unlinkSync, existsSync } = await import('node:fs');
|
|
81
|
+
if (existsSync(servicePath)) {
|
|
82
|
+
unlinkSync(servicePath);
|
|
83
|
+
console.log(`Removed: ${servicePath}`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.log(`Service file not found: ${servicePath}`);
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
await execAsync('systemctl --user daemon-reload');
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// best-effort
|
|
93
|
+
}
|
|
94
|
+
console.log('Agent Controller uninstalled.');
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=linux.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linux.js","sourceRoot":"","sources":["../../src/platform/linux.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface PlatformConfig {
|
|
2
|
+
centrifugoUrl: string;
|
|
3
|
+
agentToken: string;
|
|
4
|
+
agentId: string;
|
|
5
|
+
backendInternalUrl: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function installMacOS(config: PlatformConfig): Promise<void>;
|
|
8
|
+
export declare function uninstallMacOS(): Promise<void>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
function execAsync(cmd) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
exec(cmd, (err, stdout) => {
|
|
8
|
+
if (err)
|
|
9
|
+
reject(err);
|
|
10
|
+
else
|
|
11
|
+
resolve(stdout.toString().trim());
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
async function which(bin) {
|
|
16
|
+
try {
|
|
17
|
+
return await execAsync(`which "${bin}"`);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return '';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function installMacOS(config) {
|
|
24
|
+
const home = homedir();
|
|
25
|
+
const launchAgentsDir = join(home, 'Library', 'LaunchAgents');
|
|
26
|
+
const plistPath = join(launchAgentsDir, 'com.openclaw.agent-controller.plist');
|
|
27
|
+
const logDir = join(home, 'Library', 'Logs');
|
|
28
|
+
const logFile = join(logDir, 'agent-controller.log');
|
|
29
|
+
const errFile = join(logDir, 'agent-controller.err');
|
|
30
|
+
const nodePath = process.execPath;
|
|
31
|
+
let agentControllerPath = await which('agent-controller');
|
|
32
|
+
if (!agentControllerPath) {
|
|
33
|
+
// Fallback: derive from node path
|
|
34
|
+
agentControllerPath = join(nodePath, '..', 'agent-controller');
|
|
35
|
+
}
|
|
36
|
+
mkdirSync(launchAgentsDir, { recursive: true });
|
|
37
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
38
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
39
|
+
<plist version="1.0">
|
|
40
|
+
<dict>
|
|
41
|
+
<key>Label</key>
|
|
42
|
+
<string>com.openclaw.agent-controller</string>
|
|
43
|
+
<key>ProgramArguments</key>
|
|
44
|
+
<array>
|
|
45
|
+
<string>${nodePath}</string>
|
|
46
|
+
<string>${agentControllerPath}</string>
|
|
47
|
+
</array>
|
|
48
|
+
<key>EnvironmentVariables</key>
|
|
49
|
+
<dict>
|
|
50
|
+
<key>CENTRIFUGO_URL</key>
|
|
51
|
+
<string>${config.centrifugoUrl}</string>
|
|
52
|
+
<key>AGENT_TOKEN</key>
|
|
53
|
+
<string>${config.agentToken}</string>
|
|
54
|
+
<key>AGENT_ID</key>
|
|
55
|
+
<string>${config.agentId}</string>
|
|
56
|
+
<key>BACKEND_INTERNAL_URL</key>
|
|
57
|
+
<string>${config.backendInternalUrl}</string>
|
|
58
|
+
</dict>
|
|
59
|
+
<key>RunAtLoad</key>
|
|
60
|
+
<true/>
|
|
61
|
+
<key>KeepAlive</key>
|
|
62
|
+
<true/>
|
|
63
|
+
<key>StandardOutPath</key>
|
|
64
|
+
<string>${logFile}</string>
|
|
65
|
+
<key>StandardErrorPath</key>
|
|
66
|
+
<string>${errFile}</string>
|
|
67
|
+
</dict>
|
|
68
|
+
</plist>
|
|
69
|
+
`;
|
|
70
|
+
writeFileSync(plistPath, plist, 'utf8');
|
|
71
|
+
console.log(`Written: ${plistPath}`);
|
|
72
|
+
try {
|
|
73
|
+
await execAsync(`launchctl load -w "${plistPath}"`);
|
|
74
|
+
console.log('Service loaded via launchctl.');
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
console.warn('launchctl load failed (may already be loaded):', err instanceof Error ? err.message : err);
|
|
78
|
+
}
|
|
79
|
+
console.log('');
|
|
80
|
+
console.log('Agent Controller installed as a macOS LaunchAgent.');
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log('Useful commands:');
|
|
83
|
+
console.log(` Start: launchctl load -w "${plistPath}"`);
|
|
84
|
+
console.log(` Stop: launchctl unload "${plistPath}"`);
|
|
85
|
+
console.log(` Logs: tail -f "${logFile}"`);
|
|
86
|
+
console.log(` Errors: tail -f "${errFile}"`);
|
|
87
|
+
console.log(` Uninstall: agent-controller uninstall`);
|
|
88
|
+
}
|
|
89
|
+
export async function uninstallMacOS() {
|
|
90
|
+
const home = homedir();
|
|
91
|
+
const plistPath = join(home, 'Library', 'LaunchAgents', 'com.openclaw.agent-controller.plist');
|
|
92
|
+
try {
|
|
93
|
+
await execAsync(`launchctl unload "${plistPath}"`);
|
|
94
|
+
console.log('Service unloaded via launchctl.');
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
console.warn('launchctl unload failed (may not be loaded):', err instanceof Error ? err.message : err);
|
|
98
|
+
}
|
|
99
|
+
const { unlinkSync, existsSync } = await import('node:fs');
|
|
100
|
+
if (existsSync(plistPath)) {
|
|
101
|
+
unlinkSync(plistPath);
|
|
102
|
+
console.log(`Removed: ${plistPath}`);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
console.log(`Plist not found: ${plistPath}`);
|
|
106
|
+
}
|
|
107
|
+
console.log('Agent Controller uninstalled.');
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=macos.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"macos.js","sourceRoot":"","sources":["../../src/platform/macos.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface PlatformConfig {
|
|
2
|
+
centrifugoUrl: string;
|
|
3
|
+
agentToken: string;
|
|
4
|
+
agentId: string;
|
|
5
|
+
backendInternalUrl: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function installWindows(config: PlatformConfig): Promise<void>;
|
|
8
|
+
export declare function uninstallWindows(): Promise<void>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
function execAsync(cmd) {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
exec(cmd, (err, stdout) => {
|
|
7
|
+
if (err)
|
|
8
|
+
reject(err);
|
|
9
|
+
else
|
|
10
|
+
resolve(stdout.toString().trim());
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
async function which(bin) {
|
|
15
|
+
try {
|
|
16
|
+
return await execAsync(`where "${bin}"`);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function installWindows(config) {
|
|
23
|
+
const appData = process.env['APPDATA'] ?? join(process.env['USERPROFILE'] ?? 'C:\\Users\\Default', 'AppData', 'Roaming');
|
|
24
|
+
const openclawDir = join(appData, 'openclaw');
|
|
25
|
+
const wrapperPath = join(openclawDir, 'agent-controller.cmd');
|
|
26
|
+
const nodePath = process.execPath;
|
|
27
|
+
let agentControllerPath = await which('agent-controller');
|
|
28
|
+
if (!agentControllerPath) {
|
|
29
|
+
agentControllerPath = join(nodePath, '..', 'agent-controller');
|
|
30
|
+
}
|
|
31
|
+
mkdirSync(openclawDir, { recursive: true });
|
|
32
|
+
const wrapper = `@echo off
|
|
33
|
+
set CENTRIFUGO_URL=${config.centrifugoUrl}
|
|
34
|
+
set AGENT_TOKEN=${config.agentToken}
|
|
35
|
+
set AGENT_ID=${config.agentId}
|
|
36
|
+
set BACKEND_INTERNAL_URL=${config.backendInternalUrl}
|
|
37
|
+
"${nodePath}" "${agentControllerPath}"
|
|
38
|
+
`;
|
|
39
|
+
writeFileSync(wrapperPath, wrapper, 'utf8');
|
|
40
|
+
console.log(`Written: ${wrapperPath}`);
|
|
41
|
+
try {
|
|
42
|
+
await execAsync(`schtasks /create /tn "OpenClaw Agent Controller" /tr "${wrapperPath}" /sc ONLOGON /rl HIGHEST /f`);
|
|
43
|
+
console.log('Scheduled task created.');
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
console.warn('schtasks create failed:', err instanceof Error ? err.message : err);
|
|
47
|
+
}
|
|
48
|
+
console.log('');
|
|
49
|
+
console.log('Agent Controller installed as a Windows scheduled task (ONLOGON).');
|
|
50
|
+
console.log('');
|
|
51
|
+
console.log('Useful commands:');
|
|
52
|
+
console.log(' Start: schtasks /run /tn "OpenClaw Agent Controller"');
|
|
53
|
+
console.log(' Stop: schtasks /end /tn "OpenClaw Agent Controller"');
|
|
54
|
+
console.log(' Uninstall: agent-controller uninstall');
|
|
55
|
+
console.log(` Wrapper: ${wrapperPath}`);
|
|
56
|
+
}
|
|
57
|
+
export async function uninstallWindows() {
|
|
58
|
+
try {
|
|
59
|
+
await execAsync('schtasks /delete /tn "OpenClaw Agent Controller" /f');
|
|
60
|
+
console.log('Scheduled task deleted.');
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
console.warn('schtasks delete failed (may not exist):', err instanceof Error ? err.message : err);
|
|
64
|
+
}
|
|
65
|
+
const appData = process.env['APPDATA'] ?? join(process.env['USERPROFILE'] ?? 'C:\\Users\\Default', 'AppData', 'Roaming');
|
|
66
|
+
const wrapperPath = join(appData, 'openclaw', 'agent-controller.cmd');
|
|
67
|
+
const { unlinkSync, existsSync } = await import('node:fs');
|
|
68
|
+
if (existsSync(wrapperPath)) {
|
|
69
|
+
unlinkSync(wrapperPath);
|
|
70
|
+
console.log(`Removed: ${wrapperPath}`);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
console.log(`Wrapper not found: ${wrapperPath}`);
|
|
74
|
+
}
|
|
75
|
+
console.log('Agent Controller uninstalled.');
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=windows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows.js","sourceRoot":"","sources":["../../src/platform/windows.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
|
|
3
|
+
async function prompt(rl: ReturnType<typeof createInterface>, question: string): Promise<string> {
|
|
4
|
+
return new Promise((resolve) => {
|
|
5
|
+
rl.question(question, (answer) => {
|
|
6
|
+
resolve(answer.trim());
|
|
7
|
+
});
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function getOrPrompt(
|
|
12
|
+
rl: ReturnType<typeof createInterface>,
|
|
13
|
+
envKey: string,
|
|
14
|
+
question: string,
|
|
15
|
+
): Promise<string> {
|
|
16
|
+
const fromEnv = process.env[envKey];
|
|
17
|
+
if (fromEnv) {
|
|
18
|
+
const display = envKey.includes('TOKEN') ? '***' : fromEnv;
|
|
19
|
+
console.log(` ${envKey}: ${display} (from environment)`);
|
|
20
|
+
return fromEnv;
|
|
21
|
+
}
|
|
22
|
+
return prompt(rl, question);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function install(): Promise<void> {
|
|
26
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
27
|
+
|
|
28
|
+
console.log('OpenClaw Agent Controller — Installation Wizard');
|
|
29
|
+
console.log('================================================');
|
|
30
|
+
console.log('');
|
|
31
|
+
|
|
32
|
+
const centrifugoUrl = await getOrPrompt(rl, 'CENTRIFUGO_URL', 'CENTRIFUGO_URL (e.g. wss://ws.openclaw-cloud.io): ');
|
|
33
|
+
const agentToken = await getOrPrompt(rl, 'AGENT_TOKEN', 'AGENT_TOKEN: ');
|
|
34
|
+
const agentId = await getOrPrompt(rl, 'AGENT_ID', 'AGENT_ID: ');
|
|
35
|
+
const backendInternalUrl = await getOrPrompt(rl, 'BACKEND_INTERNAL_URL', 'BACKEND_INTERNAL_URL (e.g. https://api.openclaw-cloud.io): ');
|
|
36
|
+
|
|
37
|
+
rl.close();
|
|
38
|
+
|
|
39
|
+
if (!centrifugoUrl || !agentToken || !agentId || !backendInternalUrl) {
|
|
40
|
+
console.error('All fields are required. Aborting.');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const config = { centrifugoUrl, agentToken, agentId, backendInternalUrl };
|
|
45
|
+
|
|
46
|
+
const platform = process.platform;
|
|
47
|
+
console.log('');
|
|
48
|
+
console.log(`Detected platform: ${platform}`);
|
|
49
|
+
console.log('');
|
|
50
|
+
|
|
51
|
+
if (platform === 'darwin') {
|
|
52
|
+
const { installMacOS } = await import('../platform/macos.js');
|
|
53
|
+
await installMacOS(config);
|
|
54
|
+
} else if (platform === 'linux') {
|
|
55
|
+
const { installLinux } = await import('../platform/linux.js');
|
|
56
|
+
await installLinux(config);
|
|
57
|
+
} else if (platform === 'win32') {
|
|
58
|
+
const { installWindows } = await import('../platform/windows.js');
|
|
59
|
+
await installWindows(config);
|
|
60
|
+
} else {
|
|
61
|
+
console.error(`Unsupported platform: ${platform}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export async function uninstall(): Promise<void> {
|
|
2
|
+
const platform = process.platform;
|
|
3
|
+
console.log(`Uninstalling Agent Controller on platform: ${platform}`);
|
|
4
|
+
console.log('');
|
|
5
|
+
|
|
6
|
+
if (platform === 'darwin') {
|
|
7
|
+
const { uninstallMacOS } = await import('../platform/macos.js');
|
|
8
|
+
await uninstallMacOS();
|
|
9
|
+
} else if (platform === 'linux') {
|
|
10
|
+
const { uninstallLinux } = await import('../platform/linux.js');
|
|
11
|
+
await uninstallLinux();
|
|
12
|
+
} else if (platform === 'win32') {
|
|
13
|
+
const { uninstallWindows } = await import('../platform/windows.js');
|
|
14
|
+
await uninstallWindows();
|
|
15
|
+
} else {
|
|
16
|
+
console.error(`Unsupported platform: ${platform}`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -22,10 +22,6 @@ export function main(): void {
|
|
|
22
22
|
const token = requireEnv('AGENT_TOKEN');
|
|
23
23
|
const agentId = requireEnv('AGENT_ID');
|
|
24
24
|
const backendUrl = requireEnv('BACKEND_INTERNAL_URL');
|
|
25
|
-
// Read for backward compatibility but no longer used directly
|
|
26
|
-
void process.env.CENTRIFUGO_INTERNAL_URL;
|
|
27
|
-
void process.env.CENTRIFUGO_API_KEY;
|
|
28
|
-
|
|
29
25
|
if (DEBUG) console.log('[debug] Debug mode enabled');
|
|
30
26
|
console.log(`Starting agent-controller for agent: ${agentId}`);
|
|
31
27
|
console.log(`Backend URL: ${backendUrl} (JWT mode)`);
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
export interface PlatformConfig {
|
|
7
|
+
centrifugoUrl: string;
|
|
8
|
+
agentToken: string;
|
|
9
|
+
agentId: string;
|
|
10
|
+
backendInternalUrl: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function execAsync(cmd: string): Promise<string> {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
exec(cmd, (err, stdout) => {
|
|
16
|
+
if (err) reject(err);
|
|
17
|
+
else resolve(stdout.toString().trim());
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function which(bin: string): Promise<string> {
|
|
23
|
+
try {
|
|
24
|
+
return await execAsync(`which "${bin}"`);
|
|
25
|
+
} catch {
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function installLinux(config: PlatformConfig): Promise<void> {
|
|
31
|
+
const home = homedir();
|
|
32
|
+
const systemdDir = join(home, '.config', 'systemd', 'user');
|
|
33
|
+
const servicePath = join(systemdDir, 'agent-controller.service');
|
|
34
|
+
|
|
35
|
+
const nodePath = process.execPath;
|
|
36
|
+
let agentControllerPath = await which('agent-controller');
|
|
37
|
+
if (!agentControllerPath) {
|
|
38
|
+
agentControllerPath = join(nodePath, '..', 'agent-controller');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
mkdirSync(systemdDir, { recursive: true });
|
|
42
|
+
|
|
43
|
+
const service = `[Unit]
|
|
44
|
+
Description=OpenClaw Agent Controller
|
|
45
|
+
After=network.target
|
|
46
|
+
|
|
47
|
+
[Service]
|
|
48
|
+
Type=simple
|
|
49
|
+
Restart=always
|
|
50
|
+
RestartSec=10
|
|
51
|
+
Environment=CENTRIFUGO_URL=${config.centrifugoUrl}
|
|
52
|
+
Environment=AGENT_TOKEN=${config.agentToken}
|
|
53
|
+
Environment=AGENT_ID=${config.agentId}
|
|
54
|
+
Environment=BACKEND_INTERNAL_URL=${config.backendInternalUrl}
|
|
55
|
+
ExecStart="${nodePath}" "${agentControllerPath}"
|
|
56
|
+
|
|
57
|
+
[Install]
|
|
58
|
+
WantedBy=default.target
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
writeFileSync(servicePath, service, 'utf8');
|
|
62
|
+
console.log(`Written: ${servicePath}`);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
await execAsync('systemctl --user daemon-reload');
|
|
66
|
+
console.log('systemd daemon reloaded.');
|
|
67
|
+
await execAsync('systemctl --user enable --now agent-controller');
|
|
68
|
+
console.log('Service enabled and started.');
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.warn('systemctl command failed:', err instanceof Error ? err.message : err);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log('');
|
|
74
|
+
console.log('Agent Controller installed as a systemd user service.');
|
|
75
|
+
console.log('');
|
|
76
|
+
console.log('Useful commands:');
|
|
77
|
+
console.log(' Start: systemctl --user start agent-controller');
|
|
78
|
+
console.log(' Stop: systemctl --user stop agent-controller');
|
|
79
|
+
console.log(' Status: systemctl --user status agent-controller');
|
|
80
|
+
console.log(' Logs: journalctl --user -u agent-controller -f');
|
|
81
|
+
console.log(' Uninstall: agent-controller uninstall');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function uninstallLinux(): Promise<void> {
|
|
85
|
+
try {
|
|
86
|
+
await execAsync('systemctl --user disable --now agent-controller');
|
|
87
|
+
console.log('Service disabled and stopped.');
|
|
88
|
+
} catch (err) {
|
|
89
|
+
console.warn('systemctl disable failed (may not be enabled):', err instanceof Error ? err.message : err);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const servicePath = join(homedir(), '.config', 'systemd', 'user', 'agent-controller.service');
|
|
93
|
+
const { unlinkSync, existsSync } = await import('node:fs');
|
|
94
|
+
if (existsSync(servicePath)) {
|
|
95
|
+
unlinkSync(servicePath);
|
|
96
|
+
console.log(`Removed: ${servicePath}`);
|
|
97
|
+
} else {
|
|
98
|
+
console.log(`Service file not found: ${servicePath}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await execAsync('systemctl --user daemon-reload');
|
|
103
|
+
} catch {
|
|
104
|
+
// best-effort
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
console.log('Agent Controller uninstalled.');
|
|
108
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
export interface PlatformConfig {
|
|
7
|
+
centrifugoUrl: string;
|
|
8
|
+
agentToken: string;
|
|
9
|
+
agentId: string;
|
|
10
|
+
backendInternalUrl: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function execAsync(cmd: string): Promise<string> {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
exec(cmd, (err, stdout) => {
|
|
16
|
+
if (err) reject(err);
|
|
17
|
+
else resolve(stdout.toString().trim());
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function which(bin: string): Promise<string> {
|
|
23
|
+
try {
|
|
24
|
+
return await execAsync(`which "${bin}"`);
|
|
25
|
+
} catch {
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function installMacOS(config: PlatformConfig): Promise<void> {
|
|
31
|
+
const home = homedir();
|
|
32
|
+
const launchAgentsDir = join(home, 'Library', 'LaunchAgents');
|
|
33
|
+
const plistPath = join(launchAgentsDir, 'com.openclaw.agent-controller.plist');
|
|
34
|
+
const logDir = join(home, 'Library', 'Logs');
|
|
35
|
+
const logFile = join(logDir, 'agent-controller.log');
|
|
36
|
+
const errFile = join(logDir, 'agent-controller.err');
|
|
37
|
+
|
|
38
|
+
const nodePath = process.execPath;
|
|
39
|
+
let agentControllerPath = await which('agent-controller');
|
|
40
|
+
if (!agentControllerPath) {
|
|
41
|
+
// Fallback: derive from node path
|
|
42
|
+
agentControllerPath = join(nodePath, '..', 'agent-controller');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
mkdirSync(launchAgentsDir, { recursive: true });
|
|
46
|
+
|
|
47
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
48
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
49
|
+
<plist version="1.0">
|
|
50
|
+
<dict>
|
|
51
|
+
<key>Label</key>
|
|
52
|
+
<string>com.openclaw.agent-controller</string>
|
|
53
|
+
<key>ProgramArguments</key>
|
|
54
|
+
<array>
|
|
55
|
+
<string>${nodePath}</string>
|
|
56
|
+
<string>${agentControllerPath}</string>
|
|
57
|
+
</array>
|
|
58
|
+
<key>EnvironmentVariables</key>
|
|
59
|
+
<dict>
|
|
60
|
+
<key>CENTRIFUGO_URL</key>
|
|
61
|
+
<string>${config.centrifugoUrl}</string>
|
|
62
|
+
<key>AGENT_TOKEN</key>
|
|
63
|
+
<string>${config.agentToken}</string>
|
|
64
|
+
<key>AGENT_ID</key>
|
|
65
|
+
<string>${config.agentId}</string>
|
|
66
|
+
<key>BACKEND_INTERNAL_URL</key>
|
|
67
|
+
<string>${config.backendInternalUrl}</string>
|
|
68
|
+
</dict>
|
|
69
|
+
<key>RunAtLoad</key>
|
|
70
|
+
<true/>
|
|
71
|
+
<key>KeepAlive</key>
|
|
72
|
+
<true/>
|
|
73
|
+
<key>StandardOutPath</key>
|
|
74
|
+
<string>${logFile}</string>
|
|
75
|
+
<key>StandardErrorPath</key>
|
|
76
|
+
<string>${errFile}</string>
|
|
77
|
+
</dict>
|
|
78
|
+
</plist>
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
writeFileSync(plistPath, plist, 'utf8');
|
|
82
|
+
console.log(`Written: ${plistPath}`);
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
await execAsync(`launchctl load -w "${plistPath}"`);
|
|
86
|
+
console.log('Service loaded via launchctl.');
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.warn('launchctl load failed (may already be loaded):', err instanceof Error ? err.message : err);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log('');
|
|
92
|
+
console.log('Agent Controller installed as a macOS LaunchAgent.');
|
|
93
|
+
console.log('');
|
|
94
|
+
console.log('Useful commands:');
|
|
95
|
+
console.log(` Start: launchctl load -w "${plistPath}"`);
|
|
96
|
+
console.log(` Stop: launchctl unload "${plistPath}"`);
|
|
97
|
+
console.log(` Logs: tail -f "${logFile}"`);
|
|
98
|
+
console.log(` Errors: tail -f "${errFile}"`);
|
|
99
|
+
console.log(` Uninstall: agent-controller uninstall`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function uninstallMacOS(): Promise<void> {
|
|
103
|
+
const home = homedir();
|
|
104
|
+
const plistPath = join(home, 'Library', 'LaunchAgents', 'com.openclaw.agent-controller.plist');
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
await execAsync(`launchctl unload "${plistPath}"`);
|
|
108
|
+
console.log('Service unloaded via launchctl.');
|
|
109
|
+
} catch (err) {
|
|
110
|
+
console.warn('launchctl unload failed (may not be loaded):', err instanceof Error ? err.message : err);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const { unlinkSync, existsSync } = await import('node:fs');
|
|
114
|
+
if (existsSync(plistPath)) {
|
|
115
|
+
unlinkSync(plistPath);
|
|
116
|
+
console.log(`Removed: ${plistPath}`);
|
|
117
|
+
} else {
|
|
118
|
+
console.log(`Plist not found: ${plistPath}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log('Agent Controller uninstalled.');
|
|
122
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
export interface PlatformConfig {
|
|
6
|
+
centrifugoUrl: string;
|
|
7
|
+
agentToken: string;
|
|
8
|
+
agentId: string;
|
|
9
|
+
backendInternalUrl: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function execAsync(cmd: string): Promise<string> {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
exec(cmd, (err, stdout) => {
|
|
15
|
+
if (err) reject(err);
|
|
16
|
+
else resolve(stdout.toString().trim());
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function which(bin: string): Promise<string> {
|
|
22
|
+
try {
|
|
23
|
+
return await execAsync(`where "${bin}"`);
|
|
24
|
+
} catch {
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function installWindows(config: PlatformConfig): Promise<void> {
|
|
30
|
+
const appData = process.env['APPDATA'] ?? join(process.env['USERPROFILE'] ?? 'C:\\Users\\Default', 'AppData', 'Roaming');
|
|
31
|
+
const openclawDir = join(appData, 'openclaw');
|
|
32
|
+
const wrapperPath = join(openclawDir, 'agent-controller.cmd');
|
|
33
|
+
|
|
34
|
+
const nodePath = process.execPath;
|
|
35
|
+
let agentControllerPath = await which('agent-controller');
|
|
36
|
+
if (!agentControllerPath) {
|
|
37
|
+
agentControllerPath = join(nodePath, '..', 'agent-controller');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
mkdirSync(openclawDir, { recursive: true });
|
|
41
|
+
|
|
42
|
+
const wrapper = `@echo off
|
|
43
|
+
set CENTRIFUGO_URL=${config.centrifugoUrl}
|
|
44
|
+
set AGENT_TOKEN=${config.agentToken}
|
|
45
|
+
set AGENT_ID=${config.agentId}
|
|
46
|
+
set BACKEND_INTERNAL_URL=${config.backendInternalUrl}
|
|
47
|
+
"${nodePath}" "${agentControllerPath}"
|
|
48
|
+
`;
|
|
49
|
+
|
|
50
|
+
writeFileSync(wrapperPath, wrapper, 'utf8');
|
|
51
|
+
console.log(`Written: ${wrapperPath}`);
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
await execAsync(
|
|
55
|
+
`schtasks /create /tn "OpenClaw Agent Controller" /tr "${wrapperPath}" /sc ONLOGON /rl HIGHEST /f`
|
|
56
|
+
);
|
|
57
|
+
console.log('Scheduled task created.');
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.warn('schtasks create failed:', err instanceof Error ? err.message : err);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log('');
|
|
63
|
+
console.log('Agent Controller installed as a Windows scheduled task (ONLOGON).');
|
|
64
|
+
console.log('');
|
|
65
|
+
console.log('Useful commands:');
|
|
66
|
+
console.log(' Start: schtasks /run /tn "OpenClaw Agent Controller"');
|
|
67
|
+
console.log(' Stop: schtasks /end /tn "OpenClaw Agent Controller"');
|
|
68
|
+
console.log(' Uninstall: agent-controller uninstall');
|
|
69
|
+
console.log(` Wrapper: ${wrapperPath}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function uninstallWindows(): Promise<void> {
|
|
73
|
+
try {
|
|
74
|
+
await execAsync('schtasks /delete /tn "OpenClaw Agent Controller" /f');
|
|
75
|
+
console.log('Scheduled task deleted.');
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.warn('schtasks delete failed (may not exist):', err instanceof Error ? err.message : err);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const appData = process.env['APPDATA'] ?? join(process.env['USERPROFILE'] ?? 'C:\\Users\\Default', 'AppData', 'Roaming');
|
|
81
|
+
const wrapperPath = join(appData, 'openclaw', 'agent-controller.cmd');
|
|
82
|
+
|
|
83
|
+
const { unlinkSync, existsSync } = await import('node:fs');
|
|
84
|
+
if (existsSync(wrapperPath)) {
|
|
85
|
+
unlinkSync(wrapperPath);
|
|
86
|
+
console.log(`Removed: ${wrapperPath}`);
|
|
87
|
+
} else {
|
|
88
|
+
console.log(`Wrapper not found: ${wrapperPath}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log('Agent Controller uninstalled.');
|
|
92
|
+
}
|