skimpyclaw 0.3.10 → 0.3.14
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__/channels.test.js +1 -1
- package/dist/__tests__/context-manager.test.js +219 -76
- package/dist/__tests__/providers-utils.test.js +2 -0
- package/dist/__tests__/sandbox-manager.test.js +25 -0
- package/dist/__tests__/sandbox-mount-security.test.js +8 -0
- package/dist/__tests__/setup.test.js +1 -1
- package/dist/__tests__/tools.test.js +11 -9
- package/dist/agent.js +1 -1
- package/dist/api.js +5 -0
- package/dist/channels/discord/handlers.d.ts +7 -0
- package/dist/channels/discord/handlers.js +479 -0
- package/dist/channels/discord/index.d.ts +8 -0
- package/dist/channels/discord/index.js +149 -0
- package/dist/channels/discord/types.d.ts +6 -0
- package/dist/channels/discord/types.js +17 -0
- package/dist/channels/discord/utils.d.ts +14 -0
- package/dist/channels/discord/utils.js +161 -0
- package/dist/channels/telegram/utils.d.ts +1 -1
- package/dist/channels/telegram/utils.js +7 -9
- package/dist/channels.js +1 -1
- package/dist/cli.js +8 -43
- package/dist/code-agents/parser.js +5 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +13 -0
- package/dist/cron.js +6 -3
- package/dist/heartbeat.js +11 -15
- package/dist/providers/anthropic.js +7 -1
- package/dist/providers/codex.js +8 -2
- package/dist/providers/context-manager.d.ts +37 -6
- package/dist/providers/context-manager.js +303 -47
- package/dist/providers/openai.js +8 -2
- package/dist/providers/utils.js +1 -1
- package/dist/sandbox/manager.js +11 -0
- package/dist/sandbox/mount-security.js +5 -1
- package/dist/sandbox/runtime.d.ts +1 -0
- package/dist/sandbox/runtime.js +5 -0
- package/dist/sandbox-utils.d.ts +6 -0
- package/dist/sandbox-utils.js +36 -0
- package/dist/security.js +4 -3
- package/dist/setup-templates.d.ts +14 -0
- package/dist/setup-templates.js +214 -0
- package/dist/setup.d.ts +1 -9
- package/dist/setup.js +3 -244
- package/dist/tools/bash-tool.js +11 -1
- package/dist/tools/definitions.d.ts +57 -0
- package/dist/tools/definitions.js +19 -1
- package/dist/tools/fetch-tool.d.ts +8 -0
- package/dist/tools/fetch-tool.js +80 -0
- package/dist/tools.d.ts +4 -2
- package/dist/tools.js +110 -62
- package/dist/types.d.ts +5 -0
- package/package.json +3 -4
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// Template and skill content for onboarding (extracted from setup.ts)
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
const CONFIG_DIR = join(homedir(), '.skimpyclaw');
|
|
6
|
+
export const REQUIRED_TEMPLATE_DEFAULTS = {
|
|
7
|
+
'SOUL.md': '# SOUL\n\nBe direct, resourceful, and helpful. Keep it concise.\n',
|
|
8
|
+
'IDENTITY.md': '# IDENTITY\n\nName: Claw\nEmoji: 👙🦞\n',
|
|
9
|
+
'USER.md': '# USER\n\nName: User\n',
|
|
10
|
+
'HEARTBEAT.md': '# HEARTBEAT\n\nIf nothing needs attention, reply HEARTBEAT_OK.\n',
|
|
11
|
+
};
|
|
12
|
+
export const STARTER_SKILL_TEMPLATES = {
|
|
13
|
+
'daily-notes': `---
|
|
14
|
+
name: daily-notes
|
|
15
|
+
description: Keep daily notes organized under the configured daily notes directory.
|
|
16
|
+
triggers: ["daily note", "standup", "plan day", "journal"]
|
|
17
|
+
priority: 90
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
When writing daily notes:
|
|
21
|
+
1. Use today's date in the file name if missing.
|
|
22
|
+
2. Include sections: Priorities, Schedule, Notes, Follow-ups.
|
|
23
|
+
3. Keep entries concise and actionable.
|
|
24
|
+
4. Avoid creating files outside the configured daily notes directory.
|
|
25
|
+
`,
|
|
26
|
+
'weather': `---
|
|
27
|
+
name: weather
|
|
28
|
+
description: Fetch and format weather data for daily briefings and quick checks.
|
|
29
|
+
triggers: ["weather", "forecast", "temperature", "rain"]
|
|
30
|
+
priority: 45
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
When asked about weather or generating a daily briefing:
|
|
34
|
+
1. Use web search to find current weather for the user's location.
|
|
35
|
+
2. Format as: conditions, high/low temps, precipitation chance.
|
|
36
|
+
3. Keep it to 2-3 sentences max.
|
|
37
|
+
4. Include any weather alerts if present.
|
|
38
|
+
5. For daily briefings: mention if rain is expected (affects outdoor plans).
|
|
39
|
+
`,
|
|
40
|
+
'web-search': `---
|
|
41
|
+
name: web-search
|
|
42
|
+
description: Search the web using the Browser tool. Opens DuckDuckGo, reads results, and returns findings.
|
|
43
|
+
triggers: ["search", "look up", "google", "find online", "web search"]
|
|
44
|
+
priority: 50
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
When asked to search the web:
|
|
48
|
+
1. Use the Browser tool to open https://html.duckduckgo.com/html/?q=<URL-encoded query>
|
|
49
|
+
2. Use getText to read the search results page.
|
|
50
|
+
3. If a specific result looks promising, open that URL and extract the relevant content.
|
|
51
|
+
4. Summarize findings concisely — include source URLs.
|
|
52
|
+
5. Close the browser when done.
|
|
53
|
+
|
|
54
|
+
Do NOT fabricate results. If the search returns nothing useful, say so.
|
|
55
|
+
`,
|
|
56
|
+
'duckduckgo-html-search': `---
|
|
57
|
+
name: duckduckgo-html-search
|
|
58
|
+
description: Search the web via DuckDuckGo HTML results using the Browser tool
|
|
59
|
+
emoji: 🦆
|
|
60
|
+
tags: [search, web, browser]
|
|
61
|
+
priority: 45
|
|
62
|
+
enabled: true
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
# DuckDuckGo HTML Search Skill
|
|
66
|
+
|
|
67
|
+
Use this skill when the user asks for web search, source gathering, or lightweight browsing.
|
|
68
|
+
|
|
69
|
+
## Priority rule
|
|
70
|
+
DuckDuckGo HTML via Browser is the default search path.
|
|
71
|
+
- Prefer DuckDuckGo first, even if \\\`$web_search\\\` is available.
|
|
72
|
+
- Use \\\`$web_search\\\` only when the user explicitly asks for it, DuckDuckGo is blocked, or Browser is unavailable.
|
|
73
|
+
|
|
74
|
+
## Default workflow
|
|
75
|
+
1. Build query URL: \\\`https://duckduckgo.com/html/?q=<urlencoded query>\\\`
|
|
76
|
+
2. Open the URL with Browser.
|
|
77
|
+
3. Wait for result anchors (\\\`a.result__a\\\`) or fallback body text.
|
|
78
|
+
4. Extract results using one Browser \\\`evaluate\\\` call when possible.
|
|
79
|
+
5. Return only actually extracted items (never pad count).
|
|
80
|
+
|
|
81
|
+
## Extraction requirements
|
|
82
|
+
For each result, capture when available:
|
|
83
|
+
- title
|
|
84
|
+
- url
|
|
85
|
+
- snippet
|
|
86
|
+
|
|
87
|
+
If a field is missing, set it to \\\`UNAVAILABLE\\\`.
|
|
88
|
+
|
|
89
|
+
## Integrity rules
|
|
90
|
+
- Never fabricate results.
|
|
91
|
+
- If the page blocks, fails, or no results render, return \\\`UNAVAILABLE\\\` and state why.
|
|
92
|
+
- Never mix real and invented entries.
|
|
93
|
+
- Include source URLs in output.
|
|
94
|
+
|
|
95
|
+
## Browser strategy
|
|
96
|
+
- Prefer one-page extraction via \\\`evaluate\\\`:
|
|
97
|
+
- Collect \\\`a.result__a\\\` for title + href
|
|
98
|
+
- Collect nearby snippet nodes (\\\`.result__snippet\\\`) when present
|
|
99
|
+
- Use minimal actions: open → waitFor → evaluate → optional screenshot.
|
|
100
|
+
- If selectors change, fallback to visible text extraction and clearly mark reduced confidence.
|
|
101
|
+
|
|
102
|
+
## Output format (concise)
|
|
103
|
+
- Query used
|
|
104
|
+
- Result count actually extracted
|
|
105
|
+
- Bulleted results with title + URL + snippet
|
|
106
|
+
- Notes section for failures/limits
|
|
107
|
+
|
|
108
|
+
## Safe defaults
|
|
109
|
+
- Default top results target: 5 (or user-specified)
|
|
110
|
+
- If user asks for deep research, gather multiple queries but keep each query's extraction explicit and separated.
|
|
111
|
+
`,
|
|
112
|
+
};
|
|
113
|
+
export function ensureCoreTemplates(agentDir) {
|
|
114
|
+
const created = [];
|
|
115
|
+
for (const [file, content] of Object.entries(REQUIRED_TEMPLATE_DEFAULTS)) {
|
|
116
|
+
const dst = join(agentDir, file);
|
|
117
|
+
if (!existsSync(dst)) {
|
|
118
|
+
writeFileSync(dst, content, 'utf-8');
|
|
119
|
+
created.push(file);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return created;
|
|
123
|
+
}
|
|
124
|
+
export function ensureStarterSkills(starters) {
|
|
125
|
+
const created = [];
|
|
126
|
+
const skillsDir = join(CONFIG_DIR, 'skills');
|
|
127
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
128
|
+
const requested = [];
|
|
129
|
+
if (starters.skillDailyNotes)
|
|
130
|
+
requested.push('daily-notes');
|
|
131
|
+
if (starters.skillWeather)
|
|
132
|
+
requested.push('weather');
|
|
133
|
+
if (starters.skillWebSearch)
|
|
134
|
+
requested.push('web-search');
|
|
135
|
+
for (const skillName of requested) {
|
|
136
|
+
const dir = join(skillsDir, skillName);
|
|
137
|
+
const skillPath = join(dir, 'SKILL.md');
|
|
138
|
+
if (!existsSync(skillPath)) {
|
|
139
|
+
mkdirSync(dir, { recursive: true });
|
|
140
|
+
writeFileSync(skillPath, STARTER_SKILL_TEMPLATES[skillName], 'utf-8');
|
|
141
|
+
created.push(skillName);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return created;
|
|
145
|
+
}
|
|
146
|
+
export function buildStarterCronJobs(starters) {
|
|
147
|
+
const jobs = [];
|
|
148
|
+
jobs.push({
|
|
149
|
+
id: 'memory-trim',
|
|
150
|
+
name: 'Memory Trim',
|
|
151
|
+
model: 'claude-fast',
|
|
152
|
+
schedule: {
|
|
153
|
+
kind: 'cron',
|
|
154
|
+
expr: '0 0,12 * * *',
|
|
155
|
+
tz: starters.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
156
|
+
},
|
|
157
|
+
payload: {
|
|
158
|
+
kind: 'agentTurn',
|
|
159
|
+
message: '~/.skimpyclaw/prompts/memory-trim.md',
|
|
160
|
+
tools: {
|
|
161
|
+
enabled: true,
|
|
162
|
+
allowedPaths: [`${homedir()}/.skimpyclaw`],
|
|
163
|
+
maxIterations: 30,
|
|
164
|
+
bashTimeout: 10000,
|
|
165
|
+
toolProfile: 'minimal',
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
if (starters.cronTechNews) {
|
|
170
|
+
jobs.push({
|
|
171
|
+
id: 'tech-digest',
|
|
172
|
+
name: 'Tech News',
|
|
173
|
+
schedule: {
|
|
174
|
+
kind: 'cron',
|
|
175
|
+
expr: '0 8 * * *',
|
|
176
|
+
tz: starters.timezone,
|
|
177
|
+
},
|
|
178
|
+
payload: {
|
|
179
|
+
kind: 'agentTurn',
|
|
180
|
+
message: 'Use the Browser tool to visit https://news.ycombinator.com and fetch today\'s top 10 stories. Reply with title, URL, and 1-line summary for each item.',
|
|
181
|
+
tools: {
|
|
182
|
+
enabled: true,
|
|
183
|
+
allowedPaths: [`${homedir()}/.skimpyclaw`],
|
|
184
|
+
maxIterations: 30,
|
|
185
|
+
bashTimeout: 30000,
|
|
186
|
+
browser: { enabled: true, headless: true },
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (starters.cronWeather) {
|
|
192
|
+
jobs.push({
|
|
193
|
+
id: 'weather',
|
|
194
|
+
name: 'Weather',
|
|
195
|
+
schedule: {
|
|
196
|
+
kind: 'cron',
|
|
197
|
+
expr: '0 7 * * *',
|
|
198
|
+
tz: starters.timezone,
|
|
199
|
+
},
|
|
200
|
+
payload: {
|
|
201
|
+
kind: 'agentTurn',
|
|
202
|
+
message: `Use the Browser tool to check current weather and today's forecast for ${starters.weatherLocation}. Keep it concise: current temp/conditions, highs/lows, precipitation chance, and 1 recommendation.`,
|
|
203
|
+
tools: {
|
|
204
|
+
enabled: true,
|
|
205
|
+
allowedPaths: [`${homedir()}/.skimpyclaw`],
|
|
206
|
+
maxIterations: 30,
|
|
207
|
+
bashTimeout: 30000,
|
|
208
|
+
browser: { enabled: true, headless: true },
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return jobs;
|
|
214
|
+
}
|
package/dist/setup.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SetupStarters } from './setup-templates.js';
|
|
1
2
|
interface SetupOptions {
|
|
2
3
|
dryRun?: boolean;
|
|
3
4
|
}
|
|
@@ -16,15 +17,6 @@ interface SetupFeatures {
|
|
|
16
17
|
mcp: boolean;
|
|
17
18
|
sandbox: boolean;
|
|
18
19
|
}
|
|
19
|
-
interface SetupStarters {
|
|
20
|
-
cronTechNews: boolean;
|
|
21
|
-
cronWeather: boolean;
|
|
22
|
-
timezone: string;
|
|
23
|
-
weatherLocation: string;
|
|
24
|
-
skillDailyNotes: boolean;
|
|
25
|
-
skillWeather: boolean;
|
|
26
|
-
skillWebSearch: boolean;
|
|
27
|
-
}
|
|
28
20
|
interface SetupBuildInput {
|
|
29
21
|
workspaceDir: string;
|
|
30
22
|
extraAllowedPaths?: string[];
|
package/dist/setup.js
CHANGED
|
@@ -8,6 +8,8 @@ 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 { detectSandboxRuntime, isSandboxRuntimeRunning, sandboxNetworkExists, defaultSandboxNetwork, } from './sandbox-utils.js';
|
|
12
|
+
import { ensureCoreTemplates, ensureStarterSkills, buildStarterCronJobs, } from './setup-templates.js';
|
|
11
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
14
|
const __dirname = dirname(__filename);
|
|
13
15
|
// ANSI color helpers (no chalk dependency)
|
|
@@ -57,46 +59,13 @@ function loadExistingSetup() {
|
|
|
57
59
|
}
|
|
58
60
|
return { config, env };
|
|
59
61
|
}
|
|
60
|
-
function detectSandboxRuntime(preferred) {
|
|
61
|
-
if (preferred) {
|
|
62
|
-
const check = spawnSync(preferred, ['--version'], { encoding: 'utf-8' });
|
|
63
|
-
return check.status === 0 ? preferred : null;
|
|
64
|
-
}
|
|
65
|
-
const containerCheck = spawnSync('container', ['--version'], { encoding: 'utf-8' });
|
|
66
|
-
if (containerCheck.status === 0)
|
|
67
|
-
return 'container';
|
|
68
|
-
const dockerCheck = spawnSync('docker', ['--version'], { encoding: 'utf-8' });
|
|
69
|
-
if (dockerCheck.status === 0)
|
|
70
|
-
return 'docker';
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
function sandboxRuntimeRunning(runtime) {
|
|
74
|
-
if (runtime === 'container') {
|
|
75
|
-
return spawnSync('container', ['system', 'status'], { encoding: 'utf-8' }).status === 0;
|
|
76
|
-
}
|
|
77
|
-
return spawnSync('docker', ['info'], { encoding: 'utf-8' }).status === 0;
|
|
78
|
-
}
|
|
79
|
-
function sandboxNetworkExists(runtime, network) {
|
|
80
|
-
if (runtime === 'container') {
|
|
81
|
-
const result = spawnSync('container', ['network', 'ls'], { encoding: 'utf-8' });
|
|
82
|
-
if (result.status !== 0)
|
|
83
|
-
return false;
|
|
84
|
-
return result.stdout
|
|
85
|
-
.split('\n')
|
|
86
|
-
.some((line) => line.trim().split(/\s+/)[0] === network);
|
|
87
|
-
}
|
|
88
|
-
return spawnSync('docker', ['network', 'inspect', network], { encoding: 'utf-8' }).status === 0;
|
|
89
|
-
}
|
|
90
|
-
function defaultSandboxNetwork(runtime) {
|
|
91
|
-
return runtime === 'container' ? 'default' : 'bridge';
|
|
92
|
-
}
|
|
93
62
|
function bootstrapSandbox(runtime, image, network) {
|
|
94
63
|
const sandboxDir = join(__dirname, '..', 'sandbox');
|
|
95
64
|
const dockerfile = join(sandboxDir, 'Dockerfile');
|
|
96
65
|
if (!existsSync(dockerfile)) {
|
|
97
66
|
return { ok: false, message: `Sandbox Dockerfile not found: ${dockerfile}` };
|
|
98
67
|
}
|
|
99
|
-
if (!
|
|
68
|
+
if (!isSandboxRuntimeRunning(runtime)) {
|
|
100
69
|
const hint = runtime === 'container'
|
|
101
70
|
? 'Run `container system start` and rerun onboarding.'
|
|
102
71
|
: 'Start Docker Desktop and rerun onboarding.';
|
|
@@ -223,76 +192,6 @@ async function askProviders(rl, existingProviders) {
|
|
|
223
192
|
return choices;
|
|
224
193
|
}
|
|
225
194
|
}
|
|
226
|
-
function buildStarterCronJobs(starters) {
|
|
227
|
-
const jobs = [];
|
|
228
|
-
// Memory trim is always included — runs 2x/day on a cheap model
|
|
229
|
-
jobs.push({
|
|
230
|
-
id: 'memory-trim',
|
|
231
|
-
name: 'Memory Trim',
|
|
232
|
-
model: 'claude-haiku',
|
|
233
|
-
schedule: {
|
|
234
|
-
kind: 'cron',
|
|
235
|
-
expr: '0 0,12 * * *',
|
|
236
|
-
tz: starters.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
237
|
-
},
|
|
238
|
-
payload: {
|
|
239
|
-
kind: 'agentTurn',
|
|
240
|
-
message: '~/.skimpyclaw/prompts/memory-trim.md',
|
|
241
|
-
tools: {
|
|
242
|
-
enabled: true,
|
|
243
|
-
allowedPaths: [`${homedir()}/.skimpyclaw`],
|
|
244
|
-
maxIterations: 30,
|
|
245
|
-
bashTimeout: 10000,
|
|
246
|
-
toolProfile: 'minimal',
|
|
247
|
-
},
|
|
248
|
-
},
|
|
249
|
-
});
|
|
250
|
-
if (starters.cronTechNews) {
|
|
251
|
-
jobs.push({
|
|
252
|
-
id: 'tech-digest',
|
|
253
|
-
name: 'Tech News',
|
|
254
|
-
schedule: {
|
|
255
|
-
kind: 'cron',
|
|
256
|
-
expr: '0 8 * * *',
|
|
257
|
-
tz: starters.timezone,
|
|
258
|
-
},
|
|
259
|
-
payload: {
|
|
260
|
-
kind: 'agentTurn',
|
|
261
|
-
message: 'Use the Browser tool to visit https://news.ycombinator.com and fetch today\'s top 10 stories. Reply with title, URL, and 1-line summary for each item.',
|
|
262
|
-
tools: {
|
|
263
|
-
enabled: true,
|
|
264
|
-
allowedPaths: [`${homedir()}/.skimpyclaw`],
|
|
265
|
-
maxIterations: 30,
|
|
266
|
-
bashTimeout: 30000,
|
|
267
|
-
browser: { enabled: true, headless: true },
|
|
268
|
-
},
|
|
269
|
-
},
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
if (starters.cronWeather) {
|
|
273
|
-
jobs.push({
|
|
274
|
-
id: 'weather',
|
|
275
|
-
name: 'Weather',
|
|
276
|
-
schedule: {
|
|
277
|
-
kind: 'cron',
|
|
278
|
-
expr: '0 7 * * *',
|
|
279
|
-
tz: starters.timezone,
|
|
280
|
-
},
|
|
281
|
-
payload: {
|
|
282
|
-
kind: 'agentTurn',
|
|
283
|
-
message: `Use the Browser tool to check current weather and today's forecast for ${starters.weatherLocation}. Keep it concise: current temp/conditions, highs/lows, precipitation chance, and 1 recommendation.`,
|
|
284
|
-
tools: {
|
|
285
|
-
enabled: true,
|
|
286
|
-
allowedPaths: [`${homedir()}/.skimpyclaw`],
|
|
287
|
-
maxIterations: 30,
|
|
288
|
-
bashTimeout: 30000,
|
|
289
|
-
browser: { enabled: true, headless: true },
|
|
290
|
-
},
|
|
291
|
-
},
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
return jobs;
|
|
295
|
-
}
|
|
296
195
|
async function collectProviderSecrets(rl, providers, existingEnv) {
|
|
297
196
|
const secrets = {};
|
|
298
197
|
const env = existingEnv || {};
|
|
@@ -614,146 +513,6 @@ export function buildSetupArtifacts(input) {
|
|
|
614
513
|
config,
|
|
615
514
|
};
|
|
616
515
|
}
|
|
617
|
-
const REQUIRED_TEMPLATE_DEFAULTS = {
|
|
618
|
-
'SOUL.md': '# SOUL\n\nBe direct, resourceful, and helpful. Keep it concise.\n',
|
|
619
|
-
'IDENTITY.md': '# IDENTITY\n\nName: Claw\nEmoji: 👙🦞\n',
|
|
620
|
-
'USER.md': '# USER\n\nName: User\n',
|
|
621
|
-
'HEARTBEAT.md': '# HEARTBEAT\n\nIf nothing needs attention, reply HEARTBEAT_OK.\n',
|
|
622
|
-
};
|
|
623
|
-
const STARTER_SKILL_TEMPLATES = {
|
|
624
|
-
'daily-notes': `---
|
|
625
|
-
name: daily-notes
|
|
626
|
-
description: Keep daily notes organized under the configured daily notes directory.
|
|
627
|
-
triggers: ["daily note", "standup", "plan day", "journal"]
|
|
628
|
-
priority: 90
|
|
629
|
-
---
|
|
630
|
-
|
|
631
|
-
When writing daily notes:
|
|
632
|
-
1. Use today's date in the file name if missing.
|
|
633
|
-
2. Include sections: Priorities, Schedule, Notes, Follow-ups.
|
|
634
|
-
3. Keep entries concise and actionable.
|
|
635
|
-
4. Avoid creating files outside the configured daily notes directory.
|
|
636
|
-
`,
|
|
637
|
-
'weather': `---
|
|
638
|
-
name: weather
|
|
639
|
-
description: Fetch and format weather data for daily briefings and quick checks.
|
|
640
|
-
triggers: ["weather", "forecast", "temperature", "rain"]
|
|
641
|
-
priority: 45
|
|
642
|
-
---
|
|
643
|
-
|
|
644
|
-
When asked about weather or generating a daily briefing:
|
|
645
|
-
1. Use web search to find current weather for the user's location.
|
|
646
|
-
2. Format as: conditions, high/low temps, precipitation chance.
|
|
647
|
-
3. Keep it to 2-3 sentences max.
|
|
648
|
-
4. Include any weather alerts if present.
|
|
649
|
-
5. For daily briefings: mention if rain is expected (affects outdoor plans).
|
|
650
|
-
`,
|
|
651
|
-
'web-search': `---
|
|
652
|
-
name: web-search
|
|
653
|
-
description: Search the web using the Browser tool. Opens DuckDuckGo, reads results, and returns findings.
|
|
654
|
-
triggers: ["search", "look up", "google", "find online", "web search"]
|
|
655
|
-
priority: 50
|
|
656
|
-
---
|
|
657
|
-
|
|
658
|
-
When asked to search the web:
|
|
659
|
-
1. Use the Browser tool to open https://html.duckduckgo.com/html/?q=<URL-encoded query>
|
|
660
|
-
2. Use getText to read the search results page.
|
|
661
|
-
3. If a specific result looks promising, open that URL and extract the relevant content.
|
|
662
|
-
4. Summarize findings concisely — include source URLs.
|
|
663
|
-
5. Close the browser when done.
|
|
664
|
-
|
|
665
|
-
Do NOT fabricate results. If the search returns nothing useful, say so.
|
|
666
|
-
`,
|
|
667
|
-
'duckduckgo-html-search': `---
|
|
668
|
-
name: duckduckgo-html-search
|
|
669
|
-
description: Search the web via DuckDuckGo HTML results using the Browser tool
|
|
670
|
-
emoji: 🦆
|
|
671
|
-
tags: [search, web, browser]
|
|
672
|
-
priority: 45
|
|
673
|
-
enabled: true
|
|
674
|
-
---
|
|
675
|
-
|
|
676
|
-
# DuckDuckGo HTML Search Skill
|
|
677
|
-
|
|
678
|
-
Use this skill when the user asks for web search, source gathering, or lightweight browsing.
|
|
679
|
-
|
|
680
|
-
## Priority rule
|
|
681
|
-
DuckDuckGo HTML via Browser is the default search path.
|
|
682
|
-
- Prefer DuckDuckGo first, even if \\\`$web_search\\\` is available.
|
|
683
|
-
- Use \\\`$web_search\\\` only when the user explicitly asks for it, DuckDuckGo is blocked, or Browser is unavailable.
|
|
684
|
-
|
|
685
|
-
## Default workflow
|
|
686
|
-
1. Build query URL: \\\`https://duckduckgo.com/html/?q=<urlencoded query>\\\`
|
|
687
|
-
2. Open the URL with Browser.
|
|
688
|
-
3. Wait for result anchors (\\\`a.result__a\\\`) or fallback body text.
|
|
689
|
-
4. Extract results using one Browser \\\`evaluate\\\` call when possible.
|
|
690
|
-
5. Return only actually extracted items (never pad count).
|
|
691
|
-
|
|
692
|
-
## Extraction requirements
|
|
693
|
-
For each result, capture when available:
|
|
694
|
-
- title
|
|
695
|
-
- url
|
|
696
|
-
- snippet
|
|
697
|
-
|
|
698
|
-
If a field is missing, set it to \\\`UNAVAILABLE\\\`.
|
|
699
|
-
|
|
700
|
-
## Integrity rules
|
|
701
|
-
- Never fabricate results.
|
|
702
|
-
- If the page blocks, fails, or no results render, return \\\`UNAVAILABLE\\\` and state why.
|
|
703
|
-
- Never mix real and invented entries.
|
|
704
|
-
- Include source URLs in output.
|
|
705
|
-
|
|
706
|
-
## Browser strategy
|
|
707
|
-
- Prefer one-page extraction via \\\`evaluate\\\`:
|
|
708
|
-
- Collect \\\`a.result__a\\\` for title + href
|
|
709
|
-
- Collect nearby snippet nodes (\\\`.result__snippet\\\`) when present
|
|
710
|
-
- Use minimal actions: open → waitFor → evaluate → optional screenshot.
|
|
711
|
-
- If selectors change, fallback to visible text extraction and clearly mark reduced confidence.
|
|
712
|
-
|
|
713
|
-
## Output format (concise)
|
|
714
|
-
- Query used
|
|
715
|
-
- Result count actually extracted
|
|
716
|
-
- Bulleted results with title + URL + snippet
|
|
717
|
-
- Notes section for failures/limits
|
|
718
|
-
|
|
719
|
-
## Safe defaults
|
|
720
|
-
- Default top results target: 5 (or user-specified)
|
|
721
|
-
- If user asks for deep research, gather multiple queries but keep each query's extraction explicit and separated.
|
|
722
|
-
`,
|
|
723
|
-
};
|
|
724
|
-
function ensureCoreTemplates(agentDir) {
|
|
725
|
-
const created = [];
|
|
726
|
-
for (const [file, content] of Object.entries(REQUIRED_TEMPLATE_DEFAULTS)) {
|
|
727
|
-
const dst = join(agentDir, file);
|
|
728
|
-
if (!existsSync(dst)) {
|
|
729
|
-
writeFileSync(dst, content, 'utf-8');
|
|
730
|
-
created.push(file);
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
return created;
|
|
734
|
-
}
|
|
735
|
-
function ensureStarterSkills(starters) {
|
|
736
|
-
const created = [];
|
|
737
|
-
const skillsDir = join(CONFIG_DIR, 'skills');
|
|
738
|
-
mkdirSync(skillsDir, { recursive: true });
|
|
739
|
-
const requested = ['duckduckgo-html-search']; // always installed
|
|
740
|
-
if (starters.skillDailyNotes)
|
|
741
|
-
requested.push('daily-notes');
|
|
742
|
-
if (starters.skillWeather)
|
|
743
|
-
requested.push('weather');
|
|
744
|
-
if (starters.skillWebSearch)
|
|
745
|
-
requested.push('web-search');
|
|
746
|
-
for (const skillName of requested) {
|
|
747
|
-
const dir = join(skillsDir, skillName);
|
|
748
|
-
const skillPath = join(dir, 'SKILL.md');
|
|
749
|
-
if (!existsSync(skillPath)) {
|
|
750
|
-
mkdirSync(dir, { recursive: true });
|
|
751
|
-
writeFileSync(skillPath, STARTER_SKILL_TEMPLATES[skillName], 'utf-8');
|
|
752
|
-
created.push(skillName);
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
return created;
|
|
756
|
-
}
|
|
757
516
|
async function quickFetch(url, init) {
|
|
758
517
|
return await fetch(url, { ...init, signal: AbortSignal.timeout(12000) });
|
|
759
518
|
}
|
package/dist/tools/bash-tool.js
CHANGED
|
@@ -9,14 +9,24 @@ const SENSITIVE_ENV_PATTERNS = [
|
|
|
9
9
|
/^ANTHROPIC_/i, /^OPENAI_/i, /^CLAUDE/i, /^CODEX_/i, /^MINIMAX_/i,
|
|
10
10
|
/^KIMI_/i, /^TOGETHER_/i, /^GROQ_/i, /^OPENROUTER_/i,
|
|
11
11
|
];
|
|
12
|
+
/** Env vars that match SENSITIVE_ENV_PATTERNS but should be kept (e.g. tool auth). */
|
|
13
|
+
const SENSITIVE_ENV_ALLOWLIST = new Set(['GH_TOKEN']);
|
|
14
|
+
/** Common tool paths that may be missing when launched as a service/daemon. */
|
|
15
|
+
const EXTRA_PATH_DIRS = ['/opt/homebrew/bin', '/opt/homebrew/sbin', '/usr/local/bin'];
|
|
12
16
|
/** Create a sanitized copy of process.env with secrets stripped. */
|
|
13
17
|
function sanitizeEnv() {
|
|
14
18
|
const env = { ...process.env };
|
|
15
19
|
for (const key of Object.keys(env)) {
|
|
16
|
-
if (SENSITIVE_ENV_PATTERNS.some(p => p.test(key))) {
|
|
20
|
+
if (!SENSITIVE_ENV_ALLOWLIST.has(key) && SENSITIVE_ENV_PATTERNS.some(p => p.test(key))) {
|
|
17
21
|
delete env[key];
|
|
18
22
|
}
|
|
19
23
|
}
|
|
24
|
+
// Ensure common tool directories are in PATH (daemon/service launches often have a minimal PATH)
|
|
25
|
+
const currentPath = env.PATH || '';
|
|
26
|
+
const missing = EXTRA_PATH_DIRS.filter(d => !currentPath.includes(d));
|
|
27
|
+
if (missing.length > 0) {
|
|
28
|
+
env.PATH = `${currentPath}:${missing.join(':')}`;
|
|
29
|
+
}
|
|
20
30
|
return env;
|
|
21
31
|
}
|
|
22
32
|
export async function executeBash(command, cwd, config, context) {
|
|
@@ -154,6 +154,35 @@ export declare const BROWSER_TOOL_DEFINITION: {
|
|
|
154
154
|
required: string[];
|
|
155
155
|
};
|
|
156
156
|
};
|
|
157
|
+
export declare const FETCH_TOOL_DEFINITION: {
|
|
158
|
+
name: string;
|
|
159
|
+
description: string;
|
|
160
|
+
input_schema: {
|
|
161
|
+
type: "object";
|
|
162
|
+
properties: {
|
|
163
|
+
url: {
|
|
164
|
+
type: string;
|
|
165
|
+
description: string;
|
|
166
|
+
};
|
|
167
|
+
method: {
|
|
168
|
+
type: string;
|
|
169
|
+
description: string;
|
|
170
|
+
};
|
|
171
|
+
headers: {
|
|
172
|
+
type: "object";
|
|
173
|
+
description: string;
|
|
174
|
+
additionalProperties: {
|
|
175
|
+
type: string;
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
body: {
|
|
179
|
+
type: string;
|
|
180
|
+
description: string;
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
required: string[];
|
|
184
|
+
};
|
|
185
|
+
};
|
|
157
186
|
export declare const TOOL_DEFINITIONS: ({
|
|
158
187
|
name: string;
|
|
159
188
|
description: string;
|
|
@@ -304,6 +333,34 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
304
333
|
};
|
|
305
334
|
required: string[];
|
|
306
335
|
};
|
|
336
|
+
} | {
|
|
337
|
+
name: string;
|
|
338
|
+
description: string;
|
|
339
|
+
input_schema: {
|
|
340
|
+
type: "object";
|
|
341
|
+
properties: {
|
|
342
|
+
url: {
|
|
343
|
+
type: string;
|
|
344
|
+
description: string;
|
|
345
|
+
};
|
|
346
|
+
method: {
|
|
347
|
+
type: string;
|
|
348
|
+
description: string;
|
|
349
|
+
};
|
|
350
|
+
headers: {
|
|
351
|
+
type: "object";
|
|
352
|
+
description: string;
|
|
353
|
+
additionalProperties: {
|
|
354
|
+
type: string;
|
|
355
|
+
};
|
|
356
|
+
};
|
|
357
|
+
body: {
|
|
358
|
+
type: string;
|
|
359
|
+
description: string;
|
|
360
|
+
};
|
|
361
|
+
};
|
|
362
|
+
required: string[];
|
|
363
|
+
};
|
|
307
364
|
})[];
|
|
308
365
|
export declare const CODE_WITH_AGENT_TOOL: {
|
|
309
366
|
name: string;
|
|
@@ -110,8 +110,26 @@ export const BROWSER_TOOL_DEFINITION = {
|
|
|
110
110
|
required: ['action'],
|
|
111
111
|
},
|
|
112
112
|
};
|
|
113
|
+
export const FETCH_TOOL_DEFINITION = {
|
|
114
|
+
name: 'Fetch',
|
|
115
|
+
description: 'Make an HTTP request and return the response. HTML is auto-converted to plain text. Use for APIs, web search (e.g. https://duckduckgo.com/html/?q=your+query), or fetching page content. Prefer over Browser for simple requests.',
|
|
116
|
+
input_schema: {
|
|
117
|
+
type: 'object',
|
|
118
|
+
properties: {
|
|
119
|
+
url: { type: 'string', description: 'URL to fetch' },
|
|
120
|
+
method: { type: 'string', description: 'HTTP method (GET, POST, PUT, DELETE, PATCH). Default: GET' },
|
|
121
|
+
headers: {
|
|
122
|
+
type: 'object',
|
|
123
|
+
description: 'Request headers (e.g. {"Authorization": "Bearer ..."})',
|
|
124
|
+
additionalProperties: { type: 'string' },
|
|
125
|
+
},
|
|
126
|
+
body: { type: 'string', description: 'Request body (for POST/PUT/PATCH)' },
|
|
127
|
+
},
|
|
128
|
+
required: ['url'],
|
|
129
|
+
},
|
|
130
|
+
};
|
|
113
131
|
// Legacy export for backward compat — static list (built-ins + browser + no MCP)
|
|
114
|
-
export const TOOL_DEFINITIONS = [...BUILTIN_TOOL_DEFINITIONS, BROWSER_TOOL_DEFINITION];
|
|
132
|
+
export const TOOL_DEFINITIONS = [...BUILTIN_TOOL_DEFINITIONS, BROWSER_TOOL_DEFINITION, FETCH_TOOL_DEFINITION];
|
|
115
133
|
export const CODE_WITH_AGENT_TOOL = {
|
|
116
134
|
name: 'code_with_agent',
|
|
117
135
|
description: 'Delegate a coding task to a coding agent CLI (Claude Code or Codex). The agent will edit files, run commands, and return results. Always use this for code changes instead of writing code directly.',
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ToolConfig } from '../types.js';
|
|
2
|
+
export interface FetchInput {
|
|
3
|
+
url: string;
|
|
4
|
+
method?: string;
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
body?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function executeFetch(input: FetchInput, _config: ToolConfig): Promise<string>;
|