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.
- package/dist/__tests__/heartbeat.test.js +27 -0
- package/dist/__tests__/setup.test.js +4 -1
- package/dist/__tests__/voice.test.js +12 -0
- package/dist/heartbeat.js +4 -3
- package/dist/setup.js +4 -13
- package/dist/voice.js +5 -1
- package/package.json +1 -1
|
@@ -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.
|
|
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')
|
|
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
|
|
40
|
-
|
|
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
|
-
|
|
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);
|