skimpyclaw 0.3.3 → 0.3.5

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.
@@ -6,7 +6,7 @@
6
6
  <title>SkimpyClaw Dashboard</title>
7
7
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
8
8
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Playfair+Display:wght@600;700;800&display=swap" rel="stylesheet">
9
- <script type="module" crossorigin src="/assets/index-CkonC7Cd.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-UVAjSXCG.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="/assets/index-EAg6lqF5.css">
11
11
  </head>
12
12
  <body>
@@ -12,6 +12,7 @@ export declare function checkTelegramToken(token: string): Promise<DoctorCheckRe
12
12
  export declare function checkDiscordToken(token: string): Promise<DoctorCheckResult>;
13
13
  export declare function checkBrowserBinaryIfEnabled(config: Config): Promise<DoctorCheckResult>;
14
14
  export declare function checkVoiceDependencies(config: Config): Promise<DoctorCheckResult>;
15
+ export declare function checkPlaywrightIfBrowserEnabled(config: Config): Promise<DoctorCheckResult>;
15
16
  export declare function checkMcpConfig(config: Config): Promise<DoctorCheckResult>;
16
17
  export declare function checkGatewayHostBindable(host: string): Promise<DoctorCheckResult>;
17
18
  export declare function checkSkimpyclawDirWritable(): Promise<DoctorCheckResult>;
@@ -274,16 +274,32 @@ export async function checkVoiceDependencies(config) {
274
274
  if (ffmpeg.status !== 0) {
275
275
  issues.push('ffmpeg not found');
276
276
  }
277
- // Check for whisper-cli (C++) or whisper (Python)
277
+ // Check for STT: local whisper OR API provider
278
278
  const whisperCli = spawnSync('which', ['whisper-cli'], { encoding: 'utf-8' });
279
279
  const whisperPy = spawnSync('which', ['whisper'], { encoding: 'utf-8' });
280
- if (whisperCli.status !== 0 && whisperPy.status !== 0) {
281
- issues.push('whisper not found (neither whisper-cli nor whisper)');
280
+ const hasLocalWhisper = whisperCli.status === 0 || whisperPy.status === 0;
281
+ // Check if any API STT provider is configured
282
+ const hasApiStt = Object.values(config.voice?.providers || {}).some((p) => p && typeof p === 'object' && 'stt' in p);
283
+ if (!hasLocalWhisper && !hasApiStt) {
284
+ issues.push('No STT available — install whisper-cli (brew install whisper-cpp) or configure an API STT provider (e.g. openai.stt)');
282
285
  }
283
286
  if (issues.length > 0) {
284
- return fail(name, category, issues.join('; '), 'Install ffmpeg and whisper-cli (or whisper) for voice features.');
287
+ return fail(name, category, issues.join('; '), 'Install ffmpeg, and either whisper-cli (brew install whisper-cpp) or add openai.stt to voice providers.');
285
288
  }
286
- return ok(name, category, 'ffmpeg and whisper available');
289
+ const sttMethod = hasLocalWhisper ? (whisperCli.status === 0 ? 'whisper-cli' : 'whisper') : 'API STT';
290
+ return ok(name, category, `ffmpeg and ${sttMethod} available`);
291
+ }
292
+ export async function checkPlaywrightIfBrowserEnabled(config) {
293
+ const name = 'playwright_installed';
294
+ const category = 'runtime';
295
+ if (!isAnyBrowserEnabled(config)) {
296
+ return ok(name, category, 'Browser tools disabled');
297
+ }
298
+ const pw = spawnSync('npx', ['playwright', '--version'], { encoding: 'utf-8', timeout: 10000 });
299
+ if (pw.status === 0) {
300
+ return ok(name, category, `Playwright ${(pw.stdout || '').trim()}`);
301
+ }
302
+ return fail(name, category, 'Playwright not installed', 'Run: npx playwright install chromium');
287
303
  }
288
304
  export async function checkMcpConfig(config) {
289
305
  const name = 'mcp_config';
@@ -1,5 +1,5 @@
1
1
  import { loadConfig } from '../config.js';
2
- import { checkNodeVersion, checkPackageManagerAvailable, checkTypeScriptCompile, checkConfigExistsAndValidJson, checkRequiredEnvVars, checkEnvVarPatterns, checkAllowedPathsWritable, checkProviderAuth, checkTelegramToken, checkDiscordToken, checkBrowserBinaryIfEnabled, checkVoiceDependencies, checkMcpConfig, checkGatewayHostBindable, checkSkimpyclawDirWritable, checkPortAvailability, checkSandboxAvailable, } from './checks.js';
2
+ import { checkNodeVersion, checkPackageManagerAvailable, checkTypeScriptCompile, checkConfigExistsAndValidJson, checkRequiredEnvVars, checkEnvVarPatterns, checkAllowedPathsWritable, checkProviderAuth, checkTelegramToken, checkDiscordToken, checkBrowserBinaryIfEnabled, checkPlaywrightIfBrowserEnabled, checkVoiceDependencies, checkMcpConfig, checkGatewayHostBindable, checkSkimpyclawDirWritable, checkPortAvailability, checkSandboxAvailable, } from './checks.js';
3
3
  export function computeExitCode(report) {
4
4
  if (report.checks.some((check) => !check.ok && check.fatal)) {
5
5
  return 2;
@@ -99,6 +99,7 @@ export async function runDoctor() {
99
99
  }
100
100
  }
101
101
  checks.push(await runSafe('browser_binary_available', 'runtime', () => checkBrowserBinaryIfEnabled(config)));
102
+ checks.push(await runSafe('playwright_installed', 'runtime', () => checkPlaywrightIfBrowserEnabled(config)));
102
103
  checks.push(await runSafe('voice_dependencies', 'runtime', () => checkVoiceDependencies(config)));
103
104
  checks.push(await runSafe('mcp_config', 'runtime', () => checkMcpConfig(config)));
104
105
  checks.push(await runSafe('gateway_host_bindable', 'runtime', () => checkGatewayHostBindable(config.gateway.host ?? '127.0.0.1')));
package/dist/setup.js CHANGED
@@ -876,6 +876,22 @@ export async function runSetup(options = {}) {
876
876
  else {
877
877
  statusWarn('Chrome not found — browser tool may not work until Chrome is installed');
878
878
  }
879
+ // Check for Playwright
880
+ const pw = spawnSync('npx', ['playwright', '--version'], { encoding: 'utf-8', timeout: 10000 });
881
+ if (pw.status === 0) {
882
+ statusOk('Playwright detected');
883
+ }
884
+ else {
885
+ console.log('');
886
+ console.log(' ┌─────────────────────────────────────────────────────────┐');
887
+ console.log(' │ Browser tool requires Playwright. Install it: │');
888
+ console.log(' │ │');
889
+ console.log(' │ npx playwright install chromium │');
890
+ console.log(' │ │');
891
+ console.log(' │ Without this, the browser tool will fail at runtime. │');
892
+ console.log(' └─────────────────────────────────────────────────────────┘');
893
+ console.log('');
894
+ }
879
895
  }
880
896
  else {
881
897
  statusOk('browser disabled');
@@ -891,6 +907,32 @@ export async function runSetup(options = {}) {
891
907
  else {
892
908
  statusWarn('ffmpeg not found — voice features may not work until ffmpeg is installed');
893
909
  }
910
+ // Check for STT (speech-to-text) capability
911
+ const whisperCli = spawnSync('which', ['whisper-cli'], { encoding: 'utf-8' });
912
+ const whisperPy = spawnSync('which', ['whisper'], { encoding: 'utf-8' });
913
+ if (whisperCli.status === 0) {
914
+ statusOk('whisper-cli detected (whisper.cpp)');
915
+ }
916
+ else if (whisperPy.status === 0) {
917
+ statusOk('whisper detected (Python)');
918
+ }
919
+ else {
920
+ console.log('');
921
+ console.log(' ┌─────────────────────────────────────────────────────────┐');
922
+ console.log(' │ Voice transcription (STT) requires one of: │');
923
+ console.log(' │ │');
924
+ console.log(' │ Option A: Local whisper.cpp (free, recommended) │');
925
+ console.log(' │ brew install whisper-cpp │');
926
+ console.log(' │ whisper-cpp-download-ggml-model small │');
927
+ console.log(' │ │');
928
+ console.log(' │ Option B: OpenAI Whisper API ($0.006/min) │');
929
+ console.log(' │ Add OPENAI_API_KEY to ~/.skimpyclaw/.env │');
930
+ console.log(' │ Config auto-includes openai.stt if provider selected │');
931
+ console.log(' │ │');
932
+ console.log(' │ Without either, voice messages cannot be transcribed. │');
933
+ console.log(' └─────────────────────────────────────────────────────────┘');
934
+ console.log('');
935
+ }
894
936
  }
895
937
  else {
896
938
  statusOk('voice disabled');
@@ -142,7 +142,7 @@ export const CODE_WITH_AGENT_TOOL = {
142
142
  model: { type: 'string', description: 'Model override (e.g. opus, gpt-5.3-codex)' },
143
143
  max_turns: { type: 'number', description: 'Max agentic turns, Claude only (default: 30)' },
144
144
  timeout_minutes: { type: 'number', description: 'Timeout in minutes (default: 10, max: 30)' },
145
- validate: { type: 'boolean', description: 'Run pnpm build && pnpm test after (default: true)' },
145
+ validate: { type: 'boolean', description: 'Run build && test after completion using the auto-detected package manager (default: true)' },
146
146
  },
147
147
  required: ['task'],
148
148
  },
@@ -159,7 +159,7 @@ export const CODE_WITH_TEAM_TOOL = {
159
159
  agent: { type: 'string', enum: ['claude', 'codex', 'kimi'], description: 'Which coding CLI to use for all team workers. Omit to use configured default.' },
160
160
  model: { type: 'string', description: 'Model override (e.g. claude-sonnet-4-5, gpt-5.3-codex)' },
161
161
  timeout_minutes: { type: 'number', description: 'Total timeout in minutes (default: 20, max: 60)' },
162
- validate: { type: 'boolean', description: 'Run pnpm build && pnpm test after all agents complete (default: true)' },
162
+ validate: { type: 'boolean', description: 'Run build && test after all agents complete using the auto-detected package manager (default: true)' },
163
163
  },
164
164
  required: ['task'],
165
165
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skimpyclaw",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Lightweight personal AI assistant with Telegram and Discord integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -10,30 +10,12 @@
10
10
  },
11
11
  "files": [
12
12
  "dist",
13
+ "sandbox",
13
14
  "templates",
14
15
  "com.skimpyclaw.gateway.plist.example",
15
16
  "README.md",
16
17
  "LICENSE"
17
18
  ],
18
- "scripts": {
19
- "cli": "tsx src/cli.ts",
20
- "start": "tsx src/index.ts",
21
- "dev": "tsx watch src/index.ts",
22
- "dashboard:dev": "pnpm --dir web/dashboard dev",
23
- "dashboard:build": "pnpm --dir web/dashboard install --frozen-lockfile && pnpm --dir web/dashboard build",
24
- "docs:dev": "pnpm --dir docs dev",
25
- "docs:build": "pnpm --dir docs install --frozen-lockfile && pnpm --dir docs build",
26
- "docs:preview": "pnpm --dir docs preview",
27
- "setup": "tsx src/setup.ts",
28
- "onboard": "tsx src/cli.ts onboard",
29
- "build": "tsc && pnpm dashboard:build",
30
- "release:check": "pnpm build && pnpm test",
31
- "release:local": "bash ./scripts/release.sh",
32
- "lint": "eslint \"src/**/*.ts\"",
33
- "typecheck": "tsc --noEmit",
34
- "test": "vitest run",
35
- "ci": "pnpm run lint && pnpm run typecheck && pnpm run test"
36
- },
37
19
  "dependencies": {
38
20
  "@anthropic-ai/sdk": "^0.52.0",
39
21
  "@grammyjs/runner": "^2.0.3",
@@ -55,11 +37,6 @@
55
37
  "openai": "^4.47.0",
56
38
  "playwright": "^1.49.0"
57
39
  },
58
- "pnpm": {
59
- "onlyBuiltDependencies": [
60
- "esbuild"
61
- ]
62
- },
63
40
  "devDependencies": {
64
41
  "@eslint/js": "^9.39.2",
65
42
  "@types/node": "^20.11.0",
@@ -69,5 +46,24 @@
69
46
  "typescript": "^5.4.0",
70
47
  "typescript-eslint": "^8.54.0",
71
48
  "vitest": "^4.0.18"
49
+ },
50
+ "scripts": {
51
+ "cli": "tsx src/cli.ts",
52
+ "start": "tsx src/index.ts",
53
+ "dev": "tsx watch src/index.ts",
54
+ "dashboard:dev": "pnpm --dir web/dashboard dev",
55
+ "dashboard:build": "pnpm --dir web/dashboard install --frozen-lockfile && pnpm --dir web/dashboard build",
56
+ "docs:dev": "pnpm --dir docs dev",
57
+ "docs:build": "pnpm --dir docs install --frozen-lockfile && pnpm --dir docs build",
58
+ "docs:preview": "pnpm --dir docs preview",
59
+ "setup": "tsx src/setup.ts",
60
+ "onboard": "tsx src/cli.ts onboard",
61
+ "build": "tsc && pnpm dashboard:build",
62
+ "release:check": "pnpm build && pnpm test",
63
+ "release:local": "bash ./scripts/release.sh",
64
+ "lint": "eslint \"src/**/*.ts\"",
65
+ "typecheck": "tsc --noEmit",
66
+ "test": "vitest run",
67
+ "ci": "pnpm run lint && pnpm run typecheck && pnpm run test"
72
68
  }
73
- }
69
+ }
@@ -0,0 +1,40 @@
1
+ FROM node:22-slim
2
+
3
+ ARG SKIMPY_PROFILE=minimal
4
+
5
+ RUN apt-get update && apt-get install -y --no-install-recommends \
6
+ bash \
7
+ ca-certificates \
8
+ curl \
9
+ git \
10
+ gnupg \
11
+ jq \
12
+ python3 \
13
+ ripgrep \
14
+ && mkdir -p /etc/apt/keyrings \
15
+ && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
16
+ | gpg --dearmor -o /etc/apt/keyrings/githubcli-archive-keyring.gpg \
17
+ && chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
18
+ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
19
+ > /etc/apt/sources.list.d/github-cli.list \
20
+ && apt-get update \
21
+ && apt-get install -y --no-install-recommends gh \
22
+ && if [ "$SKIMPY_PROFILE" = "dev" ] || [ "$SKIMPY_PROFILE" = "full" ]; then \
23
+ apt-get install -y --no-install-recommends build-essential make gcc g++ pkg-config; \
24
+ fi \
25
+ && if [ "$SKIMPY_PROFILE" = "full" ]; then \
26
+ apt-get install -y --no-install-recommends python3-pip sqlite3 unzip less; \
27
+ fi \
28
+ && rm -rf /var/lib/apt/lists/*
29
+
30
+ # Install pnpm globally (Claude Code not needed inside sandbox)
31
+ RUN npm install -g pnpm
32
+
33
+ # Create non-root user matching typical macOS uid/gid
34
+ RUN groupadd -g 20 sandboxgrp || true \
35
+ && useradd -m -s /bin/bash -u 501 -g 20 sandbox || true
36
+
37
+ USER sandbox
38
+ WORKDIR /workspace
39
+
40
+ CMD ["sleep", "infinity"]