macroclaw 0.39.0 → 0.40.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/README.md CHANGED
@@ -43,12 +43,12 @@ Settings are stored in `~/.macroclaw/settings.json` and validated on startup.
43
43
  | `chatId` | `AUTHORIZED_CHAT_ID` | — | Yes |
44
44
  | `model` | `MODEL` | `sonnet` | No |
45
45
  | `workspace` | `WORKSPACE` | `~/.macroclaw-workspace` | No |
46
- | `timezone` | `TIMEZONE` | `UTC` | No |
46
+ | `timeZone` | `TIMEZONE` | `UTC` | No |
47
47
  | `openaiApiKey` | `OPENAI_API_KEY` | — | No |
48
48
  | `logLevel` | `LOG_LEVEL` | `info` | No |
49
49
  | `pinoramaUrl` | `PINORAMA_URL` | — | No |
50
50
 
51
- **`timezone`** sets the agent's local timezone (IANA format, e.g. `Europe/Prague`, `America/New_York`). Used for the agent's clock display and scheduled event timing.
51
+ **`timeZone`** sets the agent's local time zone (IANA format, e.g. `Europe/Prague`, `America/New_York`). Used for the agent's clock display and scheduled event timing.
52
52
 
53
53
  **`openaiApiKey`** is used for voice message transcription via [OpenAI Whisper](https://platform.openai.com/docs/guides/speech-to-text). Without it, voice messages are ignored.
54
54
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "macroclaw",
3
- "version": "0.39.0",
3
+ "version": "0.40.0",
4
4
  "description": "Telegram-to-Claude-Code bridge",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/app.test.ts CHANGED
@@ -133,7 +133,7 @@ function makeConfig(overrides?: Partial<AppConfig>): AppConfig {
133
133
  authorizedChatId: "12345",
134
134
  workspace: "/tmp/macroclaw-test-workspace",
135
135
  model: "sonnet",
136
- timezone: "UTC",
136
+ timeZone: "UTC",
137
137
  settingsDir: tmpSettingsDir,
138
138
  claude: defaultMockClaude(),
139
139
  stt: mockStt(),
package/src/app.ts CHANGED
@@ -12,7 +12,7 @@ export interface AppConfig {
12
12
  authorizedChatId: string;
13
13
  workspace: string;
14
14
  model: string;
15
- timezone: string;
15
+ timeZone: string;
16
16
  settingsDir?: string;
17
17
  claude?: Claude;
18
18
  stt?: SpeechToText;
@@ -30,7 +30,7 @@ export class App {
30
30
  this.#orchestrator = new Orchestrator({
31
31
  model: config.model,
32
32
  workspace: config.workspace,
33
- timezone: config.timezone,
33
+ timeZone: config.timeZone,
34
34
  settingsDir: config.settingsDir,
35
35
  claude: config.claude,
36
36
  healthCheckInterval: config.healthCheckInterval,
@@ -51,7 +51,7 @@ export class App {
51
51
  start() {
52
52
  log.info("Starting macroclaw...");
53
53
  const scheduler = new Scheduler(this.#config.workspace, {
54
- timezone: this.#config.timezone,
54
+ timeZone: this.#config.timeZone,
55
55
  onJob: (name, prompt, model, missed) => this.#orchestrator.handleCron(name, prompt, model, missed),
56
56
  });
57
57
  scheduler.start();
package/src/index.ts CHANGED
@@ -40,7 +40,7 @@ export async function start(): Promise<void> {
40
40
  authorizedChatId: resolved.chatId,
41
41
  workspace,
42
42
  model: resolved.model,
43
- timezone: resolved.timezone,
43
+ timeZone: resolved.timeZone,
44
44
  stt: resolved.openaiApiKey ? new SpeechToText(resolved.openaiApiKey) : undefined,
45
45
  };
46
46
 
@@ -117,7 +117,7 @@ function makeOrchestrator(claude: Claude, extraConfig?: Partial<OrchestratorConf
117
117
  const orch = new Orchestrator({
118
118
  workspace: TEST_WORKSPACE,
119
119
  model: "sonnet",
120
- timezone: "UTC",
120
+ timeZone: "UTC",
121
121
  settingsDir: tmpSettingsDir,
122
122
  onResponse,
123
123
  claude,
@@ -1156,7 +1156,7 @@ describe("Orchestrator", () => {
1156
1156
  const orch = new Orchestrator({
1157
1157
  workspace: TEST_WORKSPACE,
1158
1158
  model: "sonnet",
1159
- timezone: "UTC",
1159
+ timeZone: "UTC",
1160
1160
  settingsDir: tmpSettingsDir,
1161
1161
  onResponse: failingOnResponse,
1162
1162
  claude,
@@ -92,7 +92,7 @@ interface SessionInfo {
92
92
  export interface OrchestratorConfig {
93
93
  model: string;
94
94
  workspace: string;
95
- timezone: string;
95
+ timeZone: string;
96
96
  settingsDir?: string;
97
97
  onResponse: (response: OrchestratorResponse) => Promise<void>;
98
98
  claude?: Claude;
@@ -119,8 +119,8 @@ export class Orchestrator {
119
119
 
120
120
  constructor(config: OrchestratorConfig) {
121
121
  this.#config = config;
122
- this.#prompts = new PromptBuilder(config.timezone);
123
- const envVars: Record<string, string> = { TZ: config.timezone };
122
+ this.#prompts = new PromptBuilder(config.timeZone);
123
+ const envVars: Record<string, string> = { TZ: config.timeZone };
124
124
  this.#claude = config.claude ?? new Claude({ workspace: config.workspace, systemPrompt: this.#prompts.systemPrompt, envVars });
125
125
  this.#waitThreshold = config.waitThreshold ?? WAIT_THRESHOLD;
126
126
  this.#healthCheckInterval = config.healthCheckInterval ?? HEALTH_CHECK_INTERVAL_MS;
@@ -51,7 +51,7 @@ describe("systemPrompt", () => {
51
51
  expect(p.systemPrompt).toContain("opus");
52
52
  });
53
53
 
54
- it("includes timezone but not a fixed date", () => {
54
+ it("includes time zone but not a fixed date", () => {
55
55
  const prague = new PromptBuilder("Europe/Prague");
56
56
  expect(prague.systemPrompt).not.toContain("Current date:");
57
57
  expect(prague.systemPrompt).toContain("Timezone: Europe/Prague");
@@ -82,18 +82,18 @@ interface BuildXmlFields {
82
82
  }
83
83
 
84
84
  export class PromptBuilder {
85
- readonly #timezone: string;
85
+ readonly #timeZone: string;
86
86
 
87
- constructor(timezone: string) {
88
- this.#timezone = timezone;
87
+ constructor(timeZone: string) {
88
+ this.#timeZone = timeZone;
89
89
  }
90
90
 
91
91
  get systemPrompt(): string {
92
- return `${SYSTEM_PROMPT_BASE}\n\nTimezone: ${this.#timezone}. TZ env var is set — \`date\` and other CLI tools return local time.`;
92
+ return `${SYSTEM_PROMPT_BASE}\n\nTimezone: ${this.#timeZone}. TZ env var is set — \`date\` and other CLI tools return local time.`;
93
93
  }
94
94
 
95
95
  #localTime(): string {
96
- return DateTime.now().setZone(this.#timezone).toFormat("yyyy-MM-dd'T'HH:mm");
96
+ return DateTime.now().setZone(this.#timeZone).toFormat("yyyy-MM-dd'T'HH:mm");
97
97
  }
98
98
 
99
99
  static #escapeXml(text: string): string {
@@ -73,7 +73,7 @@ describe("Scheduler — cron jobs", () => {
73
73
  });
74
74
 
75
75
  const onJob = makeOnJob();
76
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
76
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
77
77
  s.start();
78
78
  s.stop();
79
79
 
@@ -86,7 +86,7 @@ describe("Scheduler — cron jobs", () => {
86
86
  });
87
87
 
88
88
  const onJob = makeOnJob();
89
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
89
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
90
90
  s.start();
91
91
  s.stop();
92
92
 
@@ -102,7 +102,7 @@ describe("Scheduler — cron jobs", () => {
102
102
  });
103
103
 
104
104
  const onJob = makeOnJob();
105
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
105
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
106
106
  s.start();
107
107
  s.stop();
108
108
 
@@ -119,7 +119,7 @@ describe("Scheduler — cron jobs", () => {
119
119
  });
120
120
 
121
121
  const onJob = makeOnJob();
122
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
122
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
123
123
  s.start();
124
124
  s.stop();
125
125
 
@@ -134,7 +134,7 @@ describe("Scheduler — cron jobs", () => {
134
134
  });
135
135
 
136
136
  const onJob = makeOnJob();
137
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
137
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
138
138
  s.start();
139
139
  s.stop();
140
140
 
@@ -147,7 +147,7 @@ describe("Scheduler — cron jobs", () => {
147
147
  });
148
148
 
149
149
  const onJob = makeOnJob();
150
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
150
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
151
151
  s.start();
152
152
  s.stop();
153
153
 
@@ -164,7 +164,7 @@ describe("Scheduler — fireAt jobs", () => {
164
164
  });
165
165
 
166
166
  const onJob = makeOnJob();
167
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
167
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
168
168
  s.start();
169
169
  s.stop();
170
170
 
@@ -181,7 +181,7 @@ describe("Scheduler — fireAt jobs", () => {
181
181
  });
182
182
 
183
183
  const onJob = makeOnJob();
184
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
184
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
185
185
  s.start();
186
186
  s.stop();
187
187
 
@@ -197,7 +197,7 @@ describe("Scheduler — fireAt jobs", () => {
197
197
  });
198
198
 
199
199
  const onJob = makeOnJob();
200
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
200
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
201
201
  s.start();
202
202
  s.stop();
203
203
 
@@ -214,7 +214,7 @@ describe("Scheduler — fireAt jobs", () => {
214
214
  });
215
215
 
216
216
  const onJob = makeOnJob();
217
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
217
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
218
218
  s.start();
219
219
  s.stop();
220
220
 
@@ -236,7 +236,7 @@ describe("Scheduler — fireAt jobs", () => {
236
236
  });
237
237
 
238
238
  const onJob = makeOnJob();
239
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
239
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
240
240
  s.start();
241
241
  s.stop();
242
242
 
@@ -252,7 +252,7 @@ describe("Scheduler — fireAt jobs", () => {
252
252
  });
253
253
 
254
254
  const onJob = makeOnJob();
255
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
255
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
256
256
  s.start();
257
257
  s.stop();
258
258
 
@@ -273,7 +273,7 @@ describe("Scheduler — fireAt jobs", () => {
273
273
  });
274
274
 
275
275
  const onJob = makeOnJob();
276
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
276
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
277
277
  s.start();
278
278
  s.stop();
279
279
 
@@ -290,14 +290,14 @@ describe("Scheduler — fireAt jobs", () => {
290
290
  });
291
291
 
292
292
  const onJob = makeOnJob();
293
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
293
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
294
294
  s.start();
295
295
  s.stop();
296
296
 
297
297
  expect(onJob).toHaveBeenCalledWith("smart", "think", "opus");
298
298
  });
299
299
 
300
- it("interprets offset-less fireAt in the configured timezone", () => {
300
+ it("interprets offset-less fireAt in the configured time zone", () => {
301
301
  // Create a fireAt 30 seconds ago in Europe/Prague local time (no offset)
302
302
  const now = new Date();
303
303
  const pragueStr = now.toLocaleString("en-US", { timeZone: "Europe/Prague" });
@@ -312,21 +312,21 @@ describe("Scheduler — fireAt jobs", () => {
312
312
  });
313
313
 
314
314
  const onJob = makeOnJob();
315
- const s = new Scheduler(TEST_DIR, { timezone: "Europe/Prague", onJob });
315
+ const s = new Scheduler(TEST_DIR, { timeZone: "Europe/Prague", onJob });
316
316
  s.start();
317
317
  s.stop();
318
318
 
319
319
  expect(onJob).toHaveBeenCalledWith("local", "local time", undefined);
320
320
  });
321
321
 
322
- it("preserves explicit offset in fireAt (ignores configured timezone)", () => {
322
+ it("preserves explicit offset in fireAt (ignores configured time zone)", () => {
323
323
  // fireAt with explicit +00:00 offset, 30s ago — should fire regardless of configured tz
324
324
  writeScheduleConfig({
325
325
  jobs: [{ name: "explicit", fireAt: justNowFireAt(), prompt: "with offset" }],
326
326
  });
327
327
 
328
328
  const onJob = makeOnJob();
329
- const s = new Scheduler(TEST_DIR, { timezone: "Asia/Tokyo", onJob });
329
+ const s = new Scheduler(TEST_DIR, { timeZone: "Asia/Tokyo", onJob });
330
330
  s.start();
331
331
  s.stop();
332
332
 
@@ -342,7 +342,7 @@ describe("Scheduler — fireAt jobs", () => {
342
342
  chmodSync(SCHEDULE_FILE, 0o444);
343
343
 
344
344
  const onJob = makeOnJob();
345
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
345
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
346
346
  s.start();
347
347
  s.stop();
348
348
 
@@ -357,7 +357,7 @@ describe("Scheduler — validation and edge cases", () => {
357
357
  rmSync(SCHEDULE_FILE, { force: true });
358
358
 
359
359
  const onJob = makeOnJob();
360
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
360
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
361
361
  s.start();
362
362
  s.stop();
363
363
 
@@ -368,7 +368,7 @@ describe("Scheduler — validation and edge cases", () => {
368
368
  writeFileSync(SCHEDULE_FILE, "not json{{{");
369
369
 
370
370
  const onJob = makeOnJob();
371
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
371
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
372
372
  s.start();
373
373
  s.stop();
374
374
 
@@ -379,7 +379,7 @@ describe("Scheduler — validation and edge cases", () => {
379
379
  writeScheduleConfig({ jobs: "not-array" });
380
380
 
381
381
  const onJob = makeOnJob();
382
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
382
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
383
383
  s.start();
384
384
  s.stop();
385
385
 
@@ -392,7 +392,7 @@ describe("Scheduler — validation and edge cases", () => {
392
392
  });
393
393
 
394
394
  const onJob = makeOnJob();
395
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
395
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
396
396
  s.start();
397
397
  s.stop();
398
398
 
@@ -405,7 +405,7 @@ describe("Scheduler — validation and edge cases", () => {
405
405
  });
406
406
 
407
407
  const onJob = makeOnJob();
408
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
408
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
409
409
  s.start();
410
410
  s.stop();
411
411
 
@@ -420,7 +420,7 @@ describe("Scheduler — validation and edge cases", () => {
420
420
  writeScheduleConfig({ jobs: [] });
421
421
 
422
422
  const onJob = makeOnJob();
423
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
423
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
424
424
  s.start();
425
425
  s.stop(); // should not throw
426
426
  });
@@ -431,7 +431,7 @@ describe("Scheduler — validation and edge cases", () => {
431
431
  });
432
432
 
433
433
  const onJob = makeOnJob();
434
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
434
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
435
435
  s.start();
436
436
  s.stop();
437
437
 
@@ -439,7 +439,7 @@ describe("Scheduler — validation and edge cases", () => {
439
439
 
440
440
  // Start again with a new instance — the lastMinute tracker is per-instance
441
441
  const onJob2 = makeOnJob();
442
- const s2 = new Scheduler(TEST_DIR, { timezone: "UTC", onJob: onJob2 });
442
+ const s2 = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob: onJob2 });
443
443
  s2.start();
444
444
  s2.stop();
445
445
 
@@ -452,7 +452,7 @@ describe("Scheduler — validation and edge cases", () => {
452
452
  });
453
453
 
454
454
  const onJob = makeOnJob();
455
- const s = new Scheduler(TEST_DIR, { timezone: "UTC", onJob });
455
+ const s = new Scheduler(TEST_DIR, { timeZone: "UTC", onJob });
456
456
  s.start();
457
457
  s.stop();
458
458
 
package/src/scheduler.ts CHANGED
@@ -28,7 +28,7 @@ export interface MissedInfo {
28
28
  }
29
29
 
30
30
  export interface SchedulerConfig {
31
- timezone: string;
31
+ timeZone: string;
32
32
  onJob: (name: string, prompt: string, model?: string, missed?: MissedInfo) => void;
33
33
  }
34
34
 
@@ -39,13 +39,13 @@ export class Scheduler {
39
39
  #lastMinute = -1;
40
40
  #schedulePath: string;
41
41
  #config: SchedulerConfig;
42
- #timezone: string;
42
+ #timeZone: string;
43
43
  #timer: Timer | null = null;
44
44
 
45
45
  constructor(workspace: string, config: SchedulerConfig) {
46
46
  this.#schedulePath = join(workspace, "data", "schedule.json");
47
47
  this.#config = config;
48
- this.#timezone = config.timezone;
48
+ this.#timeZone = config.timeZone;
49
49
  }
50
50
 
51
51
  start(): void {
@@ -120,7 +120,7 @@ export class Scheduler {
120
120
 
121
121
  #evaluateCronJob(job: { name: string; cron: string; prompt: string; model?: string }, now: Date): void {
122
122
  try {
123
- const interval = CronExpressionParser.parse(job.cron, { tz: this.#timezone });
123
+ const interval = CronExpressionParser.parse(job.cron, { tz: this.#timeZone });
124
124
  const prev = interval.prev();
125
125
  const diff = Math.abs(now.getTime() - prev.getTime());
126
126
  if (diff < 60_000) {
@@ -132,22 +132,22 @@ export class Scheduler {
132
132
  }
133
133
  }
134
134
 
135
- /** Parse a fireAt string, interpreting offset-less timestamps in the given timezone. */
136
- static #parseFireAt(fireAt: string, timezone: string): Date {
135
+ /** Parse a fireAt string, interpreting offset-less timestamps in the given time zone. */
136
+ static #parseFireAt(fireAt: string, timeZone: string): Date {
137
137
  const probe = DateTime.fromISO(fireAt, { setZone: true });
138
138
  if (probe.isValid && probe.isOffsetFixed) {
139
139
  // Has explicit offset (Z, +HH:MM, etc.) — use as-is
140
140
  return probe.toJSDate();
141
141
  }
142
- // No offset — interpret as local time in the configured timezone
143
- return DateTime.fromISO(fireAt, { zone: timezone }).toJSDate();
142
+ // No offset — interpret as local time in the configured time zone
143
+ return DateTime.fromISO(fireAt, { zone: timeZone }).toJSDate();
144
144
  }
145
145
 
146
146
  #evaluateFireAtJob(
147
147
  job: { name: string; fireAt: string; prompt: string; model?: string },
148
148
  now: Date,
149
149
  ): "remove" | "keep" {
150
- const fireAt = Scheduler.#parseFireAt(job.fireAt, this.#timezone);
150
+ const fireAt = Scheduler.#parseFireAt(job.fireAt, this.#timeZone);
151
151
  if (Number.isNaN(fireAt.getTime())) {
152
152
  log.warn({ name: job.name, fireAt: job.fireAt }, "Invalid fireAt date");
153
153
  return "keep";
@@ -10,7 +10,7 @@ const validSettings: Settings = {
10
10
  chatId: "12345678",
11
11
  model: "sonnet",
12
12
  workspace: "~/.macroclaw-workspace",
13
- timezone: "UTC",
13
+ timeZone: "UTC",
14
14
  logLevel: "debug",
15
15
  };
16
16
 
@@ -40,7 +40,7 @@ describe("SettingsManager.load", () => {
40
40
  chatId: "12345678",
41
41
  model: "sonnet",
42
42
  workspace: "~/.macroclaw-workspace",
43
- timezone: "UTC",
43
+ timeZone: "UTC",
44
44
  logLevel: "info",
45
45
  });
46
46
  });
@@ -52,7 +52,7 @@ describe("SettingsManager.load", () => {
52
52
  chatId: " 12345678 ",
53
53
  model: " opus ",
54
54
  workspace: " /custom/workspace ",
55
- timezone: " Europe/Prague ",
55
+ timeZone: " Europe/Prague ",
56
56
  openaiApiKey: " sk-test ",
57
57
  logLevel: " warn ",
58
58
  pinoramaUrl: " http://localhost:6200 ",
@@ -63,7 +63,7 @@ describe("SettingsManager.load", () => {
63
63
  chatId: "12345678",
64
64
  model: "opus",
65
65
  workspace: "/custom/workspace",
66
- timezone: "Europe/Prague",
66
+ timeZone: "Europe/Prague",
67
67
  openaiApiKey: "sk-test",
68
68
  logLevel: "warn",
69
69
  pinoramaUrl: "http://localhost:6200",
@@ -87,7 +87,7 @@ describe("SettingsManager.load", () => {
87
87
  chatId: "123",
88
88
  model: "opus",
89
89
  workspace: "/custom",
90
- timezone: "UTC",
90
+ timeZone: "UTC",
91
91
  openaiApiKey: "sk-test",
92
92
  logLevel: "info",
93
93
  pinoramaUrl: "http://localhost:6200",
@@ -152,12 +152,12 @@ describe("SettingsManager.load", () => {
152
152
  process.exit = origExit;
153
153
  });
154
154
 
155
- it("exits with code 1 when validation fails (invalid timezone)", () => {
155
+ it("exits with code 1 when validation fails (invalid timeZone)", () => {
156
156
  mkdirSync(tmpDir, { recursive: true });
157
157
  writeFileSync(join(tmpDir, "settings.json"), JSON.stringify({
158
158
  botToken: "tok",
159
159
  chatId: "123",
160
- timezone: "Europe/Prgaaue",
160
+ timeZone: "Europe/Prgaaue",
161
161
  }));
162
162
 
163
163
  const mockExit = mock(() => { throw new Error("exit"); });
@@ -283,7 +283,7 @@ describe("SettingsManager.applyEnvOverrides", () => {
283
283
  const { settings } = new SettingsManager(tmpDir).applyEnvOverrides(validSettings);
284
284
  expect(settings.model).toBe("opus");
285
285
  expect(settings.workspace).toBe("/override/path");
286
- expect(settings.timezone).toBe("Europe/Prague");
286
+ expect(settings.timeZone).toBe("Europe/Prague");
287
287
  expect(settings.logLevel).toBe("error");
288
288
  expect(settings.pinoramaUrl).toBe("http://override:6200");
289
289
  });
package/src/settings.ts CHANGED
@@ -11,7 +11,7 @@ export const settingsSchema = z.object({
11
11
  chatId: z.string().trim().regex(/^-?\d+$/, "Must be a numeric Telegram chat ID"),
12
12
  model: z.string().trim().pipe(z.enum(["haiku", "sonnet", "opus"])).default("sonnet"),
13
13
  workspace: z.string().trim().default("~/.macroclaw-workspace"),
14
- timezone: z.string().trim().refine((tz) => IANAZone.isValidZone(tz), "Must be a valid IANA timezone").default("UTC"),
14
+ timeZone: z.string().trim().refine((tz) => IANAZone.isValidZone(tz), "Must be a valid IANA timezone").default("UTC"),
15
15
  openaiApiKey: z.string().trim().optional(),
16
16
  logLevel: z.string().trim().pipe(z.enum(["debug", "info", "warn", "error"])).default("info"),
17
17
  pinoramaUrl: z.string().trim().optional(),
@@ -37,7 +37,7 @@ export class SettingsManager {
37
37
  chatId: "AUTHORIZED_CHAT_ID",
38
38
  model: "MODEL",
39
39
  workspace: "WORKSPACE",
40
- timezone: "TIMEZONE",
40
+ timeZone: "TIMEZONE",
41
41
  openaiApiKey: "OPENAI_API_KEY",
42
42
  logLevel: "LOG_LEVEL",
43
43
  pinoramaUrl: "PINORAMA_URL",
package/src/setup.test.ts CHANGED
@@ -122,7 +122,7 @@ describe("SetupWizard", () => {
122
122
  expect(settings.chatId).toBe("12345678");
123
123
  expect(settings.model).toBe("opus");
124
124
  expect(settings.workspace).toBe("/my/ws");
125
- expect(settings.timezone).toBe("Europe/Prague");
125
+ expect(settings.timeZone).toBe("Europe/Prague");
126
126
  expect(settings.openaiApiKey).toBe("sk-test");
127
127
  expect(settings.logLevel).toBe("info");
128
128
  });
@@ -151,7 +151,7 @@ describe("SetupWizard", () => {
151
151
  expect(settings.chatId).toBe("99887766");
152
152
  expect(settings.model).toBe("haiku");
153
153
  expect(settings.workspace).toBe("/env/ws");
154
- expect(settings.timezone).toBe("America/New_York");
154
+ expect(settings.timeZone).toBe("America/New_York");
155
155
  expect(settings.openaiApiKey).toBe("sk-env");
156
156
  });
157
157
 
package/src/setup.ts CHANGED
@@ -88,8 +88,8 @@ export class SetupWizard {
88
88
  this.#io.write("\nLocal timezone for the agent's clock and scheduled events.\n");
89
89
  this.#io.write("Use an IANA timezone name (e.g. Europe/Prague, America/New_York, UTC).\n\n");
90
90
  const detectedTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
91
- const defaultTimezone = this.#default("timezone", detectedTz || "UTC");
92
- const timezone = await this.#askValidated("timezone", `Timezone [${defaultTimezone}]: `, defaultTimezone);
91
+ const defaultTimezone = this.#default("timeZone", detectedTz || "UTC");
92
+ const timeZone = await this.#askValidated("timeZone", `Timezone [${defaultTimezone}]: `, defaultTimezone);
93
93
 
94
94
  // OpenAI API key
95
95
  this.#io.write("\nMacroclaw uses OpenAI's Whisper API to transcribe voice messages.\n");
@@ -106,7 +106,7 @@ export class SetupWizard {
106
106
  chatId,
107
107
  model,
108
108
  workspace,
109
- timezone,
109
+ timeZone,
110
110
  openaiApiKey,
111
111
  ...(logLevel && { logLevel }),
112
112
  });
@@ -1,9 +1,9 @@
1
1
  ---
2
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."
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
4
  ---
5
5
 
6
- Read or change macroclaw settings. Only `model` and `timezone` can be changed through this skill.
6
+ Read or change macroclaw settings. Only `model` and `timeZone` can be changed through this skill.
7
7
 
8
8
  ## Settings file
9
9
 
@@ -11,7 +11,7 @@ Location: `~/.macroclaw/settings.json`
11
11
 
12
12
  ## Reading settings
13
13
 
14
- When the user asks about current settings ("what model am I on?", "what's the timezone?"):
14
+ When the user asks about current settings ("what model am I on?", "what's the time zone?"):
15
15
 
16
16
  1. Read `~/.macroclaw/settings.json`
17
17
  2. Report the requested value
@@ -20,7 +20,7 @@ When the user asks about current settings ("what model am I on?", "what's the ti
20
20
 
21
21
  Allowed changes:
22
22
  - **model**: `haiku`, `sonnet`, or `opus`
23
- - **timezone**: any valid IANA timezone (e.g. `Europe/Prague`, `America/New_York`, `UTC`)
23
+ - **timeZone**: any valid IANA timezone (e.g. `Europe/Prague`, `America/New_York`, `UTC`)
24
24
 
25
25
  All other settings (botToken, chatId, workspace, etc.) cannot be changed through this skill — tell the user to run `macroclaw setup` instead.
26
26