macroclaw 0.14.0 → 0.15.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "macroclaw",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Telegram-to-Claude-Code bridge",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/cli.test.ts CHANGED
@@ -115,7 +115,8 @@ function mockService(overrides?: Record<string, unknown>): SystemServiceManager
115
115
  uninstall: mock(() => {}),
116
116
  start: mock(() => ""),
117
117
  stop: mock(() => {}),
118
- update: mock(() => ""),
118
+ update: mock(() => ({ previousVersion: "0.6.0", currentVersion: "0.7.0" })),
119
+ isRunning: false,
119
120
  status: mock(() => ({ installed: false, running: false, platform: "systemd" as const })),
120
121
  logs: mock(() => "journalctl -u macroclaw -n 50 --no-pager"),
121
122
  ...overrides,
@@ -151,11 +152,26 @@ describe("Cli.service", () => {
151
152
  expect(stop).toHaveBeenCalled();
152
153
  });
153
154
 
154
- it("runs update action", () => {
155
- const update = mock(() => "tail -f /logs");
156
- const cli = new Cli({ systemService: mockService({ update }) });
155
+ it("runs update action — stops and starts when running", () => {
156
+ const stop = mock(() => {});
157
+ const start = mock(() => "tail -f /logs");
158
+ const update = mock(() => ({ previousVersion: "0.6.0", currentVersion: "0.7.0" }));
159
+ const cli = new Cli({ systemService: mockService({ stop, start, update, isRunning: true }) });
157
160
  cli.service("update");
161
+ expect(stop).toHaveBeenCalled();
158
162
  expect(update).toHaveBeenCalled();
163
+ expect(start).toHaveBeenCalled();
164
+ });
165
+
166
+ it("runs update action — skips stop but still starts when not running", () => {
167
+ const stop = mock(() => {});
168
+ const start = mock(() => "tail -f /logs");
169
+ const update = mock(() => ({ previousVersion: "0.6.0", currentVersion: "0.7.0" }));
170
+ const cli = new Cli({ systemService: mockService({ stop, start, update, isRunning: false }) });
171
+ cli.service("update");
172
+ expect(stop).not.toHaveBeenCalled();
173
+ expect(update).toHaveBeenCalled();
174
+ expect(start).toHaveBeenCalled();
159
175
  });
160
176
 
161
177
  it("runs status action", () => {
package/src/cli.ts CHANGED
@@ -55,8 +55,19 @@ export class Cli {
55
55
  console.log("Service stopped.");
56
56
  break;
57
57
  case "update": {
58
- const logCmd = this.#serviceManager.update();
59
- console.log(`Service updated. Check logs:\n ${logCmd}`);
58
+ if (this.#serviceManager.isRunning) {
59
+ this.#serviceManager.stop();
60
+ console.log("Service stopped.");
61
+ }
62
+ const result = this.#serviceManager.update();
63
+ if (result.previousVersion === result.currentVersion) {
64
+ console.log(`macroclaw v${result.currentVersion} (already up to date)`);
65
+ } else {
66
+ console.log(`Updated macroclaw v${result.previousVersion} → v${result.currentVersion}`);
67
+ }
68
+
69
+ const logCmd = this.#serviceManager.start();
70
+ console.log(`Service started. Check logs:\n ${logCmd}`);
60
71
  break;
61
72
  }
62
73
  case "status": {
@@ -600,23 +600,58 @@ describe("update", () => {
600
600
  );
601
601
  });
602
602
 
603
- it("updates launchd without sudo", () => {
603
+ it("runs bun install without stop/start", () => {
604
604
  const tmpHome = `/tmp/macroclaw-test-updateld-${Date.now()}`;
605
605
  mkdirSync(join(tmpHome, "Library/LaunchAgents"), { recursive: true });
606
606
  writeFileSync(join(tmpHome, "Library/LaunchAgents/com.macroclaw.plist"), "test");
607
607
 
608
608
  mockExecSync.mockImplementation((cmd: string) => {
609
- if (cmd.startsWith("launchctl list ")) return LAUNCHD_RUNNING;
609
+ if (cmd === "bun pm ls -g") return "macroclaw@0.6.0\n";
610
610
  return "";
611
611
  });
612
612
  const mgr = createManager({ platform: "darwin", home: tmpHome });
613
- mgr.update();
614
- expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining("launchctl unload"), expect.anything());
613
+ const result = mgr.update();
615
614
  expect(mockExecSync).toHaveBeenCalledWith("bun install -g macroclaw@latest", expect.anything());
616
- expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining("launchctl load"), expect.anything());
617
- for (const call of mockExecSync.mock.calls) {
618
- expect(call[0]).not.toMatch(/^sudo /);
619
- }
615
+ expect(mockExecSync).not.toHaveBeenCalledWith(expect.stringContaining("launchctl"), expect.anything());
616
+ expect(mockExecSync).not.toHaveBeenCalledWith(expect.stringContaining("systemctl"), expect.anything());
617
+ expect(result.previousVersion).toBe("0.6.0");
618
+ expect(result.currentVersion).toBe("0.6.0");
619
+ rmSync(tmpHome, { recursive: true });
620
+ });
621
+
622
+ it("returns different versions when update changes version", () => {
623
+ const tmpHome = `/tmp/macroclaw-test-updatever-${Date.now()}`;
624
+ mkdirSync(join(tmpHome, "Library/LaunchAgents"), { recursive: true });
625
+ writeFileSync(join(tmpHome, "Library/LaunchAgents/com.macroclaw.plist"), "test");
626
+
627
+ let installCalled = false;
628
+ mockExecSync.mockImplementation((cmd: string) => {
629
+ if (cmd.startsWith("launchctl list ")) return LAUNCHD_STOPPED;
630
+ if (cmd === "bun install -g macroclaw@latest") { installCalled = true; return ""; }
631
+ if (cmd === "bun pm ls -g") return installCalled ? "macroclaw@0.7.0\n" : "macroclaw@0.6.0\n";
632
+ return "";
633
+ });
634
+ const mgr = createManager({ platform: "darwin", home: tmpHome });
635
+ const result = mgr.update();
636
+ expect(result.previousVersion).toBe("0.6.0");
637
+ expect(result.currentVersion).toBe("0.7.0");
638
+ rmSync(tmpHome, { recursive: true });
639
+ });
640
+
641
+ it("returns unknown when version query fails", () => {
642
+ const tmpHome = `/tmp/macroclaw-test-updateunk-${Date.now()}`;
643
+ mkdirSync(join(tmpHome, "Library/LaunchAgents"), { recursive: true });
644
+ writeFileSync(join(tmpHome, "Library/LaunchAgents/com.macroclaw.plist"), "test");
645
+
646
+ mockExecSync.mockImplementation((cmd: string) => {
647
+ if (cmd.startsWith("launchctl list ")) return LAUNCHD_STOPPED;
648
+ if (cmd === "bun pm ls -g") throw new Error("command not found");
649
+ return "";
650
+ });
651
+ const mgr = createManager({ platform: "darwin", home: tmpHome });
652
+ const result = mgr.update();
653
+ expect(result.previousVersion).toBe("unknown");
654
+ expect(result.currentVersion).toBe("unknown");
620
655
  rmSync(tmpHome, { recursive: true });
621
656
  });
622
657
  });
@@ -24,6 +24,11 @@ export interface ServiceStatus {
24
24
  uptime?: string;
25
25
  }
26
26
 
27
+ export interface UpdateResult {
28
+ previousVersion: string;
29
+ currentVersion: string;
30
+ }
31
+
27
32
  export class SystemServiceManager {
28
33
  readonly #platform: Platform;
29
34
  readonly #home: string;
@@ -179,25 +184,15 @@ export class SystemServiceManager {
179
184
  log.debug("Service stopped");
180
185
  }
181
186
 
182
- update(): string {
187
+ update(): UpdateResult {
183
188
  this.#requireInstalled();
184
189
 
185
- if (this.#platform === "launchd") {
186
- if (this.isRunning) {
187
- this.#exec(`launchctl unload ${this.serviceFilePath}`);
188
- }
189
- this.#exec("bun install -g macroclaw@latest");
190
- this.#exec(`launchctl load ${this.serviceFilePath}`);
191
- } else {
192
- if (this.isRunning) {
193
- this.#sudo("systemctl stop macroclaw");
194
- }
195
- this.#exec("bun install -g macroclaw@latest");
196
- this.#sudo("systemctl start macroclaw");
197
- }
190
+ const previousVersion = this.#getInstalledVersion();
191
+ this.#exec("bun install -g macroclaw@latest");
192
+ const currentVersion = this.#getInstalledVersion();
198
193
 
199
- log.debug("Service updated (reinstalled, restarted)");
200
- return this.#logTailCommand();
194
+ log.debug({ previousVersion, currentVersion }, "Service updated");
195
+ return { previousVersion, currentVersion };
201
196
  }
202
197
 
203
198
  status(): ServiceStatus {
@@ -261,6 +256,17 @@ export class SystemServiceManager {
261
256
  return binPath;
262
257
  }
263
258
 
259
+
260
+ #getInstalledVersion(): string {
261
+ try {
262
+ const output = this.#exec("bun pm ls -g");
263
+ const match = /macroclaw@(\S+)/.exec(output);
264
+ return match?.[1] ?? "unknown";
265
+ } catch {
266
+ return "unknown";
267
+ }
268
+ }
269
+
264
270
  #requireInstalled(): void {
265
271
  if (!this.isInstalled) {
266
272
  throw new Error("Service not installed. Run `macroclaw service install` first.");