macroclaw 0.35.0 → 0.36.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.35.0",
3
+ "version": "0.36.0",
4
4
  "description": "Telegram-to-Claude-Code bridge",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/cli.test.ts CHANGED
@@ -133,6 +133,7 @@ function mockService(overrides?: Record<string, unknown>): SystemServiceManager
133
133
  uninstall: mock(() => {}),
134
134
  start: mock(() => ""),
135
135
  stop: mock(() => {}),
136
+ restart: mock(() => ""),
136
137
  update: mock(() => ({ previousVersion: "0.6.0", currentVersion: "0.7.0" })),
137
138
  isRunning: false,
138
139
  status: mock(() => ({ installed: false, running: false, platform: "systemd" as const })),
@@ -170,6 +171,13 @@ describe("Cli.service", () => {
170
171
  expect(stop).toHaveBeenCalled();
171
172
  });
172
173
 
174
+ it("runs restart action", () => {
175
+ const restart = mock(() => "tail -f /logs");
176
+ const cli = new Cli({ systemService: mockService({ restart }) });
177
+ cli.service("restart");
178
+ expect(restart).toHaveBeenCalled();
179
+ });
180
+
173
181
  it("runs update action — stops and starts when running", () => {
174
182
  const stop = mock(() => {});
175
183
  const start = mock(() => "tail -f /logs");
package/src/cli.ts CHANGED
@@ -75,6 +75,11 @@ export class Cli {
75
75
  console.log(`Service started. Check logs:\n ${logCmd}`);
76
76
  break;
77
77
  }
78
+ case "restart": {
79
+ const logCmd = this.#serviceManager.restart();
80
+ console.log(`Service restarted. Check logs:\n ${logCmd}`);
81
+ break;
82
+ }
78
83
  case "status": {
79
84
  const s = this.#serviceManager.status();
80
85
  const lines = [
@@ -173,6 +178,11 @@ const serviceStatusCommand = defineCommand({
173
178
  run: () => { try { defaultCli.service("status"); } catch (err) { handleError(err); } },
174
179
  });
175
180
 
181
+ const serviceRestartCommand = defineCommand({
182
+ meta: { name: "restart", description: "Restart the service" },
183
+ run: () => { try { defaultCli.service("restart"); } catch (err) { handleError(err); } },
184
+ });
185
+
176
186
  const serviceLogsCommand = defineCommand({
177
187
  meta: { name: "logs", description: "Print the command to view service logs" },
178
188
  args: {
@@ -188,6 +198,7 @@ const serviceCommand = defineCommand({
188
198
  uninstall: serviceUninstallCommand,
189
199
  start: serviceStartCommand,
190
200
  stop: serviceStopCommand,
201
+ restart: serviceRestartCommand,
191
202
  update: serviceUpdateCommand,
192
203
  status: serviceStatusCommand,
193
204
  logs: serviceLogsCommand,
@@ -641,6 +641,50 @@ describe("stop", () => {
641
641
  });
642
642
  });
643
643
 
644
+ describe("restart", () => {
645
+ it("throws when service is not installed", () => {
646
+ const mgr = createManager({ platform: "darwin", home: "/nonexistent" });
647
+ expect(() => mgr.restart()).toThrow(
648
+ "Service not installed. Run `macroclaw service install` first.",
649
+ );
650
+ });
651
+
652
+ it("stops then starts when running (launchd)", () => {
653
+ const tmpHome = `/tmp/macroclaw-test-restartld-${Date.now()}`;
654
+ mkdirSync(join(tmpHome, "Library/LaunchAgents"), { recursive: true });
655
+ writeFileSync(join(tmpHome, "Library/LaunchAgents/com.macroclaw.plist"), "test");
656
+
657
+ let stopped = false;
658
+ mockExecSync.mockImplementation((cmd: string) => {
659
+ if (cmd.startsWith("launchctl list ")) return stopped ? LAUNCHD_STOPPED : LAUNCHD_RUNNING;
660
+ if (cmd.includes("launchctl unload")) { stopped = true; return ""; }
661
+ return "";
662
+ });
663
+ const mgr = createManager({ platform: "darwin", home: tmpHome });
664
+ mgr.restart();
665
+ expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining("launchctl unload"), expect.anything());
666
+ expect(mockExecSync).toHaveBeenCalledWith(expect.stringContaining("launchctl load"), expect.anything());
667
+ rmSync(tmpHome, { recursive: true });
668
+ });
669
+
670
+ it("skips stop and starts when not running (systemd)", () => {
671
+ const tmpHome = `/tmp/macroclaw-test-restartsys-${Date.now()}`;
672
+ const unitDir = join(tmpHome, ".config/systemd/user");
673
+ mkdirSync(unitDir, { recursive: true });
674
+ writeFileSync(join(unitDir, "macroclaw.service"), "test");
675
+
676
+ mockExecSync.mockImplementation((cmd: string) => {
677
+ if (cmd === "systemctl --user is-active macroclaw") throw new Error("inactive");
678
+ return "";
679
+ });
680
+ const mgr = createManager({ platform: "linux", home: tmpHome });
681
+ mgr.restart();
682
+ expect(mockExecSync).not.toHaveBeenCalledWith("systemctl --user stop macroclaw", expect.anything());
683
+ expect(mockExecSync).toHaveBeenCalledWith("systemctl --user start macroclaw", expect.anything());
684
+ rmSync(tmpHome, { recursive: true });
685
+ });
686
+ });
687
+
644
688
  describe("update", () => {
645
689
  it("throws when service is not installed", () => {
646
690
  const mgr = createManager({ platform: "darwin", home: "/nonexistent" });
@@ -171,6 +171,16 @@ export class SystemServiceManager {
171
171
  return this.#logTailCommand();
172
172
  }
173
173
 
174
+ restart(): string {
175
+ this.#requireInstalled();
176
+
177
+ if (this.isRunning) {
178
+ this.stop();
179
+ }
180
+
181
+ return this.start();
182
+ }
183
+
174
184
  stop(): void {
175
185
  this.#requireInstalled();
176
186
 
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: settings
3
+ description: "Read or change macroclaw settings (model, timezone). Use when the user asks about current settings, wants to switch the Claude model, change the timezone, or asks what model/timezone is configured."
4
+ ---
5
+
6
+ Read or change macroclaw settings. Only `model` and `timezone` can be changed through this skill.
7
+
8
+ ## Settings file
9
+
10
+ Location: `~/.macroclaw/settings.json`
11
+
12
+ ## Reading settings
13
+
14
+ When the user asks about current settings ("what model am I on?", "what's the timezone?"):
15
+
16
+ 1. Read `~/.macroclaw/settings.json`
17
+ 2. Report the requested value
18
+
19
+ ## Changing settings
20
+
21
+ Allowed changes:
22
+ - **model**: `haiku`, `sonnet`, or `opus`
23
+ - **timezone**: any valid IANA timezone (e.g. `Europe/Prague`, `America/New_York`, `UTC`)
24
+
25
+ All other settings (botToken, chatId, workspace, etc.) cannot be changed through this skill — tell the user to run `macroclaw setup` instead.
26
+
27
+ ## Procedure for changing a setting
28
+
29
+ Changing a setting requires a service restart, which kills the current process. Everything must happen in a single response — the same pattern as self-update.
30
+
31
+ 1. **Read current settings**: Read `~/.macroclaw/settings.json` and note the current value.
32
+
33
+ 2. **Validate**: Check the new value is valid (see allowed values above). If invalid, tell the user and stop.
34
+
35
+ 3. **Write**: Update the value in `~/.macroclaw/settings.json`, preserving all other fields. Write the file with `JSON.stringify(settings, null, 2)` formatting.
36
+
37
+ 4. **Generate LOG_FILE_PATH**: Run `echo "/tmp/macroclaw-restart-$(date -u +%Y-%m-%dT%H-%M-%SZ).log"` and use the output as `<LOG_FILE_PATH>` in the following steps.
38
+
39
+ 5. **Schedule follow-up**: Using the `schedule` skill, create a one-shot event 1 minute from now:
40
+ - Name: `settings-check`
41
+ - Prompt: `Check macroclaw restart after settings change. Read ~/.macroclaw/settings.json and confirm <setting>=<new-value>. Read <LOG_FILE_PATH> — if it contains "restarted", the restart succeeded. If the file doesn't exist or is empty, schedule another check in 1 minute. Run macroclaw service status to verify the service is running. Report the result.`
42
+ - Model: `haiku`
43
+ - Recurring: `false`
44
+
45
+ 6. **Run the restart**: Execute the restart script bundled with this skill:
46
+ ```
47
+ bash ${CLAUDE_SKILL_DIR}/scripts/restart.sh <LOG_FILE_PATH>
48
+ ```
49
+
50
+ ## Important
51
+
52
+ - The restart stops the service, which kills all processes including this Claude Code session.
53
+ - Do NOT use a background agent — it gets killed along with the main process.
54
+ - Always run step 6 LAST — everything after it may not execute.
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if [[ $# -ne 1 ]]; then
5
+ echo "Usage: $0 <log-file>" >&2
6
+ exit 1
7
+ fi
8
+
9
+ LOG_FILE="$1"
10
+
11
+ case "$(uname -s)" in
12
+ Linux)
13
+ systemd-run --user \
14
+ --unit="macroclaw-restart-$(date -u +%Y%m%dT%H%M%SZ)" \
15
+ --collect \
16
+ --no-block \
17
+ --setenv="PATH=$PATH" \
18
+ /bin/bash -lc "exec macroclaw service restart > \"$LOG_FILE\" 2>&1"
19
+ ;;
20
+ Darwin)
21
+ nohup setsid /bin/bash -lc "exec macroclaw service restart > \"$LOG_FILE\" 2>&1" >/dev/null 2>&1 &
22
+ ;;
23
+ *)
24
+ echo "Unsupported platform: $(uname -s)" >&2
25
+ exit 1
26
+ ;;
27
+ esac