skimpyclaw 0.1.8 → 0.1.9

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.
@@ -41,4 +41,31 @@ describe('heartbeat prompt path normalization', () => {
41
41
  expect(mockRunAgentTurn).toHaveBeenCalledTimes(1);
42
42
  expect(mockRunAgentTurn).toHaveBeenCalledWith('main', expect.stringContaining('/.skimpyclaw/agents/main/HEARTBEAT.md'), config, 'claude-fast', expect.any(Object), undefined, expect.any(Object));
43
43
  });
44
+ it('normalizes /workspace heartbeat path to agents/main/HEARTBEAT.md', async () => {
45
+ const config = {
46
+ agents: { default: 'main' },
47
+ heartbeat: {
48
+ intervalMs: 60000,
49
+ prompt: 'Read /workspace/HEARTBEAT.md only. Reply HEARTBEAT_OK.',
50
+ model: 'claude-fast',
51
+ tools: {
52
+ enabled: true,
53
+ allowedPaths: ['/Users/katre/.skimpyclaw'],
54
+ maxIterations: 10,
55
+ bashTimeout: 15000,
56
+ },
57
+ },
58
+ channels: {
59
+ active: 'telegram',
60
+ telegram: {
61
+ defaultAllowedPaths: ['/Users/katre/.skimpyclaw'],
62
+ },
63
+ },
64
+ };
65
+ await runHeartbeatCheck(config);
66
+ expect(mockRunAgentTurn).toHaveBeenCalledTimes(1);
67
+ const promptArg = mockRunAgentTurn.mock.calls[0][1];
68
+ expect(promptArg).toContain('/.skimpyclaw/agents/main/HEARTBEAT.md');
69
+ expect(promptArg).not.toContain('/workspace/HEARTBEAT.md');
70
+ });
44
71
  });
@@ -15,7 +15,10 @@ describe('setup config generation', () => {
15
15
  expect(config.models.providers.anthropic.apiKey).toBe('${ANTHROPIC_API_KEY}');
16
16
  expect(config.models.providers.codex.authPath).toBe('${HOME}/.codex/auth.json');
17
17
  expect(config.channels.telegram.allowFrom).toEqual([12345]);
18
- expect(config.channels.telegram.defaultAllowedPaths).toContain('/tmp/workspace');
18
+ expect(config.channels.telegram.dailyNotesDir).toBe('${HOME}/.skimpyclaw/Daily Notes');
19
+ expect(config.channels.telegram.defaultAllowedPaths).toEqual(['${HOME}/.skimpyclaw']);
20
+ expect(config.channels.discord.defaultAllowedPaths).toEqual(['${HOME}/.skimpyclaw']);
21
+ expect(config.heartbeat.tools.allowedPaths).toEqual(['${HOME}/.skimpyclaw']);
19
22
  expect(config.models.aliases['claude-think']).toBe('anthropic/claude-sonnet-4-6');
20
23
  expect(config.models.aliases['claude-opus']).toBe('anthropic/claude-opus-4-6');
21
24
  expect(config.models.aliases.codex).toBe('codex/gpt-5.3-codex');
@@ -240,3 +240,15 @@ describe('checkVoiceDependencies', () => {
240
240
  expect(result.missing[0]).toContain('No local whisper CLI and no API providers configured');
241
241
  });
242
242
  });
243
+ describe('transcription provider messaging', () => {
244
+ it('explains that macos is TTS-only when no STT provider exists', async () => {
245
+ const { transcribeAudio } = await import('../voice.js');
246
+ const config = {
247
+ ...baseVoiceConfig,
248
+ providers: {
249
+ macos: { tts: { voice: 'Samantha' } },
250
+ },
251
+ };
252
+ await expect(transcribeAudio('/tmp/fake-audio.ogg', config)).rejects.toThrow('macos" is TTS-only');
253
+ });
254
+ });
package/dist/heartbeat.js CHANGED
@@ -11,7 +11,7 @@ let heartbeatTimer = null;
11
11
  let running = false;
12
12
  const DEFAULT_HEARTBEAT_TOOLS = {
13
13
  enabled: true,
14
- allowedPaths: [join(homedir(), '.skimpyclaw'), process.cwd()],
14
+ allowedPaths: [join(homedir(), '.skimpyclaw')],
15
15
  maxIterations: 100,
16
16
  bashTimeout: 15000,
17
17
  };
@@ -36,8 +36,9 @@ function getHeartbeatFilePath(config) {
36
36
  function getHeartbeatPrompt(config) {
37
37
  const heartbeatPath = getHeartbeatFilePath(config);
38
38
  const basePrompt = config.heartbeat.prompt || '';
39
- // Normalize legacy/wrong heartbeat locations to the agent template path.
40
- const normalized = basePrompt.replace(/(~\/(?:\.skimpyclaw\/)?HEARTBEAT\.md|\/Users\/[^/\s]+\/(?:\.skimpyclaw\/)?HEARTBEAT\.md|\/HEARTBEAT\.md)/g, heartbeatPath);
39
+ // Normalize any explicit HEARTBEAT.md path token (legacy /workspace, /Users/*, ~/...)
40
+ // to the active agent heartbeat template path.
41
+ const normalized = basePrompt.replace(/(?:~|\/)\S*HEARTBEAT\.md/g, heartbeatPath);
41
42
  if (normalized.includes('HEARTBEAT.md')) {
42
43
  return normalized;
43
44
  }
package/dist/setup.js CHANGED
@@ -376,20 +376,14 @@ export function buildSetupConfig(input) {
376
376
  enabled: true,
377
377
  token: '${TELEGRAM_BOT_TOKEN}',
378
378
  allowFrom: [parseInt(input.telegramId, 10) || input.telegramId],
379
- dailyNotesDir: '${HOME}/Daily Notes',
380
- defaultAllowedPaths: [
381
- '${HOME}/.skimpyclaw',
382
- input.workspaceDir,
383
- ],
379
+ dailyNotesDir: '${HOME}/.skimpyclaw/Daily Notes',
380
+ defaultAllowedPaths: ['${HOME}/.skimpyclaw'],
384
381
  },
385
382
  discord: {
386
383
  enabled: useDiscord,
387
384
  token: useDiscord ? '${DISCORD_BOT_TOKEN}' : '',
388
385
  allowFrom: useDiscord ? [input.discordUserId || ''] : [],
389
- defaultAllowedPaths: [
390
- '${HOME}/.skimpyclaw',
391
- input.workspaceDir,
392
- ],
386
+ defaultAllowedPaths: ['${HOME}/.skimpyclaw'],
393
387
  ...(input.discordDefaultChannelId ? { defaultChannelId: input.discordDefaultChannelId } : {}),
394
388
  },
395
389
  },
@@ -401,10 +395,7 @@ export function buildSetupConfig(input) {
401
395
  prompt: 'Read ~/.skimpyclaw/agents/main/HEARTBEAT.md. Follow it strictly. If nothing needs attention, reply HEARTBEAT_OK.',
402
396
  tools: {
403
397
  enabled: true,
404
- allowedPaths: [
405
- '${HOME}/.skimpyclaw',
406
- input.workspaceDir,
407
- ],
398
+ allowedPaths: ['${HOME}/.skimpyclaw'],
408
399
  maxIterations: 10,
409
400
  bashTimeout: 15000,
410
401
  ...(features.browser ? { browser: { enabled: true } } : { browser: { enabled: false } }),
package/dist/voice.js CHANGED
@@ -191,7 +191,8 @@ function getSTTProvider(config) {
191
191
  if (!provider)
192
192
  return false;
193
193
  // macOS voice provider is TTS-only and must never be used for transcription.
194
- if (name === 'macos')
194
+ const normalizedName = name.trim().toLowerCase();
195
+ if (normalizedName === 'macos')
195
196
  return false;
196
197
  return Boolean(provider.stt || provider.apiKey);
197
198
  };
@@ -275,6 +276,9 @@ export async function transcribeAudio(audioPath, config) {
275
276
  // No local whisper — try API provider directly
276
277
  const sttProvider = getSTTProvider(config);
277
278
  if (!sttProvider) {
279
+ if (config.providers?.macos) {
280
+ throw new Error('No voice transcription provider configured. "macos" is TTS-only. Install local whisper or configure an API STT provider (e.g. openai.stt).');
281
+ }
278
282
  throw new Error('No voice transcription available. Install whisper (pip install openai-whisper) or configure an API provider.');
279
283
  }
280
284
  return transcribeWithAPI(audioPath, sttProvider.name, sttProvider.provider);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skimpyclaw",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Lightweight personal AI assistant with Telegram and Discord integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",