skimpyclaw 0.3.4 → 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.
- package/dist/__tests__/api.test.js +19 -0
- package/dist/__tests__/doctor.runner.test.js +5 -1
- package/dist/__tests__/package-manager-detection.test.d.ts +1 -0
- package/dist/__tests__/package-manager-detection.test.js +172 -0
- package/dist/api.js +27 -0
- package/dist/cli.js +8 -0
- package/dist/code-agents/executor.d.ts +16 -0
- package/dist/code-agents/executor.js +77 -3
- package/dist/code-agents/utils.js +7 -3
- package/dist/dashboard/assets/index-UVAjSXCG.js +107 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/doctor/checks.d.ts +1 -0
- package/dist/doctor/checks.js +21 -5
- package/dist/doctor/runner.js +2 -1
- package/dist/setup.js +42 -0
- package/dist/tools/definitions.js +2 -2
- package/package.json +22 -26
- package/sandbox/Dockerfile +40 -0
- package/dist/dashboard/assets/index-CkonC7Cd.js +0 -65
|
@@ -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-
|
|
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>
|
package/dist/doctor/checks.d.ts
CHANGED
|
@@ -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>;
|
package/dist/doctor/checks.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
281
|
-
|
|
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 (
|
|
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
|
-
|
|
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';
|
package/dist/doctor/runner.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
+
"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"]
|