macroclaw 0.30.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli.ts +6 -6
- package/src/orchestrator.ts +41 -70
- package/src/prompts.test.ts +102 -162
- package/src/prompts.ts +62 -53
- package/src/setup.ts +1 -1
- package/src/system-service.test.ts +119 -85
- package/src/system-service.ts +35 -59
package/src/system-service.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
2
|
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { userInfo as osUserInfo
|
|
3
|
+
import { userInfo as osUserInfo } from "node:os";
|
|
5
4
|
import { dirname, join, resolve } from "node:path";
|
|
6
5
|
import { createLogger } from "./logger";
|
|
7
6
|
|
|
@@ -10,12 +9,6 @@ const LAUNCHD_LABEL = "com.macroclaw";
|
|
|
10
9
|
|
|
11
10
|
export type Platform = "launchd" | "systemd";
|
|
12
11
|
|
|
13
|
-
interface LinuxUser {
|
|
14
|
-
user: string;
|
|
15
|
-
group: string;
|
|
16
|
-
home: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
12
|
export interface ServiceStatus {
|
|
20
13
|
installed: boolean;
|
|
21
14
|
running: boolean;
|
|
@@ -51,7 +44,7 @@ export class SystemServiceManager {
|
|
|
51
44
|
get serviceFilePath(): string {
|
|
52
45
|
return this.#platform === "launchd"
|
|
53
46
|
? resolve(this.#home, "Library/LaunchAgents/com.macroclaw.plist")
|
|
54
|
-
: "/
|
|
47
|
+
: resolve(this.#home, ".config/systemd/user/macroclaw.service");
|
|
55
48
|
}
|
|
56
49
|
|
|
57
50
|
get isInstalled(): boolean {
|
|
@@ -68,7 +61,7 @@ export class SystemServiceManager {
|
|
|
68
61
|
}
|
|
69
62
|
}
|
|
70
63
|
try {
|
|
71
|
-
const out = this.#exec("systemctl is-active macroclaw");
|
|
64
|
+
const out = this.#exec("systemctl --user is-active macroclaw");
|
|
72
65
|
return out.trim() === "active";
|
|
73
66
|
} catch {
|
|
74
67
|
return false;
|
|
@@ -110,10 +103,13 @@ export class SystemServiceManager {
|
|
|
110
103
|
}
|
|
111
104
|
|
|
112
105
|
#installSystemd(): void {
|
|
113
|
-
const
|
|
106
|
+
const settingsPath = resolve(this.#home, ".macroclaw/settings.json");
|
|
107
|
+
if (!existsSync(settingsPath)) {
|
|
108
|
+
throw new Error("Settings not found. Run `macroclaw setup` first.");
|
|
109
|
+
}
|
|
114
110
|
|
|
115
111
|
if (this.isRunning) {
|
|
116
|
-
this.#
|
|
112
|
+
this.#exec("systemctl --user stop macroclaw");
|
|
117
113
|
}
|
|
118
114
|
|
|
119
115
|
this.#exec("bun install -g macroclaw");
|
|
@@ -123,12 +119,19 @@ export class SystemServiceManager {
|
|
|
123
119
|
|
|
124
120
|
const pathDirs = [...new Set([dirname(bunPath), dirname(claudePath), dirname(macroclawPath)])];
|
|
125
121
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
122
|
+
// Enable lingering so user services run without an active login session
|
|
123
|
+
const username = osUserInfo().username;
|
|
124
|
+
if (!existsSync(`/var/lib/systemd/linger/${username}`)) {
|
|
125
|
+
this.#sudo(`loginctl enable-linger ${username}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const unitContent = this.#generateSystemdUnit(bunPath, macroclawPath, pathDirs);
|
|
129
|
+
mkdirSync(dirname(this.serviceFilePath), { recursive: true });
|
|
130
|
+
writeFileSync(this.serviceFilePath, unitContent);
|
|
131
|
+
log.debug({ filePath: this.serviceFilePath }, "Wrote systemd unit");
|
|
132
|
+
this.#exec("systemctl --user daemon-reload");
|
|
133
|
+
this.#exec("systemctl --user enable macroclaw");
|
|
134
|
+
this.#exec("systemctl --user start macroclaw");
|
|
132
135
|
}
|
|
133
136
|
|
|
134
137
|
uninstall(): void {
|
|
@@ -141,11 +144,11 @@ export class SystemServiceManager {
|
|
|
141
144
|
rmSync(this.serviceFilePath);
|
|
142
145
|
} else {
|
|
143
146
|
if (this.isRunning) {
|
|
144
|
-
this.#
|
|
147
|
+
this.#exec("systemctl --user stop macroclaw");
|
|
145
148
|
}
|
|
146
|
-
try { this.#
|
|
147
|
-
|
|
148
|
-
this.#
|
|
149
|
+
try { this.#exec("systemctl --user disable macroclaw"); } catch { /* already disabled */ }
|
|
150
|
+
rmSync(this.serviceFilePath);
|
|
151
|
+
this.#exec("systemctl --user daemon-reload");
|
|
149
152
|
}
|
|
150
153
|
|
|
151
154
|
log.debug("Service uninstalled");
|
|
@@ -161,7 +164,7 @@ export class SystemServiceManager {
|
|
|
161
164
|
if (this.#platform === "launchd") {
|
|
162
165
|
this.#exec(`launchctl load ${this.serviceFilePath}`);
|
|
163
166
|
} else {
|
|
164
|
-
this.#
|
|
167
|
+
this.#exec("systemctl --user start macroclaw");
|
|
165
168
|
}
|
|
166
169
|
|
|
167
170
|
log.debug("Service started");
|
|
@@ -178,7 +181,7 @@ export class SystemServiceManager {
|
|
|
178
181
|
if (this.#platform === "launchd") {
|
|
179
182
|
this.#exec(`launchctl unload ${this.serviceFilePath}`);
|
|
180
183
|
} else {
|
|
181
|
-
this.#
|
|
184
|
+
this.#exec("systemctl --user stop macroclaw");
|
|
182
185
|
}
|
|
183
186
|
|
|
184
187
|
log.debug("Service stopped");
|
|
@@ -211,7 +214,7 @@ export class SystemServiceManager {
|
|
|
211
214
|
} catch { /* best effort */ }
|
|
212
215
|
} else {
|
|
213
216
|
try {
|
|
214
|
-
const out = this.#exec("systemctl show macroclaw --property=MainPID,ActiveEnterTimestamp --no-pager");
|
|
217
|
+
const out = this.#exec("systemctl --user show macroclaw --property=MainPID,ActiveEnterTimestamp --no-pager");
|
|
215
218
|
const pidMatch = /MainPID=(\d+)/.exec(out);
|
|
216
219
|
if (pidMatch && pidMatch[1] !== "0") result.pid = Number(pidMatch[1]);
|
|
217
220
|
const tsMatch = /ActiveEnterTimestamp=(.+)/.exec(out);
|
|
@@ -231,8 +234,8 @@ export class SystemServiceManager {
|
|
|
231
234
|
: `tail -n 50 ${logDir}/stdout.log`;
|
|
232
235
|
}
|
|
233
236
|
return follow
|
|
234
|
-
? "journalctl -u macroclaw -f"
|
|
235
|
-
: "journalctl -u macroclaw -n 50 --no-pager";
|
|
237
|
+
? "journalctl --user -u macroclaw -f"
|
|
238
|
+
: "journalctl --user -u macroclaw -n 50 --no-pager";
|
|
236
239
|
}
|
|
237
240
|
|
|
238
241
|
#exec(cmd: string): string {
|
|
@@ -278,38 +281,13 @@ export class SystemServiceManager {
|
|
|
278
281
|
const logDir = resolve(this.#home, ".macroclaw/logs");
|
|
279
282
|
return `tail -f ${logDir}/*.log`;
|
|
280
283
|
}
|
|
281
|
-
return "journalctl -u macroclaw -f";
|
|
284
|
+
return "journalctl --user -u macroclaw -f";
|
|
282
285
|
}
|
|
283
286
|
|
|
284
287
|
#sudo(cmd: string): void {
|
|
285
288
|
this.#exec(`sudo ${cmd}`);
|
|
286
289
|
}
|
|
287
290
|
|
|
288
|
-
/** Write unit content to a temp file, then sudo-copy it into /etc/systemd/system/. */
|
|
289
|
-
#writeSystemdUnit(content: string): void {
|
|
290
|
-
const tmpPath = join(tmpdir(), `macroclaw-${randomUUID()}.service`);
|
|
291
|
-
writeFileSync(tmpPath, content);
|
|
292
|
-
try {
|
|
293
|
-
this.#sudo(`cp ${tmpPath} ${this.serviceFilePath}`);
|
|
294
|
-
} finally {
|
|
295
|
-
try { rmSync(tmpPath); } catch { /* best-effort cleanup */ }
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
#resolveLinuxUser(): LinuxUser {
|
|
300
|
-
const info = osUserInfo();
|
|
301
|
-
const user = info.username;
|
|
302
|
-
const home = info.homedir;
|
|
303
|
-
const group = this.#exec(`id -gn ${user}`).trim();
|
|
304
|
-
|
|
305
|
-
const settingsPath = resolve(home, ".macroclaw/settings.json");
|
|
306
|
-
if (!existsSync(settingsPath)) {
|
|
307
|
-
throw new Error("Settings not found. Run `macroclaw setup` first.");
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return { user, group, home };
|
|
311
|
-
}
|
|
312
|
-
|
|
313
291
|
#generateLaunchdPlist(bunPath: string, macroclawPath: string, pathDirs: string[], oauthToken?: string): string {
|
|
314
292
|
const logDir = resolve(this.#home, ".macroclaw/logs");
|
|
315
293
|
const tokenEnv = oauthToken ? `\n\t\t<key>CLAUDE_CODE_OAUTH_TOKEN</key>\n\t\t<string>${oauthToken}</string>` : "";
|
|
@@ -343,24 +321,22 @@ export class SystemServiceManager {
|
|
|
343
321
|
`;
|
|
344
322
|
}
|
|
345
323
|
|
|
346
|
-
#generateSystemdUnit(bunPath: string, macroclawPath: string,
|
|
324
|
+
#generateSystemdUnit(bunPath: string, macroclawPath: string, pathDirs: string[]): string {
|
|
347
325
|
return `[Unit]
|
|
348
326
|
Description=Macroclaw - Telegram-to-Claude-Code bridge
|
|
349
327
|
After=network.target
|
|
350
328
|
|
|
351
329
|
[Service]
|
|
352
330
|
Type=simple
|
|
353
|
-
|
|
354
|
-
Group=${target.group}
|
|
355
|
-
Environment=HOME=${target.home}
|
|
331
|
+
Environment=HOME=${this.#home}
|
|
356
332
|
Environment=PATH=${pathDirs.join(":")}
|
|
357
|
-
WorkingDirectory=${
|
|
333
|
+
WorkingDirectory=${this.#home}
|
|
358
334
|
ExecStart=${bunPath} ${macroclawPath} start
|
|
359
335
|
Restart=on-failure
|
|
360
336
|
RestartSec=5
|
|
361
337
|
|
|
362
338
|
[Install]
|
|
363
|
-
WantedBy=
|
|
339
|
+
WantedBy=default.target
|
|
364
340
|
`;
|
|
365
341
|
}
|
|
366
342
|
}
|