skimpyclaw 0.3.14 → 0.4.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 +47 -37
- package/dist/__tests__/adapter-types.test.d.ts +4 -0
- package/dist/__tests__/adapter-types.test.js +63 -0
- package/dist/__tests__/anthropic-adapter.test.d.ts +4 -0
- package/dist/__tests__/anthropic-adapter.test.js +264 -0
- package/dist/__tests__/api.test.js +0 -1
- package/dist/__tests__/cli.integration.test.js +2 -4
- package/dist/__tests__/cli.test.js +0 -1
- package/dist/__tests__/code-agents-notifications.test.js +137 -0
- package/dist/__tests__/code-agents-parser.test.js +19 -1
- package/dist/__tests__/code-agents-preflight.test.js +3 -28
- package/dist/__tests__/code-agents-utils.test.js +34 -9
- package/dist/__tests__/code-agents-worktrees.test.js +116 -0
- package/dist/__tests__/codex-adapter.test.js +184 -0
- package/dist/__tests__/codex-auth.test.js +66 -0
- package/dist/__tests__/codex-provider-gating.test.js +35 -0
- package/dist/__tests__/codex-unified-loop.test.js +111 -0
- package/dist/__tests__/config-security.test.js +127 -0
- package/dist/__tests__/config.test.js +23 -0
- package/dist/__tests__/context-manager.test.js +243 -164
- package/dist/__tests__/cron-run.test.js +250 -0
- package/dist/__tests__/cron.test.js +12 -38
- package/dist/__tests__/digests.test.js +67 -0
- package/dist/__tests__/discord-attachments.test.js +211 -0
- package/dist/__tests__/discord-docs.test.d.ts +1 -0
- package/dist/__tests__/discord-docs.test.js +27 -0
- package/dist/__tests__/discord-thread-agents.test.d.ts +1 -0
- package/dist/__tests__/discord-thread-agents.test.js +115 -0
- package/dist/__tests__/discord-thread-context.test.d.ts +1 -0
- package/dist/__tests__/discord-thread-context.test.js +42 -0
- package/dist/__tests__/doctor.formatters.test.js +4 -4
- package/dist/__tests__/doctor.index.test.js +1 -1
- package/dist/__tests__/doctor.runner.test.js +3 -15
- package/dist/__tests__/env-sanitizer.test.d.ts +1 -0
- package/dist/__tests__/env-sanitizer.test.js +45 -0
- package/dist/__tests__/exec-approval.test.js +61 -0
- package/dist/__tests__/fetch-tool.test.d.ts +1 -0
- package/dist/__tests__/fetch-tool.test.js +85 -0
- package/dist/__tests__/gateway-status-auth.test.d.ts +1 -0
- package/dist/__tests__/gateway-status-auth.test.js +72 -0
- package/dist/__tests__/heartbeat.test.js +3 -3
- package/dist/__tests__/interactive-sessions.test.d.ts +1 -0
- package/dist/__tests__/interactive-sessions.test.js +96 -0
- package/dist/__tests__/langfuse.test.js +6 -18
- package/dist/__tests__/model-selection.test.js +3 -4
- package/dist/__tests__/providers-init.test.js +2 -8
- package/dist/__tests__/providers-routing.test.js +1 -1
- package/dist/__tests__/providers-utils.test.js +13 -3
- package/dist/__tests__/sessions.test.js +14 -10
- package/dist/__tests__/setup.test.js +12 -29
- package/dist/__tests__/skills.test.js +10 -7
- package/dist/__tests__/stream-formatter.test.d.ts +1 -0
- package/dist/__tests__/stream-formatter.test.js +114 -0
- package/dist/__tests__/token-efficiency.test.js +131 -15
- package/dist/__tests__/tool-loop.test.d.ts +4 -0
- package/dist/__tests__/tool-loop.test.js +505 -0
- package/dist/__tests__/tools.test.js +101 -276
- package/dist/__tests__/utils.test.d.ts +1 -0
- package/dist/__tests__/utils.test.js +14 -0
- package/dist/__tests__/voice.test.js +21 -0
- package/dist/agent.js +35 -4
- package/dist/api.js +113 -37
- package/dist/channels/discord/attachments.d.ts +50 -0
- package/dist/channels/discord/attachments.js +137 -0
- package/dist/channels/discord/delegation.d.ts +5 -0
- package/dist/channels/discord/delegation.js +136 -0
- package/dist/channels/discord/handlers.js +694 -7
- package/dist/channels/discord/index.d.ts +16 -1
- package/dist/channels/discord/index.js +64 -1
- package/dist/channels/discord/thread-agents.d.ts +54 -0
- package/dist/channels/discord/thread-agents.js +323 -0
- package/dist/channels/discord/threads.d.ts +58 -0
- package/dist/channels/discord/threads.js +192 -0
- package/dist/channels/discord/types.js +4 -2
- package/dist/channels/discord/utils.d.ts +16 -0
- package/dist/channels/discord/utils.js +86 -6
- package/dist/channels/telegram/index.d.ts +1 -1
- package/dist/channels/telegram/types.js +1 -1
- package/dist/channels/telegram/utils.js +9 -3
- package/dist/channels.d.ts +1 -1
- package/dist/cli.js +20 -400
- package/dist/code-agents/executor.d.ts +1 -1
- package/dist/code-agents/executor.js +101 -45
- package/dist/code-agents/index.d.ts +2 -7
- package/dist/code-agents/index.js +111 -80
- package/dist/code-agents/interactive-resume.d.ts +6 -0
- package/dist/code-agents/interactive-resume.js +98 -0
- package/dist/code-agents/interactive-sessions.d.ts +20 -0
- package/dist/code-agents/interactive-sessions.js +132 -0
- package/dist/code-agents/parser.js +5 -1
- package/dist/code-agents/registry.d.ts +7 -1
- package/dist/code-agents/registry.js +11 -23
- package/dist/code-agents/stream-formatter.d.ts +8 -0
- package/dist/code-agents/stream-formatter.js +92 -0
- package/dist/code-agents/types.d.ts +16 -24
- package/dist/code-agents/utils.d.ts +35 -11
- package/dist/code-agents/utils.js +349 -95
- package/dist/code-agents/worktrees.d.ts +37 -0
- package/dist/code-agents/worktrees.js +116 -0
- package/dist/config.d.ts +2 -4
- package/dist/config.js +123 -23
- package/dist/cron.d.ts +1 -6
- package/dist/cron.js +175 -82
- package/dist/dashboard/assets/index-B345aOO-.js +65 -0
- package/dist/dashboard/assets/index-ZWK4dalJ.css +1 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/digests.d.ts +1 -0
- package/dist/digests.js +132 -42
- package/dist/doctor/checks.d.ts +0 -3
- package/dist/doctor/checks.js +1 -108
- package/dist/doctor/runner.js +1 -4
- package/dist/env-sanitizer.d.ts +2 -0
- package/dist/env-sanitizer.js +61 -0
- package/dist/exec-approval.d.ts +11 -1
- package/dist/exec-approval.js +17 -4
- package/dist/gateway.d.ts +3 -1
- package/dist/gateway.js +17 -7
- package/dist/heartbeat.js +1 -6
- package/dist/langfuse.js +3 -29
- package/dist/model-selection.js +3 -1
- package/dist/providers/adapter.d.ts +118 -0
- package/dist/providers/adapter.js +6 -0
- package/dist/providers/adapters/anthropic-adapter.d.ts +22 -0
- package/dist/providers/adapters/anthropic-adapter.js +204 -0
- package/dist/providers/adapters/codex-adapter.d.ts +26 -0
- package/dist/providers/adapters/codex-adapter.js +203 -0
- package/dist/providers/anthropic.d.ts +1 -0
- package/dist/providers/anthropic.js +10 -272
- package/dist/providers/codex.d.ts +21 -0
- package/dist/providers/codex.js +149 -330
- package/dist/providers/content.d.ts +1 -1
- package/dist/providers/content.js +2 -2
- package/dist/providers/context-manager.d.ts +18 -6
- package/dist/providers/context-manager.js +199 -223
- package/dist/providers/index.d.ts +9 -1
- package/dist/providers/index.js +73 -64
- package/dist/providers/loop-utils.d.ts +20 -0
- package/dist/providers/loop-utils.js +30 -0
- package/dist/providers/tool-loop.d.ts +12 -0
- package/dist/providers/tool-loop.js +251 -0
- package/dist/providers/utils.d.ts +19 -3
- package/dist/providers/utils.js +100 -29
- package/dist/secure-store.d.ts +8 -0
- package/dist/secure-store.js +80 -0
- package/dist/service.js +3 -28
- package/dist/sessions.d.ts +3 -0
- package/dist/sessions.js +147 -18
- package/dist/setup-templates.js +13 -25
- package/dist/setup.d.ts +10 -6
- package/dist/setup.js +84 -292
- package/dist/skills.js +3 -11
- package/dist/tools/agent-delegation.d.ts +19 -0
- package/dist/tools/agent-delegation.js +49 -0
- package/dist/tools/bash-tool.js +89 -34
- package/dist/tools/definitions.d.ts +199 -302
- package/dist/tools/definitions.js +70 -123
- package/dist/tools/execute-context.d.ts +13 -4
- package/dist/tools/fetch-tool.js +109 -13
- package/dist/tools/file-tools.js +7 -1
- package/dist/tools.d.ts +7 -7
- package/dist/tools.js +133 -151
- package/dist/types.d.ts +37 -30
- package/dist/utils.js +4 -6
- package/dist/voice.d.ts +1 -1
- package/dist/voice.js +17 -4
- package/package.json +33 -23
- package/templates/TOOLS.md +0 -27
- package/dist/__tests__/audit.test.js +0 -122
- package/dist/__tests__/code-agents-orchestrator.test.js +0 -216
- package/dist/__tests__/code-agents-sandbox.test.js +0 -163
- package/dist/__tests__/orchestrator.test.js +0 -425
- package/dist/__tests__/sandbox-bridge.test.js +0 -116
- package/dist/__tests__/sandbox-manager.test.js +0 -144
- package/dist/__tests__/sandbox-mount-security.test.js +0 -139
- package/dist/__tests__/sandbox-runtime.test.js +0 -176
- package/dist/__tests__/subagent.test.js +0 -240
- package/dist/__tests__/telegram.test.js +0 -42
- package/dist/code-agents/orchestrator.d.ts +0 -29
- package/dist/code-agents/orchestrator.js +0 -694
- package/dist/code-agents/worktree.d.ts +0 -40
- package/dist/code-agents/worktree.js +0 -215
- package/dist/dashboard/assets/index-BoTHPby4.js +0 -65
- package/dist/dashboard/assets/index-D4mufvBg.css +0 -1
- package/dist/dashboard.d.ts +0 -8
- package/dist/dashboard.js +0 -4071
- package/dist/discord.d.ts +0 -8
- package/dist/discord.js +0 -792
- package/dist/mcp-context-a8c.d.ts +0 -13
- package/dist/mcp-context-a8c.js +0 -34
- package/dist/orchestrator.d.ts +0 -15
- package/dist/orchestrator.js +0 -676
- package/dist/providers/openai.d.ts +0 -10
- package/dist/providers/openai.js +0 -355
- package/dist/sandbox/bridge.d.ts +0 -5
- package/dist/sandbox/bridge.js +0 -63
- package/dist/sandbox/index.d.ts +0 -5
- package/dist/sandbox/index.js +0 -4
- package/dist/sandbox/manager.d.ts +0 -7
- package/dist/sandbox/manager.js +0 -100
- package/dist/sandbox/mount-security.d.ts +0 -12
- package/dist/sandbox/mount-security.js +0 -122
- package/dist/sandbox/runtime.d.ts +0 -39
- package/dist/sandbox/runtime.js +0 -192
- package/dist/sandbox-utils.d.ts +0 -6
- package/dist/sandbox-utils.js +0 -36
- package/dist/subagent.d.ts +0 -19
- package/dist/subagent.js +0 -407
- package/dist/telegram.d.ts +0 -2
- package/dist/telegram.js +0 -11
- package/dist/tools/browser-tool.d.ts +0 -3
- package/dist/tools/browser-tool.js +0 -266
- package/sandbox/Dockerfile +0 -40
- /package/dist/__tests__/{audit.test.d.ts → code-agents-notifications.test.d.ts} +0 -0
- /package/dist/__tests__/{code-agents-orchestrator.test.d.ts → code-agents-worktrees.test.d.ts} +0 -0
- /package/dist/__tests__/{code-agents-sandbox.test.d.ts → codex-adapter.test.d.ts} +0 -0
- /package/dist/__tests__/{orchestrator.test.d.ts → codex-auth.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-bridge.test.d.ts → codex-provider-gating.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-manager.test.d.ts → codex-unified-loop.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-mount-security.test.d.ts → config-security.test.d.ts} +0 -0
- /package/dist/__tests__/{sandbox-runtime.test.d.ts → cron-run.test.d.ts} +0 -0
- /package/dist/__tests__/{subagent.test.d.ts → digests.test.d.ts} +0 -0
- /package/dist/__tests__/{telegram.test.d.ts → discord-attachments.test.d.ts} +0 -0
package/dist/setup.d.ts
CHANGED
|
@@ -3,19 +3,21 @@ interface SetupOptions {
|
|
|
3
3
|
dryRun?: boolean;
|
|
4
4
|
}
|
|
5
5
|
export declare function renderGatewayPlist(): string;
|
|
6
|
-
type ProviderChoice = 'anthropic-api' | 'anthropic-oauth' | '
|
|
6
|
+
type ProviderChoice = 'anthropic-api' | 'anthropic-oauth' | 'codex-oauth';
|
|
7
7
|
interface ProviderSecrets {
|
|
8
8
|
anthropicKey?: string;
|
|
9
9
|
oauthToken?: string;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
}
|
|
11
|
+
/** Map of secret name → config reference string (KEYCHAIN ref or ENV ref). */
|
|
12
|
+
interface SecretRefs {
|
|
13
|
+
anthropicKey?: string;
|
|
14
|
+
oauthToken?: string;
|
|
15
|
+
telegramToken?: string;
|
|
16
|
+
discordToken?: string;
|
|
13
17
|
}
|
|
14
18
|
interface SetupFeatures {
|
|
15
|
-
browser: boolean;
|
|
16
19
|
voice: boolean;
|
|
17
20
|
mcp: boolean;
|
|
18
|
-
sandbox: boolean;
|
|
19
21
|
}
|
|
20
22
|
interface SetupBuildInput {
|
|
21
23
|
workspaceDir: string;
|
|
@@ -28,6 +30,8 @@ interface SetupBuildInput {
|
|
|
28
30
|
agentName: string;
|
|
29
31
|
selectedProviders: Set<ProviderChoice>;
|
|
30
32
|
providerSecrets: ProviderSecrets;
|
|
33
|
+
/** Resolved secret references (KEYCHAIN or ENV). If not provided, falls back to env var refs. */
|
|
34
|
+
secretRefs?: SecretRefs;
|
|
31
35
|
features?: SetupFeatures;
|
|
32
36
|
starters?: SetupStarters;
|
|
33
37
|
}
|
package/dist/setup.js
CHANGED
|
@@ -8,7 +8,7 @@ import { spawnSync } from 'child_process';
|
|
|
8
8
|
import { randomUUID } from 'crypto';
|
|
9
9
|
import { runDoctor as runDoctorChecks } from './doctor/runner.js';
|
|
10
10
|
import { toErrorMessage } from './utils.js';
|
|
11
|
-
import {
|
|
11
|
+
import { secureStoreAvailable, setSecureValue } from './secure-store.js';
|
|
12
12
|
import { ensureCoreTemplates, ensureStarterSkills, buildStarterCronJobs, } from './setup-templates.js';
|
|
13
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
14
|
const __dirname = dirname(__filename);
|
|
@@ -59,33 +59,6 @@ function loadExistingSetup() {
|
|
|
59
59
|
}
|
|
60
60
|
return { config, env };
|
|
61
61
|
}
|
|
62
|
-
function bootstrapSandbox(runtime, image, network) {
|
|
63
|
-
const sandboxDir = join(__dirname, '..', 'sandbox');
|
|
64
|
-
const dockerfile = join(sandboxDir, 'Dockerfile');
|
|
65
|
-
if (!existsSync(dockerfile)) {
|
|
66
|
-
return { ok: false, message: `Sandbox Dockerfile not found: ${dockerfile}` };
|
|
67
|
-
}
|
|
68
|
-
if (!isSandboxRuntimeRunning(runtime)) {
|
|
69
|
-
const hint = runtime === 'container'
|
|
70
|
-
? 'Run `container system start` and rerun onboarding.'
|
|
71
|
-
: 'Start Docker Desktop and rerun onboarding.';
|
|
72
|
-
return { ok: false, message: `Runtime "${runtime}" is not running. ${hint}` };
|
|
73
|
-
}
|
|
74
|
-
if (!sandboxNetworkExists(runtime, network)) {
|
|
75
|
-
return { ok: false, message: `Network "${network}" not found for ${runtime}. Update sandbox.network and run \`skimpyclaw sandbox init\`.` };
|
|
76
|
-
}
|
|
77
|
-
const build = spawnSync(runtime, ['build', '--build-arg', 'SKIMPY_PROFILE=minimal', '-t', image, sandboxDir], { encoding: 'utf-8' });
|
|
78
|
-
if (build.status !== 0) {
|
|
79
|
-
const detail = `${build.stderr || ''}\n${build.stdout || ''}`.trim();
|
|
80
|
-
return { ok: false, message: `Image build failed: ${detail.slice(-800)}` };
|
|
81
|
-
}
|
|
82
|
-
const smoke = spawnSync(runtime, ['run', '--rm', '--network', network, image, 'sh', '-lc', 'hostname && command -v gh >/dev/null && command -v rg >/dev/null && echo sandbox-ok'], { encoding: 'utf-8' });
|
|
83
|
-
if (smoke.status !== 0) {
|
|
84
|
-
const detail = `${smoke.stderr || ''}\n${smoke.stdout || ''}`.trim();
|
|
85
|
-
return { ok: false, message: `Sandbox smoke test failed: ${detail.slice(-800)}` };
|
|
86
|
-
}
|
|
87
|
-
return { ok: true, message: (smoke.stdout || '').trim().split('\n').join(' | ') };
|
|
88
|
-
}
|
|
89
62
|
function ask(rl, question) {
|
|
90
63
|
return new Promise((resolve) => {
|
|
91
64
|
rl.question(question, (answer) => {
|
|
@@ -124,10 +97,7 @@ export function renderGatewayPlist() {
|
|
|
124
97
|
const PROVIDER_OPTIONS = [
|
|
125
98
|
{ key: 'anthropic-api', label: 'Anthropic API key' },
|
|
126
99
|
{ key: 'anthropic-oauth', label: 'Anthropic OAuth (Claude Code)' },
|
|
127
|
-
{ key: 'openai-api', label: 'OpenAI API key' },
|
|
128
100
|
{ key: 'codex-oauth', label: 'OpenAI Codex OAuth' },
|
|
129
|
-
{ key: 'minimax-api', label: 'MiniMax API key' },
|
|
130
|
-
{ key: 'kimi-api', label: 'Kimi (Moonshot) API key' },
|
|
131
101
|
];
|
|
132
102
|
function detectExistingProviders(config) {
|
|
133
103
|
const existing = new Set();
|
|
@@ -138,14 +108,8 @@ function detectExistingProviders(config) {
|
|
|
138
108
|
existing.add('anthropic-oauth');
|
|
139
109
|
else if (providers.anthropic?.apiKey)
|
|
140
110
|
existing.add('anthropic-api');
|
|
141
|
-
if (providers.openai?.apiKey)
|
|
142
|
-
existing.add('openai-api');
|
|
143
111
|
if (providers.codex || providers.openai?.authToken === 'codex')
|
|
144
112
|
existing.add('codex-oauth');
|
|
145
|
-
if (providers.minimax)
|
|
146
|
-
existing.add('minimax-api');
|
|
147
|
-
if (providers.kimi)
|
|
148
|
-
existing.add('kimi-api');
|
|
149
113
|
return existing;
|
|
150
114
|
}
|
|
151
115
|
async function askProviders(rl, existingProviders) {
|
|
@@ -192,6 +156,56 @@ async function askProviders(rl, existingProviders) {
|
|
|
192
156
|
return choices;
|
|
193
157
|
}
|
|
194
158
|
}
|
|
159
|
+
const KEYCHAIN_SERVICE = 'skimpyclaw';
|
|
160
|
+
/** Secret name → Keychain account name mapping. */
|
|
161
|
+
const SECRET_KEYCHAIN_ACCOUNTS = {
|
|
162
|
+
anthropicKey: 'anthropic-api-key',
|
|
163
|
+
oauthToken: 'anthropic-oauth-token',
|
|
164
|
+
telegramToken: 'telegram-bot-token',
|
|
165
|
+
discordToken: 'discord-bot-token',
|
|
166
|
+
};
|
|
167
|
+
/** Secret name → env var name mapping (fallback when Keychain unavailable). */
|
|
168
|
+
const SECRET_ENV_VARS = {
|
|
169
|
+
anthropicKey: 'ANTHROPIC_API_KEY',
|
|
170
|
+
oauthToken: 'CLAUDE_CODE_OAUTH_TOKEN',
|
|
171
|
+
telegramToken: 'TELEGRAM_BOT_TOKEN',
|
|
172
|
+
discordToken: 'DISCORD_BOT_TOKEN',
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* Store secrets securely. On macOS, uses Keychain and returns ${KEYCHAIN:...} refs.
|
|
176
|
+
* On other platforms, returns ${ENV_VAR} refs for .env file fallback.
|
|
177
|
+
* Returns the reference map and whether Keychain was used.
|
|
178
|
+
*/
|
|
179
|
+
function storeSecretsSecurely(secrets, telegramToken, discordToken) {
|
|
180
|
+
const keychainOk = secureStoreAvailable().ok;
|
|
181
|
+
const refs = {};
|
|
182
|
+
let storedCount = 0;
|
|
183
|
+
const allSecrets = [
|
|
184
|
+
{ key: 'anthropicKey', value: secrets.anthropicKey },
|
|
185
|
+
{ key: 'oauthToken', value: secrets.oauthToken },
|
|
186
|
+
{ key: 'telegramToken', value: telegramToken },
|
|
187
|
+
{ key: 'discordToken', value: discordToken },
|
|
188
|
+
];
|
|
189
|
+
for (const { key, value } of allSecrets) {
|
|
190
|
+
if (!value)
|
|
191
|
+
continue;
|
|
192
|
+
if (keychainOk) {
|
|
193
|
+
try {
|
|
194
|
+
const account = SECRET_KEYCHAIN_ACCOUNTS[key];
|
|
195
|
+
setSecureValue(KEYCHAIN_SERVICE, account, value);
|
|
196
|
+
refs[key] = `\${KEYCHAIN:${KEYCHAIN_SERVICE}/${account}}`;
|
|
197
|
+
storedCount++;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// Fall through to env var ref
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const envVar = SECRET_ENV_VARS[key];
|
|
205
|
+
refs[key] = `\${${envVar}}`;
|
|
206
|
+
}
|
|
207
|
+
return { refs, useKeychain: keychainOk && storedCount > 0, storedCount };
|
|
208
|
+
}
|
|
195
209
|
async function collectProviderSecrets(rl, providers, existingEnv) {
|
|
196
210
|
const secrets = {};
|
|
197
211
|
const env = existingEnv || {};
|
|
@@ -232,45 +246,6 @@ async function collectProviderSecrets(rl, providers, existingEnv) {
|
|
|
232
246
|
console.log(` ${c.yellow('⚠')} Skipped — run \`claude setup-token\`, then add CLAUDE_CODE_OAUTH_TOKEN to ~/.skimpyclaw/.env`);
|
|
233
247
|
}
|
|
234
248
|
}
|
|
235
|
-
if (providers.has('openai-api')) {
|
|
236
|
-
const existing = env.OPENAI_API_KEY || '';
|
|
237
|
-
console.log('\n OpenAI API Key');
|
|
238
|
-
if (existing) {
|
|
239
|
-
const input = await ask(rl, ` Enter key [${maskInput(existing)}]: `);
|
|
240
|
-
secrets.openaiKey = input || existing;
|
|
241
|
-
}
|
|
242
|
-
else {
|
|
243
|
-
console.log(' Get one from: https://platform.openai.com/api-keys');
|
|
244
|
-
secrets.openaiKey = await ask(rl, ' Enter key: ');
|
|
245
|
-
}
|
|
246
|
-
console.log(` ✓ ${maskInput(secrets.openaiKey)}`);
|
|
247
|
-
}
|
|
248
|
-
if (providers.has('minimax-api')) {
|
|
249
|
-
const existing = env.MINIMAX_API_KEY || '';
|
|
250
|
-
console.log('\n MiniMax API Key');
|
|
251
|
-
if (existing) {
|
|
252
|
-
const input = await ask(rl, ` Enter key [${maskInput(existing)}]: `);
|
|
253
|
-
secrets.minimaxKey = input || existing;
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
console.log(' Get one from: https://platform.minimax.io/user-center/basic-information/interface-key');
|
|
257
|
-
secrets.minimaxKey = await ask(rl, ' Enter key: ');
|
|
258
|
-
}
|
|
259
|
-
console.log(` ✓ ${maskInput(secrets.minimaxKey)}`);
|
|
260
|
-
}
|
|
261
|
-
if (providers.has('kimi-api')) {
|
|
262
|
-
const existing = env.KIMI_API_KEY || '';
|
|
263
|
-
console.log('\n Kimi (Moonshot) API Key');
|
|
264
|
-
if (existing) {
|
|
265
|
-
const input = await ask(rl, ` Enter key [${maskInput(existing)}]: `);
|
|
266
|
-
secrets.kimiKey = input || existing;
|
|
267
|
-
}
|
|
268
|
-
else {
|
|
269
|
-
console.log(' Get one from: https://platform.moonshot.cn/console/api-keys');
|
|
270
|
-
secrets.kimiKey = await ask(rl, ' Enter key: ');
|
|
271
|
-
}
|
|
272
|
-
console.log(` ✓ ${maskInput(secrets.kimiKey)}`);
|
|
273
|
-
}
|
|
274
249
|
if (providers.has('codex-oauth')) {
|
|
275
250
|
console.log('\n OpenAI Codex OAuth');
|
|
276
251
|
console.log(' No key needed — uses ~/.codex/auth.json at runtime.');
|
|
@@ -279,22 +254,13 @@ async function collectProviderSecrets(rl, providers, existingEnv) {
|
|
|
279
254
|
console.log('');
|
|
280
255
|
return secrets;
|
|
281
256
|
}
|
|
282
|
-
function buildProviders(providers) {
|
|
257
|
+
function buildProviders(providers, refs) {
|
|
283
258
|
const result = {};
|
|
284
259
|
if (providers.has('anthropic-api')) {
|
|
285
|
-
result.anthropic = { apiKey: '${ANTHROPIC_API_KEY}' };
|
|
260
|
+
result.anthropic = { apiKey: refs?.anthropicKey || '${ANTHROPIC_API_KEY}' };
|
|
286
261
|
}
|
|
287
262
|
else if (providers.has('anthropic-oauth')) {
|
|
288
|
-
result.anthropic = { authToken: '${CLAUDE_CODE_OAUTH_TOKEN}' };
|
|
289
|
-
}
|
|
290
|
-
if (providers.has('openai-api')) {
|
|
291
|
-
result.openai = { apiKey: '${OPENAI_API_KEY}', baseURL: 'https://api.openai.com/v1' };
|
|
292
|
-
}
|
|
293
|
-
if (providers.has('minimax-api')) {
|
|
294
|
-
result.minimax = { apiKey: '${MINIMAX_API_KEY}', baseURL: 'https://api.minimax.io/v1' };
|
|
295
|
-
}
|
|
296
|
-
if (providers.has('kimi-api')) {
|
|
297
|
-
result.kimi = { apiKey: '${KIMI_API_KEY}', baseURL: 'https://api.kimi.com/coding/v1' };
|
|
263
|
+
result.anthropic = { authToken: refs?.oauthToken || '${CLAUDE_CODE_OAUTH_TOKEN}' };
|
|
298
264
|
}
|
|
299
265
|
if (providers.has('codex-oauth')) {
|
|
300
266
|
result.codex = {
|
|
@@ -308,48 +274,31 @@ function buildProviders(providers) {
|
|
|
308
274
|
function buildDefaultModel(providers) {
|
|
309
275
|
const hasAnthropic = providers.has('anthropic-api') || providers.has('anthropic-oauth');
|
|
310
276
|
if (hasAnthropic)
|
|
311
|
-
return 'claude-opus';
|
|
277
|
+
return 'anthropic/claude-opus-4-7';
|
|
312
278
|
if (providers.has('codex-oauth'))
|
|
313
|
-
return 'codex/gpt-5.
|
|
314
|
-
|
|
315
|
-
return 'kimi/kimi-for-coding';
|
|
316
|
-
if (providers.has('minimax-api'))
|
|
317
|
-
return 'minimax/MiniMax-M2.5';
|
|
318
|
-
return 'openai/gpt-4o';
|
|
279
|
+
return 'codex/gpt-5.5';
|
|
280
|
+
return 'anthropic/claude-opus-4-7';
|
|
319
281
|
}
|
|
320
282
|
function buildAliases(providers) {
|
|
321
|
-
// Always include well-known aliases so users can switch models easily
|
|
322
283
|
const aliases = {
|
|
323
|
-
'claude-fast': 'anthropic/claude-haiku-4-5',
|
|
324
|
-
'claude-think': 'anthropic/claude-sonnet-4-6',
|
|
325
|
-
'claude-opus': 'anthropic/claude-opus-4-6',
|
|
326
284
|
'codex5.1': 'codex/gpt-5.1-codex',
|
|
327
285
|
'codex5.2': 'codex/gpt-5.2-codex',
|
|
328
286
|
'codex5.3': 'codex/gpt-5.3-codex',
|
|
329
|
-
'
|
|
330
|
-
'kimi': 'kimi/kimi-for-coding',
|
|
287
|
+
'codex5.5': 'codex/gpt-5.5',
|
|
331
288
|
};
|
|
332
|
-
if (providers.has('openai-api')) {
|
|
333
|
-
aliases['gpt-fast'] = 'openai/gpt-4o-mini';
|
|
334
|
-
aliases.gpt = 'openai/gpt-4o';
|
|
335
|
-
}
|
|
336
289
|
if (providers.has('codex-oauth')) {
|
|
337
|
-
aliases.codex = 'codex/gpt-5.
|
|
338
|
-
}
|
|
339
|
-
if (providers.has('minimax-api')) {
|
|
340
|
-
aliases.minimax = 'minimax/MiniMax-M2.5';
|
|
341
|
-
}
|
|
342
|
-
if (providers.has('kimi-api')) {
|
|
343
|
-
aliases.kimi = 'kimi/kimi-for-coding';
|
|
290
|
+
aliases.codex = 'codex/gpt-5.5';
|
|
344
291
|
}
|
|
345
292
|
return aliases;
|
|
346
293
|
}
|
|
347
|
-
function buildEnvContent(telegramToken, providers, secrets, discordToken) {
|
|
294
|
+
function buildEnvContent(telegramToken, providers, secrets, discordToken, keychainRefs) {
|
|
348
295
|
const lines = ['# SkimpyClaw secrets'];
|
|
349
|
-
|
|
296
|
+
// Helper: only write to .env if NOT stored in Keychain
|
|
297
|
+
const isInKeychain = (key) => keychainRefs?.[key]?.includes('KEYCHAIN:');
|
|
298
|
+
if (providers.has('anthropic-api') && secrets.anthropicKey && !isInKeychain('anthropicKey')) {
|
|
350
299
|
lines.push(`ANTHROPIC_API_KEY=${secrets.anthropicKey}`);
|
|
351
300
|
}
|
|
352
|
-
if (providers.has('anthropic-oauth')) {
|
|
301
|
+
if (providers.has('anthropic-oauth') && !isInKeychain('oauthToken')) {
|
|
353
302
|
if (secrets.oauthToken) {
|
|
354
303
|
lines.push(`CLAUDE_CODE_OAUTH_TOKEN=${secrets.oauthToken}`);
|
|
355
304
|
}
|
|
@@ -358,17 +307,10 @@ function buildEnvContent(telegramToken, providers, secrets, discordToken) {
|
|
|
358
307
|
lines.push('CLAUDE_CODE_OAUTH_TOKEN=');
|
|
359
308
|
}
|
|
360
309
|
}
|
|
361
|
-
if (
|
|
362
|
-
lines.push(`
|
|
363
|
-
}
|
|
364
|
-
if (providers.has('minimax-api') && secrets.minimaxKey) {
|
|
365
|
-
lines.push(`MINIMAX_API_KEY=${secrets.minimaxKey}`);
|
|
366
|
-
}
|
|
367
|
-
if (providers.has('kimi-api') && secrets.kimiKey) {
|
|
368
|
-
lines.push(`KIMI_API_KEY=${secrets.kimiKey}`);
|
|
310
|
+
if (!isInKeychain('telegramToken')) {
|
|
311
|
+
lines.push(`TELEGRAM_BOT_TOKEN=${telegramToken}`);
|
|
369
312
|
}
|
|
370
|
-
|
|
371
|
-
if (discordToken) {
|
|
313
|
+
if (discordToken && !isInKeychain('discordToken')) {
|
|
372
314
|
lines.push(`DISCORD_BOT_TOKEN=${discordToken}`);
|
|
373
315
|
}
|
|
374
316
|
lines.push('');
|
|
@@ -376,7 +318,7 @@ function buildEnvContent(telegramToken, providers, secrets, discordToken) {
|
|
|
376
318
|
}
|
|
377
319
|
export function buildSetupConfig(input) {
|
|
378
320
|
const useDiscord = Boolean(input.discordToken);
|
|
379
|
-
const features = input.features ?? {
|
|
321
|
+
const features = input.features ?? { voice: false, mcp: false };
|
|
380
322
|
const starters = input.starters ?? {
|
|
381
323
|
cronTechNews: false,
|
|
382
324
|
cronWeather: false,
|
|
@@ -416,14 +358,14 @@ export function buildSetupConfig(input) {
|
|
|
416
358
|
},
|
|
417
359
|
},
|
|
418
360
|
models: {
|
|
419
|
-
providers: buildProviders(input.selectedProviders),
|
|
361
|
+
providers: buildProviders(input.selectedProviders, input.secretRefs),
|
|
420
362
|
aliases: buildAliases(input.selectedProviders),
|
|
421
363
|
},
|
|
422
364
|
channels: {
|
|
423
365
|
active: useDiscord ? 'discord' : 'telegram',
|
|
424
366
|
telegram: {
|
|
425
367
|
enabled: true,
|
|
426
|
-
token: '${TELEGRAM_BOT_TOKEN}',
|
|
368
|
+
token: input.secretRefs?.telegramToken || '${TELEGRAM_BOT_TOKEN}',
|
|
427
369
|
allowFrom: [parseInt(input.telegramId, 10) || input.telegramId],
|
|
428
370
|
dailyNotesDir: '${HOME}/.skimpyclaw/Daily Notes',
|
|
429
371
|
defaultAllowedPaths: allPaths,
|
|
@@ -432,12 +374,11 @@ export function buildSetupConfig(input) {
|
|
|
432
374
|
allowedPaths: allPaths,
|
|
433
375
|
maxIterations: 100,
|
|
434
376
|
bashTimeout: 15000,
|
|
435
|
-
...(features.browser ? { browser: { type: 'chromium', enabled: true, headless: true } } : {}),
|
|
436
377
|
},
|
|
437
378
|
},
|
|
438
379
|
discord: {
|
|
439
380
|
enabled: useDiscord,
|
|
440
|
-
token: useDiscord ? '${DISCORD_BOT_TOKEN}' : '',
|
|
381
|
+
token: useDiscord ? (input.secretRefs?.discordToken || '${DISCORD_BOT_TOKEN}') : '',
|
|
441
382
|
allowFrom: useDiscord ? [input.discordUserId || ''] : [],
|
|
442
383
|
defaultAllowedPaths: allPaths,
|
|
443
384
|
...(input.discordDefaultChannelId ? { defaultChannelId: input.discordDefaultChannelId } : {}),
|
|
@@ -446,7 +387,6 @@ export function buildSetupConfig(input) {
|
|
|
446
387
|
allowedPaths: allPaths,
|
|
447
388
|
maxIterations: 100,
|
|
448
389
|
bashTimeout: 15000,
|
|
449
|
-
...(features.browser ? { browser: { type: 'chromium', enabled: true, headless: false } } : {}),
|
|
450
390
|
},
|
|
451
391
|
},
|
|
452
392
|
},
|
|
@@ -461,7 +401,6 @@ export function buildSetupConfig(input) {
|
|
|
461
401
|
allowedPaths: allPaths,
|
|
462
402
|
maxIterations: 10,
|
|
463
403
|
bashTimeout: 15000,
|
|
464
|
-
...(features.browser ? { browser: { enabled: true } } : { browser: { enabled: false } }),
|
|
465
404
|
},
|
|
466
405
|
},
|
|
467
406
|
...(features.voice ? {
|
|
@@ -470,13 +409,6 @@ export function buildSetupConfig(input) {
|
|
|
470
409
|
defaultProvider: 'macos',
|
|
471
410
|
providers: {
|
|
472
411
|
macos: { tts: { voice: 'Samantha' } },
|
|
473
|
-
...(input.selectedProviders.has('openai-api') ? {
|
|
474
|
-
openai: {
|
|
475
|
-
apiKey: '${OPENAI_API_KEY}',
|
|
476
|
-
baseURL: 'https://api.openai.com/v1',
|
|
477
|
-
stt: { model: 'whisper-1' },
|
|
478
|
-
},
|
|
479
|
-
} : {}),
|
|
480
412
|
},
|
|
481
413
|
channels: {
|
|
482
414
|
telegram: { enabled: true, acceptVoice: true, sendVoice: true },
|
|
@@ -484,16 +416,6 @@ export function buildSetupConfig(input) {
|
|
|
484
416
|
},
|
|
485
417
|
},
|
|
486
418
|
} : {}),
|
|
487
|
-
...(features.sandbox ? {
|
|
488
|
-
sandbox: {
|
|
489
|
-
enabled: true,
|
|
490
|
-
image: 'skimpyclaw-sandbox',
|
|
491
|
-
cpus: 2,
|
|
492
|
-
memory: '2G',
|
|
493
|
-
network: 'bridge',
|
|
494
|
-
idleTimeoutMs: 3600000,
|
|
495
|
-
},
|
|
496
|
-
} : {}),
|
|
497
419
|
...(Object.keys(starterSkillEntries).length > 0 ? {
|
|
498
420
|
skills: {
|
|
499
421
|
enabled: true,
|
|
@@ -509,7 +431,7 @@ export function buildSetupArtifacts(input) {
|
|
|
509
431
|
const config = buildSetupConfig(input);
|
|
510
432
|
return {
|
|
511
433
|
configJson: JSON.stringify(config, null, 2),
|
|
512
|
-
envContent: buildEnvContent(input.telegramToken, input.selectedProviders, input.providerSecrets, input.discordToken),
|
|
434
|
+
envContent: buildEnvContent(input.telegramToken, input.selectedProviders, input.providerSecrets, input.discordToken, input.secretRefs),
|
|
513
435
|
config,
|
|
514
436
|
};
|
|
515
437
|
}
|
|
@@ -550,34 +472,6 @@ async function validateProviderAuth(providers, secrets) {
|
|
|
550
472
|
checks.push({ name: 'Anthropic API', ok: false, detail: toErrorMessage(err) });
|
|
551
473
|
}
|
|
552
474
|
}
|
|
553
|
-
if (providers.has('openai-api') && secrets.openaiKey) {
|
|
554
|
-
try {
|
|
555
|
-
const res = await quickFetch('https://api.openai.com/v1/models', {
|
|
556
|
-
headers: { authorization: `Bearer ${secrets.openaiKey}` },
|
|
557
|
-
});
|
|
558
|
-
checks.push({ name: 'OpenAI API', ok: res.ok, detail: res.ok ? 'auth ok' : `HTTP ${res.status}` });
|
|
559
|
-
}
|
|
560
|
-
catch (err) {
|
|
561
|
-
checks.push({ name: 'OpenAI API', ok: false, detail: toErrorMessage(err) });
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
if (providers.has('minimax-api') && secrets.minimaxKey) {
|
|
565
|
-
try {
|
|
566
|
-
const res = await quickFetch('https://api.minimax.io/anthropic/v1/messages', {
|
|
567
|
-
method: 'POST',
|
|
568
|
-
headers: {
|
|
569
|
-
authorization: `Bearer ${secrets.minimaxKey}`,
|
|
570
|
-
'content-type': 'application/json',
|
|
571
|
-
'anthropic-version': '2023-06-01',
|
|
572
|
-
},
|
|
573
|
-
body: JSON.stringify({ model: 'MiniMax-M2.5', max_tokens: 8, messages: [{ role: 'user', content: 'ping' }] }),
|
|
574
|
-
});
|
|
575
|
-
checks.push({ name: 'MiniMax API', ok: res.ok, detail: res.ok ? 'auth ok' : `HTTP ${res.status}` });
|
|
576
|
-
}
|
|
577
|
-
catch (err) {
|
|
578
|
-
checks.push({ name: 'MiniMax API', ok: false, detail: toErrorMessage(err) });
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
475
|
if (providers.has('codex-oauth')) {
|
|
582
476
|
const authPath = join(homedir(), '.codex', 'auth.json');
|
|
583
477
|
checks.push({ name: 'Codex OAuth', ok: existsSync(authPath), detail: existsSync(authPath) ? authPath : `missing ${authPath}` });
|
|
@@ -720,43 +614,9 @@ export async function runSetup(options = {}) {
|
|
|
720
614
|
}
|
|
721
615
|
statusOk('Acknowledged');
|
|
722
616
|
// 8. Optional Features
|
|
723
|
-
const existingBrowser = existing.config?.heartbeat?.tools?.browser?.enabled === true
|
|
724
|
-
|| existing.config?.channels?.telegram?.tools?.browser?.enabled === true;
|
|
725
617
|
const existingVoice = existing.config?.voice?.enabled === true;
|
|
726
618
|
sectionHeader('Optional Features');
|
|
727
|
-
//
|
|
728
|
-
const browserDefault = existingBrowser ? 'Y' : 'N';
|
|
729
|
-
const enableBrowser = /^y(es)?$/i.test(await ask(rl, ` Enable browser tool? (requires Chrome) [${existingBrowser ? 'Y/n' : 'y/N'}]: `) || browserDefault);
|
|
730
|
-
if (enableBrowser) {
|
|
731
|
-
const macChrome = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
|
732
|
-
const which = spawnSync('which', ['google-chrome'], { encoding: 'utf-8' });
|
|
733
|
-
if (which.status === 0 || existsSync(macChrome)) {
|
|
734
|
-
statusOk('Chrome detected');
|
|
735
|
-
}
|
|
736
|
-
else {
|
|
737
|
-
statusWarn('Chrome not found — browser tool may not work until Chrome is installed');
|
|
738
|
-
}
|
|
739
|
-
// Check for Playwright
|
|
740
|
-
const pw = spawnSync('npx', ['playwright', '--version'], { encoding: 'utf-8', timeout: 10000 });
|
|
741
|
-
if (pw.status === 0) {
|
|
742
|
-
statusOk('Playwright detected');
|
|
743
|
-
}
|
|
744
|
-
else {
|
|
745
|
-
console.log('');
|
|
746
|
-
console.log(' ┌─────────────────────────────────────────────────────────┐');
|
|
747
|
-
console.log(' │ Browser tool requires Playwright. Install it: │');
|
|
748
|
-
console.log(' │ │');
|
|
749
|
-
console.log(' │ npx playwright install chromium │');
|
|
750
|
-
console.log(' │ │');
|
|
751
|
-
console.log(' │ Without this, the browser tool will fail at runtime. │');
|
|
752
|
-
console.log(' └─────────────────────────────────────────────────────────┘');
|
|
753
|
-
console.log('');
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
else {
|
|
757
|
-
statusOk('browser disabled');
|
|
758
|
-
}
|
|
759
|
-
// 6b. Voice/TTS
|
|
619
|
+
// Voice/TTS
|
|
760
620
|
const voiceDefault = existingVoice ? 'Y' : 'N';
|
|
761
621
|
const enableVoice = /^y(es)?$/i.test(await ask(rl, ` Enable voice/TTS? (requires ffmpeg) [${existingVoice ? 'Y/n' : 'y/N'}]: `) || voiceDefault);
|
|
762
622
|
if (enableVoice) {
|
|
@@ -779,16 +639,12 @@ export async function runSetup(options = {}) {
|
|
|
779
639
|
else {
|
|
780
640
|
console.log('');
|
|
781
641
|
console.log(' ┌─────────────────────────────────────────────────────────┐');
|
|
782
|
-
console.log(' │ Voice transcription (STT) requires
|
|
642
|
+
console.log(' │ Voice transcription (STT) requires local whisper.cpp: │');
|
|
783
643
|
console.log(' │ │');
|
|
784
|
-
console.log(' │
|
|
644
|
+
console.log(' │ Local whisper.cpp (free, recommended) │');
|
|
785
645
|
console.log(' │ brew install whisper-cpp │');
|
|
786
646
|
console.log(' │ whisper-cpp-download-ggml-model small │');
|
|
787
647
|
console.log(' │ │');
|
|
788
|
-
console.log(' │ Option B: OpenAI Whisper API ($0.006/min) │');
|
|
789
|
-
console.log(' │ Add OPENAI_API_KEY to ~/.skimpyclaw/.env │');
|
|
790
|
-
console.log(' │ Config auto-includes openai.stt if provider selected │');
|
|
791
|
-
console.log(' │ │');
|
|
792
648
|
console.log(' │ Without either, voice messages cannot be transcribed. │');
|
|
793
649
|
console.log(' └─────────────────────────────────────────────────────────┘');
|
|
794
650
|
console.log('');
|
|
@@ -797,7 +653,7 @@ export async function runSetup(options = {}) {
|
|
|
797
653
|
else {
|
|
798
654
|
statusOk('voice disabled');
|
|
799
655
|
}
|
|
800
|
-
//
|
|
656
|
+
// MCP tools
|
|
801
657
|
console.log(' Install mcporter: https://github.com/steipete/mcporter');
|
|
802
658
|
const enableMcp = /^y(es)?$/i.test(await ask(rl, ' Enable MCP tools? (requires mcporter at ~/.mcporter/) [y/N]: '));
|
|
803
659
|
if (enableMcp) {
|
|
@@ -812,34 +668,9 @@ export async function runSetup(options = {}) {
|
|
|
812
668
|
else {
|
|
813
669
|
statusOk('MCP tools disabled');
|
|
814
670
|
}
|
|
815
|
-
// 6d. Sandbox (container isolation)
|
|
816
|
-
const existingSandbox = existing.config?.sandbox?.enabled === true;
|
|
817
|
-
const sandboxDefault = existingSandbox ? 'Y' : 'N';
|
|
818
|
-
const enableSandbox = /^y(es)?$/i.test(await ask(rl, ` Enable sandbox? (requires Docker or Apple Containers) [${existingSandbox ? 'Y/n' : 'y/N'}]: `) || sandboxDefault);
|
|
819
|
-
let detectedSandboxRuntime = null;
|
|
820
|
-
if (enableSandbox) {
|
|
821
|
-
const containerCli = spawnSync('which', ['container'], { encoding: 'utf-8' });
|
|
822
|
-
const docker = spawnSync('which', ['docker'], { encoding: 'utf-8' });
|
|
823
|
-
if (containerCli.status === 0) {
|
|
824
|
-
detectedSandboxRuntime = 'container';
|
|
825
|
-
statusOk('Apple Containers detected');
|
|
826
|
-
}
|
|
827
|
-
else if (docker.status === 0) {
|
|
828
|
-
detectedSandboxRuntime = 'docker';
|
|
829
|
-
statusOk('Docker detected');
|
|
830
|
-
}
|
|
831
|
-
else {
|
|
832
|
-
statusWarn('No container runtime found — sandbox features won\'t work until Docker or Apple Containers is installed');
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
else {
|
|
836
|
-
statusOk('sandbox disabled');
|
|
837
|
-
}
|
|
838
671
|
const features = {
|
|
839
|
-
browser: enableBrowser,
|
|
840
672
|
voice: enableVoice,
|
|
841
673
|
mcp: enableMcp,
|
|
842
|
-
sandbox: enableSandbox,
|
|
843
674
|
};
|
|
844
675
|
sectionHeader('Starter Packs (optional)');
|
|
845
676
|
const localTz = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
@@ -866,6 +697,11 @@ export async function runSetup(options = {}) {
|
|
|
866
697
|
skillWeather: false,
|
|
867
698
|
skillWebSearch: false,
|
|
868
699
|
};
|
|
700
|
+
// Store secrets in macOS Keychain when available (default on macOS)
|
|
701
|
+
const { refs: secretRefs, useKeychain, storedCount } = storeSecretsSecurely(providerSecrets, telegramToken, useDiscord ? discordToken : undefined);
|
|
702
|
+
if (useKeychain) {
|
|
703
|
+
statusOk(`${storedCount} secret(s) stored in macOS Keychain`);
|
|
704
|
+
}
|
|
869
705
|
const { envContent, config: generatedConfig } = buildSetupArtifacts({
|
|
870
706
|
workspaceDir: extraAllowedPaths[0] || join(homedir(), '.skimpyclaw'),
|
|
871
707
|
extraAllowedPaths,
|
|
@@ -877,6 +713,7 @@ export async function runSetup(options = {}) {
|
|
|
877
713
|
agentName,
|
|
878
714
|
selectedProviders,
|
|
879
715
|
providerSecrets,
|
|
716
|
+
secretRefs,
|
|
880
717
|
features,
|
|
881
718
|
starters,
|
|
882
719
|
});
|
|
@@ -908,9 +745,6 @@ export async function runSetup(options = {}) {
|
|
|
908
745
|
if (existing.config.langfuse) {
|
|
909
746
|
generatedConfig.langfuse = existing.config.langfuse;
|
|
910
747
|
}
|
|
911
|
-
if (existing.config.sandbox) {
|
|
912
|
-
generatedConfig.sandbox = existing.config.sandbox;
|
|
913
|
-
}
|
|
914
748
|
// Preserve voice provider config if voice was already configured
|
|
915
749
|
if (existing.config.voice?.providers && Object.keys(existing.config.voice.providers).length > 0) {
|
|
916
750
|
generatedConfig.voice = existing.config.voice;
|
|
@@ -927,16 +761,6 @@ export async function runSetup(options = {}) {
|
|
|
927
761
|
};
|
|
928
762
|
}
|
|
929
763
|
}
|
|
930
|
-
if (enableSandbox && generatedConfig.sandbox) {
|
|
931
|
-
const runtime = detectSandboxRuntime(detectedSandboxRuntime) || detectSandboxRuntime();
|
|
932
|
-
if (runtime) {
|
|
933
|
-
generatedConfig.sandbox.runtime = runtime;
|
|
934
|
-
generatedConfig.sandbox.network = defaultSandboxNetwork(runtime);
|
|
935
|
-
}
|
|
936
|
-
if (!generatedConfig.sandbox.image) {
|
|
937
|
-
generatedConfig.sandbox.image = 'skimpyclaw-sandbox:latest';
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
764
|
// Create directories
|
|
941
765
|
console.log('Creating directories...');
|
|
942
766
|
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
@@ -995,27 +819,6 @@ export async function runSetup(options = {}) {
|
|
|
995
819
|
writeFileSync(envPath, envContent);
|
|
996
820
|
console.log(`✓ Secrets written to ${envPath}`);
|
|
997
821
|
}
|
|
998
|
-
if (enableSandbox) {
|
|
999
|
-
sectionHeader('Sandbox Bootstrap');
|
|
1000
|
-
const sandboxCfg = generatedConfig.sandbox || {};
|
|
1001
|
-
const runtime = detectSandboxRuntime(sandboxCfg.runtime);
|
|
1002
|
-
const image = String(sandboxCfg.image || 'skimpyclaw-sandbox:latest');
|
|
1003
|
-
const network = String(sandboxCfg.network || (runtime ? defaultSandboxNetwork(runtime) : 'bridge'));
|
|
1004
|
-
if (!runtime) {
|
|
1005
|
-
statusWarn('Sandbox enabled, but no runtime detected. Run `skimpyclaw sandbox init` later.');
|
|
1006
|
-
}
|
|
1007
|
-
else {
|
|
1008
|
-
console.log(` Building sandbox image (${runtime}, network=${network})...`);
|
|
1009
|
-
const bootstrap = bootstrapSandbox(runtime, image, network);
|
|
1010
|
-
if (bootstrap.ok) {
|
|
1011
|
-
statusOk(`sandbox ready (${bootstrap.message})`);
|
|
1012
|
-
}
|
|
1013
|
-
else {
|
|
1014
|
-
statusWarn(bootstrap.message);
|
|
1015
|
-
console.log(` ${c.dim('You can retry later with: skimpyclaw sandbox init')}`);
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
822
|
// Update USER.md with name
|
|
1020
823
|
writeFileSync(join(AGENTS_DIR, 'USER.md'), `# USER.md - About ${userName}\n\nName: ${userName}\n\n## Preferences\n\n- Direct communication, no fluff\n\n## Routines\n\n- Morning: Review tasks and messages\n- EOD: Review completed work, plan tomorrow\n`);
|
|
1021
824
|
// Create launchd plist from template
|
|
@@ -1054,17 +857,6 @@ export async function runSetup(options = {}) {
|
|
|
1054
857
|
console.log('\nNext steps:');
|
|
1055
858
|
let step = 1;
|
|
1056
859
|
console.log(`${step++}. Review templates in ~/.skimpyclaw/agents/main/`);
|
|
1057
|
-
if (enableSandbox) {
|
|
1058
|
-
const runtimeHint = detectedSandboxRuntime === 'docker'
|
|
1059
|
-
? 'open -a Docker # or start Docker Desktop'
|
|
1060
|
-
: 'container system start';
|
|
1061
|
-
console.log(`${step++}. Start the container runtime (if not already running):`);
|
|
1062
|
-
console.log(` ${runtimeHint}`);
|
|
1063
|
-
console.log(`${step++}. Initialize the sandbox:`);
|
|
1064
|
-
console.log(' skimpyclaw sandbox init');
|
|
1065
|
-
console.log(`${step++}. Verify sandbox is working:`);
|
|
1066
|
-
console.log(' skimpyclaw sandbox doctor');
|
|
1067
|
-
}
|
|
1068
860
|
console.log(`${step++}. Start the daemon:`);
|
|
1069
861
|
console.log(' skimpyclaw start --daemon');
|
|
1070
862
|
console.log(`${step++}. Check health:`);
|
|
@@ -1072,10 +864,10 @@ export async function runSetup(options = {}) {
|
|
|
1072
864
|
console.log(`${step++}. Optional daemon controls: skimpyclaw stop | skimpyclaw restart`);
|
|
1073
865
|
console.log(`${step++}. Send /help in your ${useDiscord ? 'Discord bot DM/server' : 'Telegram bot'}`);
|
|
1074
866
|
console.log('');
|
|
1075
|
-
console.log(`${c.yellow('Note:')} The /
|
|
867
|
+
console.log(`${c.yellow('Note:')} The /code tool requires an external coding CLI on your PATH:`);
|
|
1076
868
|
console.log(' • Claude Code CLI → https://docs.anthropic.com/en/docs/claude-code');
|
|
1077
869
|
console.log(' • Codex CLI → https://github.com/openai/codex');
|
|
1078
|
-
console.log(' Install at least one to use code_with_agent
|
|
870
|
+
console.log(' Install at least one to use code_with_agent.');
|
|
1079
871
|
console.log('\n👙🦞 Enjoy!');
|
|
1080
872
|
}
|
|
1081
873
|
finally {
|