macroclaw 0.42.0 → 0.43.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/system-service.test.ts +29 -4
- package/src/system-service.ts +17 -11
package/package.json
CHANGED
|
@@ -10,6 +10,7 @@ const existsSync = realExistsSync;
|
|
|
10
10
|
const DEFAULT_LOGIN_PATH = "/usr/local/bin:/usr/bin:/bin";
|
|
11
11
|
const DEFAULT_BUN_GLOBAL_BIN = "/home/testuser/.bun/bin";
|
|
12
12
|
const DEFAULT_SERVICE_PATH = `${DEFAULT_BUN_GLOBAL_BIN}:${DEFAULT_LOGIN_PATH}`;
|
|
13
|
+
const DEFAULT_EXECUTABLE_PATH = `${DEFAULT_BUN_GLOBAL_BIN}/macroclaw`;
|
|
13
14
|
const mockExecSync = mock((cmd: string, _opts?: object): string => {
|
|
14
15
|
if (cmd === "/bin/bash -lc 'printf %s \"$PATH\"'") return `${DEFAULT_LOGIN_PATH}\n`;
|
|
15
16
|
if (cmd === "bun pm bin -g") return `${DEFAULT_BUN_GLOBAL_BIN}\n`;
|
|
@@ -236,7 +237,7 @@ describe("install", () => {
|
|
|
236
237
|
const plistPath = join(plistDir, "com.macroclaw.plist");
|
|
237
238
|
expect(existsSync(plistPath)).toBe(true);
|
|
238
239
|
const writtenContent = readFileSync(plistPath, "utf-8");
|
|
239
|
-
expect(writtenContent).toContain(
|
|
240
|
+
expect(writtenContent).toContain(`<string>${DEFAULT_EXECUTABLE_PATH}</string>`);
|
|
240
241
|
expect(writtenContent).toContain("<string>start</string>");
|
|
241
242
|
expect(writtenContent).toContain("<key>KeepAlive</key>");
|
|
242
243
|
expect(writtenContent).toContain(".macroclaw/logs/stdout.log");
|
|
@@ -368,7 +369,7 @@ describe("install", () => {
|
|
|
368
369
|
expect(unitContent).not.toContain("Environment=HOME=");
|
|
369
370
|
expect(unitContent).toContain(`Environment=PATH=${DEFAULT_SERVICE_PATH}`);
|
|
370
371
|
expect(unitContent).toContain("WorkingDirectory=%h");
|
|
371
|
-
expect(unitContent).toContain(
|
|
372
|
+
expect(unitContent).toContain(`ExecStart=${DEFAULT_EXECUTABLE_PATH} start`);
|
|
372
373
|
|
|
373
374
|
// Lingering enabled via sudo
|
|
374
375
|
expect(mockExecSync).toHaveBeenCalledWith("sudo loginctl enable-linger testuser", expect.anything());
|
|
@@ -801,6 +802,7 @@ describe("refresh", () => {
|
|
|
801
802
|
expect(mockExecSync).toHaveBeenCalledWith("/bin/bash -lc 'printf %s \"$PATH\"'", expect.anything());
|
|
802
803
|
expect(mockExecSync).toHaveBeenCalledWith("bun pm bin -g", expect.anything());
|
|
803
804
|
expect(plist).toContain("<string>/home/testuser/.bun/bin:/custom/bin:/usr/bin:/bin</string>");
|
|
805
|
+
expect(plist).toContain("<string>/home/testuser/.bun/bin/macroclaw</string>");
|
|
804
806
|
expect(plist).toContain("<key>CLAUDE_CODE_OAUTH_TOKEN</key>");
|
|
805
807
|
expect(plist).toContain("<string>sk-test-token</string>");
|
|
806
808
|
rmSync(tmpHome, { recursive: true });
|
|
@@ -826,6 +828,7 @@ describe("refresh", () => {
|
|
|
826
828
|
expect(mockExecSync).toHaveBeenCalledWith("bun pm bin -g", expect.anything());
|
|
827
829
|
expect(mockExecSync).toHaveBeenCalledWith("systemctl --user daemon-reload", expect.anything());
|
|
828
830
|
expect(unitContent).toContain("Environment=PATH=/home/testuser/.bun/bin:/custom/bin:/usr/bin:/bin");
|
|
831
|
+
expect(unitContent).toContain("ExecStart=/home/testuser/.bun/bin/macroclaw start");
|
|
829
832
|
rmSync(tmpHome, { recursive: true });
|
|
830
833
|
});
|
|
831
834
|
|
|
@@ -845,8 +848,30 @@ describe("refresh", () => {
|
|
|
845
848
|
mgr.refresh();
|
|
846
849
|
const unitContent = readFileSync(join(unitDir, "macroclaw.service"), "utf-8");
|
|
847
850
|
|
|
848
|
-
expect(unitContent).toContain("Environment=PATH=/
|
|
849
|
-
expect(unitContent).not.toContain("Environment=PATH=/
|
|
851
|
+
expect(unitContent).toContain("Environment=PATH=/home/testuser/.bun/bin:/usr/local/bin:/usr/bin:/bin");
|
|
852
|
+
expect(unitContent).not.toContain("Environment=PATH=/usr/local/bin:/home/testuser/.bun/bin:/usr/local/bin:/usr/bin:/bin");
|
|
853
|
+
rmSync(tmpHome, { recursive: true });
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
it("dedupes repeated PATH entries while keeping bun global bin first", () => {
|
|
857
|
+
const tmpHome = `/tmp/macroclaw-test-refresh-systemd-path-dedup-${Date.now()}`;
|
|
858
|
+
const unitDir = join(tmpHome, ".config/systemd/user");
|
|
859
|
+
mkdirSync(unitDir, { recursive: true });
|
|
860
|
+
writeFileSync(join(unitDir, "macroclaw.service"), "test");
|
|
861
|
+
|
|
862
|
+
mockExecSync.mockImplementation((cmd: string) => {
|
|
863
|
+
if (cmd === "/bin/bash -lc 'printf %s \"$PATH\"'") {
|
|
864
|
+
return "/home/testuser/.bun/bin:/usr/local/bin:/usr/bin:/usr/local/bin:/usr/bin:/bin:/home/testuser/.bun/bin\n";
|
|
865
|
+
}
|
|
866
|
+
if (cmd === "bun pm bin -g") return "/home/testuser/.bun/bin\n";
|
|
867
|
+
return "";
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
const mgr = createManager({ platform: "linux", home: tmpHome });
|
|
871
|
+
mgr.refresh();
|
|
872
|
+
const unitContent = readFileSync(join(unitDir, "macroclaw.service"), "utf-8");
|
|
873
|
+
|
|
874
|
+
expect(unitContent).toContain("Environment=PATH=/home/testuser/.bun/bin:/usr/local/bin:/usr/bin:/bin");
|
|
850
875
|
rmSync(tmpHome, { recursive: true });
|
|
851
876
|
});
|
|
852
877
|
});
|
package/src/system-service.ts
CHANGED
|
@@ -87,6 +87,7 @@ export class SystemServiceManager {
|
|
|
87
87
|
|
|
88
88
|
this.#exec("bun install -g macroclaw");
|
|
89
89
|
const servicePath = this.#getServicePath();
|
|
90
|
+
const executablePath = this.#getMacroclawExecutablePath();
|
|
90
91
|
|
|
91
92
|
const logDir = resolve(this.#home, ".macroclaw/logs");
|
|
92
93
|
mkdirSync(logDir, { recursive: true });
|
|
@@ -94,7 +95,7 @@ export class SystemServiceManager {
|
|
|
94
95
|
this.#exec(`launchctl unload ${this.serviceFilePath}`);
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
writeFileSync(this.serviceFilePath, this.#generateLaunchdPlist(servicePath, oauthToken));
|
|
98
|
+
writeFileSync(this.serviceFilePath, this.#generateLaunchdPlist(servicePath, executablePath, oauthToken));
|
|
98
99
|
log.debug({ filePath: this.serviceFilePath }, "Wrote launchd plist");
|
|
99
100
|
this.#exec(`launchctl load ${this.serviceFilePath}`);
|
|
100
101
|
}
|
|
@@ -111,6 +112,7 @@ export class SystemServiceManager {
|
|
|
111
112
|
|
|
112
113
|
this.#exec("bun install -g macroclaw");
|
|
113
114
|
const servicePath = this.#getServicePath();
|
|
115
|
+
const executablePath = this.#getMacroclawExecutablePath();
|
|
114
116
|
|
|
115
117
|
// Enable lingering so user services run without an active login session
|
|
116
118
|
const username = osUserInfo().username;
|
|
@@ -118,7 +120,7 @@ export class SystemServiceManager {
|
|
|
118
120
|
this.#sudo(`loginctl enable-linger ${username}`);
|
|
119
121
|
}
|
|
120
122
|
|
|
121
|
-
const unitContent = this.#generateSystemdUnit(servicePath);
|
|
123
|
+
const unitContent = this.#generateSystemdUnit(servicePath, executablePath);
|
|
122
124
|
mkdirSync(dirname(this.serviceFilePath), { recursive: true });
|
|
123
125
|
writeFileSync(this.serviceFilePath, unitContent);
|
|
124
126
|
log.debug({ filePath: this.serviceFilePath }, "Wrote systemd unit");
|
|
@@ -211,13 +213,14 @@ export class SystemServiceManager {
|
|
|
211
213
|
refresh(): void {
|
|
212
214
|
this.#requireInstalled();
|
|
213
215
|
const servicePath = this.#getServicePath();
|
|
216
|
+
const executablePath = this.#getMacroclawExecutablePath();
|
|
214
217
|
if (this.#platform === "systemd") {
|
|
215
|
-
writeFileSync(this.serviceFilePath, this.#generateSystemdUnit(servicePath));
|
|
218
|
+
writeFileSync(this.serviceFilePath, this.#generateSystemdUnit(servicePath, executablePath));
|
|
216
219
|
this.#exec("systemctl --user daemon-reload");
|
|
217
220
|
return;
|
|
218
221
|
}
|
|
219
222
|
const oauthToken = this.#getLaunchdOauthToken();
|
|
220
|
-
writeFileSync(this.serviceFilePath, this.#generateLaunchdPlist(servicePath, oauthToken));
|
|
223
|
+
writeFileSync(this.serviceFilePath, this.#generateLaunchdPlist(servicePath, executablePath, oauthToken));
|
|
221
224
|
}
|
|
222
225
|
|
|
223
226
|
status(): ServiceStatus {
|
|
@@ -271,9 +274,12 @@ export class SystemServiceManager {
|
|
|
271
274
|
#getServicePath(): string {
|
|
272
275
|
const shellPath = this.#getLoginShellPath();
|
|
273
276
|
const bunGlobalBin = this.#exec("bun pm bin -g").trim();
|
|
274
|
-
const entries = shellPath.split(":").filter(Boolean);
|
|
275
|
-
|
|
276
|
-
|
|
277
|
+
const entries = [bunGlobalBin, ...shellPath.split(":").filter(Boolean)];
|
|
278
|
+
return [...new Set(entries)].join(":");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
#getMacroclawExecutablePath(): string {
|
|
282
|
+
return resolve(this.#exec("bun pm bin -g").trim(), "macroclaw");
|
|
277
283
|
}
|
|
278
284
|
|
|
279
285
|
#getLaunchdOauthToken(): string | undefined {
|
|
@@ -311,7 +317,7 @@ export class SystemServiceManager {
|
|
|
311
317
|
this.#exec(`sudo ${cmd}`);
|
|
312
318
|
}
|
|
313
319
|
|
|
314
|
-
#generateLaunchdPlist(servicePath: string, oauthToken?: string): string {
|
|
320
|
+
#generateLaunchdPlist(servicePath: string, executablePath: string, oauthToken?: string): string {
|
|
315
321
|
const logDir = resolve(this.#home, ".macroclaw/logs");
|
|
316
322
|
const envVars = [`\n\t<key>PATH</key>\n\t\t<string>${servicePath}</string>`];
|
|
317
323
|
if (oauthToken) {
|
|
@@ -326,7 +332,7 @@ export class SystemServiceManager {
|
|
|
326
332
|
<string>com.macroclaw</string>
|
|
327
333
|
<key>ProgramArguments</key>
|
|
328
334
|
<array>
|
|
329
|
-
<string
|
|
335
|
+
<string>${executablePath}</string>
|
|
330
336
|
<string>start</string>
|
|
331
337
|
</array>
|
|
332
338
|
<key>KeepAlive</key>
|
|
@@ -340,7 +346,7 @@ export class SystemServiceManager {
|
|
|
340
346
|
`;
|
|
341
347
|
}
|
|
342
348
|
|
|
343
|
-
#generateSystemdUnit(servicePath: string): string {
|
|
349
|
+
#generateSystemdUnit(servicePath: string, executablePath: string): string {
|
|
344
350
|
return `[Unit]
|
|
345
351
|
Description=Macroclaw - Telegram-to-Claude-Code bridge
|
|
346
352
|
After=network.target
|
|
@@ -349,7 +355,7 @@ After=network.target
|
|
|
349
355
|
Type=simple
|
|
350
356
|
WorkingDirectory=%h
|
|
351
357
|
Environment=PATH=${servicePath}
|
|
352
|
-
ExecStart
|
|
358
|
+
ExecStart=${executablePath} start
|
|
353
359
|
Restart=on-failure
|
|
354
360
|
RestartSec=5
|
|
355
361
|
|