@vesk/agentic 0.2.11
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 +53 -0
- package/dist/checkpoints.d.ts +155 -0
- package/dist/checkpoints.d.ts.map +1 -0
- package/dist/checkpoints.js +394 -0
- package/dist/config.d.ts +57 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +399 -0
- package/dist/context.d.ts +21 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +64 -0
- package/dist/dev-api.d.ts +85 -0
- package/dist/dev-api.d.ts.map +1 -0
- package/dist/dev-api.js +942 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/loop.d.ts +156 -0
- package/dist/loop.d.ts.map +1 -0
- package/dist/loop.js +178 -0
- package/dist/permissions.d.ts +14 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +74 -0
- package/dist/plugin.d.ts +38 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +28 -0
- package/dist/providers/anthropic.d.ts +13 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +100 -0
- package/dist/providers/google.d.ts +12 -0
- package/dist/providers/google.d.ts.map +1 -0
- package/dist/providers/google.js +87 -0
- package/dist/providers/ollama.d.ts +11 -0
- package/dist/providers/ollama.d.ts.map +1 -0
- package/dist/providers/ollama.js +61 -0
- package/dist/providers/openai.d.ts +12 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +193 -0
- package/dist/providers/registry.d.ts +7 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +33 -0
- package/dist/providers/types.d.ts +28 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +15 -0
- package/dist/slash.d.ts +18 -0
- package/dist/slash.d.ts.map +1 -0
- package/dist/slash.js +77 -0
- package/dist/tools/browser.d.ts +3 -0
- package/dist/tools/browser.d.ts.map +1 -0
- package/dist/tools/browser.js +289 -0
- package/dist/tools/command.d.ts +14 -0
- package/dist/tools/command.d.ts.map +1 -0
- package/dist/tools/command.js +55 -0
- package/dist/tools/fs.d.ts +10 -0
- package/dist/tools/fs.d.ts.map +1 -0
- package/dist/tools/fs.js +142 -0
- package/dist/tools/vesk.d.ts +22 -0
- package/dist/tools/vesk.d.ts.map +1 -0
- package/dist/tools/vesk.js +828 -0
- package/dist/tools/web.d.ts +3 -0
- package/dist/tools/web.d.ts.map +1 -0
- package/dist/tools/web.js +111 -0
- package/package.json +47 -0
package/dist/slash.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slash command parser for devtool agentic tab.
|
|
3
|
+
* Pure string ops, no regex.
|
|
4
|
+
*/
|
|
5
|
+
export const SLASH_COMMANDS = [
|
|
6
|
+
{ name: '/provider', description: 'switch provider', usage: '/provider <openai|anthropic|google|ollama>' },
|
|
7
|
+
{ name: '/model', description: 'switch or list models', usage: '/model [name] (no arg = list)' },
|
|
8
|
+
{ name: '/models', description: 'list models for current provider', usage: '/models' },
|
|
9
|
+
{ name: '/tools', description: 'list available tools', usage: '/tools' },
|
|
10
|
+
{ name: '/tool', description: 'show tool details', usage: '/tool <name>' },
|
|
11
|
+
{ name: '/commands', description: 'list slash commands', usage: '/commands' },
|
|
12
|
+
{ name: '/mode', description: 'switch mode', usage: '/mode <explore|debug|agent>' },
|
|
13
|
+
{ name: '/clear', description: 'clear chat', usage: '/clear' },
|
|
14
|
+
{ name: '/help', description: 'show help', usage: '/help' },
|
|
15
|
+
{ name: '/history', description: 'show checkpoints', usage: '/history' },
|
|
16
|
+
{ name: '/rollback', description: 'rollback to checkpoint', usage: '/rollback <id>' },
|
|
17
|
+
{ name: '/checkpoint', description: 'create checkpoint', usage: '/checkpoint [message]' },
|
|
18
|
+
{ name: '/config', description: 'show agentic config', usage: '/config' },
|
|
19
|
+
];
|
|
20
|
+
export function parseSlash(input) {
|
|
21
|
+
const trimmed = input.trim();
|
|
22
|
+
if (!trimmed.startsWith('/'))
|
|
23
|
+
return null;
|
|
24
|
+
const spaceIdx = trimmed.indexOf(' ');
|
|
25
|
+
let cmdStr;
|
|
26
|
+
let rest;
|
|
27
|
+
if (spaceIdx === -1) {
|
|
28
|
+
cmdStr = trimmed.slice(1);
|
|
29
|
+
rest = '';
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
cmdStr = trimmed.slice(1, spaceIdx);
|
|
33
|
+
rest = trimmed.slice(spaceIdx + 1).trim();
|
|
34
|
+
}
|
|
35
|
+
const cmd = cmdStr.toLowerCase();
|
|
36
|
+
const known = SLASH_COMMANDS.map(c => c.name.slice(1));
|
|
37
|
+
if (!known.includes(cmd))
|
|
38
|
+
return { cmd: cmd, args: [], raw: trimmed };
|
|
39
|
+
const args = [];
|
|
40
|
+
if (rest) {
|
|
41
|
+
let cur = '';
|
|
42
|
+
let inQuote = false;
|
|
43
|
+
let quoteChar = '';
|
|
44
|
+
for (let i = 0; i < rest.length; i++) {
|
|
45
|
+
const ch = rest[i];
|
|
46
|
+
if (!inQuote && (ch === '"' || ch === "'")) {
|
|
47
|
+
inQuote = true;
|
|
48
|
+
quoteChar = ch;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (inQuote && ch === quoteChar) {
|
|
52
|
+
inQuote = false;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (!inQuote && ch === ' ') {
|
|
56
|
+
if (cur) {
|
|
57
|
+
args.push(cur);
|
|
58
|
+
cur = '';
|
|
59
|
+
}
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
cur += ch;
|
|
63
|
+
}
|
|
64
|
+
if (cur)
|
|
65
|
+
args.push(cur);
|
|
66
|
+
}
|
|
67
|
+
return { cmd, args, raw: trimmed };
|
|
68
|
+
}
|
|
69
|
+
export function helpText() {
|
|
70
|
+
let out = 'Available slash commands:\n';
|
|
71
|
+
for (const c of SLASH_COMMANDS)
|
|
72
|
+
out += `${c.name.padEnd(14)} ${c.description} (${c.usage})\n`;
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
export function isSlash(input) {
|
|
76
|
+
return input.trim().startsWith('/');
|
|
77
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/tools/browser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAkDvC,wBAAgB,kBAAkB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,CA2N9D"}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
// Global bridge injected by dev server when available (set via globalThis.__vesk_browser_bridge)
|
|
5
|
+
function getBridge() {
|
|
6
|
+
const g = globalThis;
|
|
7
|
+
return g.__vesk_browser_bridge || null;
|
|
8
|
+
}
|
|
9
|
+
async function ensurePuppeteerInstalled(projectDir) {
|
|
10
|
+
// Check if already installed
|
|
11
|
+
const puppeteerPath = resolve(projectDir, 'node_modules', 'puppeteer');
|
|
12
|
+
const corePath = resolve(projectDir, 'node_modules', 'puppeteer-core');
|
|
13
|
+
if (existsSync(puppeteerPath) || existsSync(corePath)) {
|
|
14
|
+
return { ok: true, message: 'puppeteer already installed' };
|
|
15
|
+
}
|
|
16
|
+
// Try to install puppeteer (includes chromium) as devDep
|
|
17
|
+
return new Promise((res) => {
|
|
18
|
+
const child = spawn('npm', ['install', '-D', 'puppeteer'], {
|
|
19
|
+
cwd: projectDir,
|
|
20
|
+
stdio: 'pipe',
|
|
21
|
+
shell: false,
|
|
22
|
+
});
|
|
23
|
+
let out = '';
|
|
24
|
+
let err = '';
|
|
25
|
+
child.stdout?.on('data', (d) => (out += String(d)));
|
|
26
|
+
child.stderr?.on('data', (d) => (err += String(d)));
|
|
27
|
+
const timer = setTimeout(() => {
|
|
28
|
+
try {
|
|
29
|
+
child.kill();
|
|
30
|
+
}
|
|
31
|
+
catch { }
|
|
32
|
+
res({ ok: false, message: 'install timed out after 120s' });
|
|
33
|
+
}, 120_000);
|
|
34
|
+
child.on('close', (code) => {
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
if (code === 0)
|
|
37
|
+
res({ ok: true, message: 'puppeteer installed successfully\n' + out.slice(-500) });
|
|
38
|
+
else
|
|
39
|
+
res({ ok: false, message: `npm install failed code=${code}\n${err.slice(-1000)}` });
|
|
40
|
+
});
|
|
41
|
+
child.on('error', (e) => {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
res({ ok: false, message: `spawn failed: ${e.message}` });
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
export function createBrowserTools(projectDir) {
|
|
48
|
+
const dir = projectDir || process.cwd();
|
|
49
|
+
return [
|
|
50
|
+
{
|
|
51
|
+
name: 'browser.open',
|
|
52
|
+
description: 'Open a URL in the users browser first via devtools (if connected), otherwise via puppeteer (auto-installs if needed). Returns rendered text. The agent should prefer this for JS-heavy pages, inspection, and any browser task.',
|
|
53
|
+
parameters: {
|
|
54
|
+
type: 'object',
|
|
55
|
+
properties: {
|
|
56
|
+
url: { type: 'string', description: 'URL to open (http/https)' },
|
|
57
|
+
waitMs: { type: 'number', description: 'Wait after load (ms, default 1500)' },
|
|
58
|
+
},
|
|
59
|
+
required: ['url'],
|
|
60
|
+
},
|
|
61
|
+
async execute(args) {
|
|
62
|
+
const url = String(args.url || '').trim();
|
|
63
|
+
const waitMs = Math.min(15000, Math.max(0, Number(args.waitMs) || 1500));
|
|
64
|
+
if (!url)
|
|
65
|
+
return JSON.stringify({ error: 'missing url' });
|
|
66
|
+
if (!url.startsWith('http://') && !url.startsWith('https://'))
|
|
67
|
+
return JSON.stringify({ error: 'only http/https allowed' });
|
|
68
|
+
// 1) Try devtools bridge first (users browser)
|
|
69
|
+
const bridge = getBridge();
|
|
70
|
+
if (bridge && bridge.hasClients()) {
|
|
71
|
+
try {
|
|
72
|
+
const result = await bridge.request({ action: 'open', url, waitMs }, 15000);
|
|
73
|
+
if (result && typeof result.text === 'string')
|
|
74
|
+
return String(result.text).slice(0, 12000);
|
|
75
|
+
if (result && typeof result.error === 'string') {
|
|
76
|
+
// fall through to puppeteer
|
|
77
|
+
}
|
|
78
|
+
else if (result)
|
|
79
|
+
return JSON.stringify(result).slice(0, 12000);
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
// bridge failed, fall through
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// 2) Try puppeteer-core / puppeteer if available
|
|
86
|
+
try {
|
|
87
|
+
// @ts-ignore - optional dep, installed on demand
|
|
88
|
+
// @ts-ignore
|
|
89
|
+
const puppeteer = await import('puppeteer').catch(() => null);
|
|
90
|
+
// @ts-ignore - optional dep
|
|
91
|
+
const core = !puppeteer ? await import('puppeteer-core').catch(() => null) : null;
|
|
92
|
+
const impl = puppeteer || core;
|
|
93
|
+
if (impl && typeof impl.launch === 'function') {
|
|
94
|
+
const executablePath = process.env.CHROMIUM_PATH || process.env.PUPPETEER_EXECUTABLE_PATH || undefined;
|
|
95
|
+
const launchOpts = { headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] };
|
|
96
|
+
if (executablePath)
|
|
97
|
+
launchOpts.executablePath = executablePath;
|
|
98
|
+
const browser = await impl.launch(launchOpts);
|
|
99
|
+
const page = await browser.newPage();
|
|
100
|
+
await page.goto(url, { waitUntil: 'networkidle2', timeout: 20000 });
|
|
101
|
+
if (waitMs)
|
|
102
|
+
await new Promise(r => setTimeout(r, waitMs));
|
|
103
|
+
const text = await page.evaluate(() => document.body.innerText.slice(0, 12000));
|
|
104
|
+
await page.close();
|
|
105
|
+
await browser.close();
|
|
106
|
+
return text;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
// puppeteer launch failed, will try install
|
|
111
|
+
}
|
|
112
|
+
// 3) Auto-install puppeteer if not present and we have a projectDir
|
|
113
|
+
if (dir) {
|
|
114
|
+
const install = await ensurePuppeteerInstalled(dir);
|
|
115
|
+
if (install.ok) {
|
|
116
|
+
try {
|
|
117
|
+
// @ts-ignore
|
|
118
|
+
const puppeteer = await import('puppeteer');
|
|
119
|
+
const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] });
|
|
120
|
+
const page = await browser.newPage();
|
|
121
|
+
await page.goto(url, { waitUntil: 'networkidle2', timeout: 20000 });
|
|
122
|
+
if (waitMs)
|
|
123
|
+
await new Promise(r => setTimeout(r, waitMs));
|
|
124
|
+
const text = await page.evaluate(() => document.body.innerText.slice(0, 12000));
|
|
125
|
+
await page.close();
|
|
126
|
+
await browser.close();
|
|
127
|
+
return text + '\n\n[installed puppeteer automatically]';
|
|
128
|
+
}
|
|
129
|
+
catch (e2) {
|
|
130
|
+
// fall through to fetch
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// install failed, fall through but include message
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// 4) Final fallback: fetch and strip html
|
|
138
|
+
try {
|
|
139
|
+
const res = await fetch(url, { headers: { 'User-Agent': 'vesk-agentic/0.2.10' } });
|
|
140
|
+
if (!res.ok)
|
|
141
|
+
return JSON.stringify({ error: `fetch failed: ${res.status} ${res.statusText}`, hint: 'tried devtools bridge, puppeteer, and fetch — all failed' });
|
|
142
|
+
const html = await res.text();
|
|
143
|
+
const text = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '').replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '').replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 12000);
|
|
144
|
+
return text + '\n\n[fallback: fetch strip — for full JS rendering install puppeteer or connect a browser devtools]';
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
147
|
+
return JSON.stringify({ error: e instanceof Error ? e.message : String(e), hint: 'no browser available and fetch failed' });
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: 'browser.inspect',
|
|
153
|
+
description: 'Inspect a page/element via users browser devtools first, then puppeteer. Can get outerHTML, styles, source, network, etc.',
|
|
154
|
+
parameters: {
|
|
155
|
+
type: 'object',
|
|
156
|
+
properties: {
|
|
157
|
+
url: { type: 'string', description: 'URL to inspect (optional if already open)' },
|
|
158
|
+
selector: { type: 'string', description: 'CSS selector to inspect (e.g. "#app", ".btn")' },
|
|
159
|
+
what: { type: 'string', description: 'what to inspect: html, text, styles, attributes, source, network, js', enum: ['html', 'text', 'styles', 'attributes', 'source', 'network', 'js'] },
|
|
160
|
+
js: { type: 'string', description: 'JS code to evaluate (when what=js)' },
|
|
161
|
+
},
|
|
162
|
+
required: [],
|
|
163
|
+
},
|
|
164
|
+
async execute(args) {
|
|
165
|
+
const url = args.url ? String(args.url) : undefined;
|
|
166
|
+
const selector = String(args.selector || 'body');
|
|
167
|
+
const what = String(args.what || 'html');
|
|
168
|
+
const js = args.js ? String(args.js) : undefined;
|
|
169
|
+
const bridge = getBridge();
|
|
170
|
+
if (bridge && bridge.hasClients()) {
|
|
171
|
+
try {
|
|
172
|
+
const res = await bridge.request({ action: 'inspect', url, selector, what, js }, 15000);
|
|
173
|
+
if (res && typeof res.result === 'string')
|
|
174
|
+
return String(res.result).slice(0, 15000);
|
|
175
|
+
if (res)
|
|
176
|
+
return JSON.stringify(res, null, 2).slice(0, 15000);
|
|
177
|
+
}
|
|
178
|
+
catch { }
|
|
179
|
+
}
|
|
180
|
+
// Fallback to fetch + basic inspection
|
|
181
|
+
if (what === 'js' && js) {
|
|
182
|
+
return JSON.stringify({ error: 'js evaluation requires a browser — no devtools client connected and puppeteer not configured. The agent will install puppeteer.' });
|
|
183
|
+
}
|
|
184
|
+
if (url) {
|
|
185
|
+
try {
|
|
186
|
+
const res = await fetch(url, { headers: { 'User-Agent': 'vesk-agentic/0.2.10' } });
|
|
187
|
+
const html = await res.text();
|
|
188
|
+
if (what === 'source')
|
|
189
|
+
return html.slice(0, 15000);
|
|
190
|
+
if (what === 'html') {
|
|
191
|
+
// naive selector: just return whole html if selector is body
|
|
192
|
+
return html.slice(0, 15000);
|
|
193
|
+
}
|
|
194
|
+
const text = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '').replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 12000);
|
|
195
|
+
return text;
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
return JSON.stringify({ error: e instanceof Error ? e.message : String(e) });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return JSON.stringify({ error: 'no url and no devtools browser connected — cannot inspect without a page' });
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: 'browser.eval',
|
|
206
|
+
description: 'Evaluate JavaScript in the users browser (via devtools) or puppeteer. Use for debugging, styles, networks.',
|
|
207
|
+
parameters: {
|
|
208
|
+
type: 'object',
|
|
209
|
+
properties: {
|
|
210
|
+
js: { type: 'string', description: 'JS code to evaluate (return value will be stringified)' },
|
|
211
|
+
url: { type: 'string', description: 'URL to open first (optional)' },
|
|
212
|
+
},
|
|
213
|
+
required: ['js'],
|
|
214
|
+
},
|
|
215
|
+
async execute(args) {
|
|
216
|
+
const js = String(args.js || '');
|
|
217
|
+
const url = args.url ? String(args.url) : undefined;
|
|
218
|
+
if (!js)
|
|
219
|
+
return JSON.stringify({ error: 'missing js' });
|
|
220
|
+
const bridge = getBridge();
|
|
221
|
+
if (bridge && bridge.hasClients()) {
|
|
222
|
+
try {
|
|
223
|
+
const res = await bridge.request({ action: 'eval', js, url }, 15000);
|
|
224
|
+
if (res && typeof res.result !== 'undefined')
|
|
225
|
+
return typeof res.result === 'string' ? res.result.slice(0, 15000) : JSON.stringify(res.result, null, 2).slice(0, 15000);
|
|
226
|
+
if (res && typeof res.error === 'string')
|
|
227
|
+
return JSON.stringify({ error: res.error });
|
|
228
|
+
}
|
|
229
|
+
catch { }
|
|
230
|
+
}
|
|
231
|
+
// Fallback: try puppeteer
|
|
232
|
+
try {
|
|
233
|
+
// @ts-ignore
|
|
234
|
+
const puppeteer = await import('puppeteer').catch(() => null);
|
|
235
|
+
if (puppeteer) {
|
|
236
|
+
// would need to launch, but for eval we need a page
|
|
237
|
+
return JSON.stringify({ error: 'eval requires devtools browser — puppeteer fallback not yet wired for eval, will install and retry' });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch { }
|
|
241
|
+
return JSON.stringify({ error: 'no browser available for eval — connect a browser with devtools or ensure puppeteer is installed' });
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: 'browser.screenshot',
|
|
246
|
+
description: 'Take a screenshot via users browser devtools or puppeteer (auto-installs if needed). Returns base64 or note.',
|
|
247
|
+
parameters: {
|
|
248
|
+
type: 'object',
|
|
249
|
+
properties: { url: { type: 'string', description: 'URL to screenshot' } },
|
|
250
|
+
required: ['url'],
|
|
251
|
+
},
|
|
252
|
+
async execute(args) {
|
|
253
|
+
const url = String(args.url || '').trim();
|
|
254
|
+
if (!url)
|
|
255
|
+
return JSON.stringify({ error: 'missing url' });
|
|
256
|
+
const bridge = getBridge();
|
|
257
|
+
if (bridge && bridge.hasClients()) {
|
|
258
|
+
try {
|
|
259
|
+
const res = await bridge.request({ action: 'screenshot', url }, 20000);
|
|
260
|
+
if (res && typeof res.base64 === 'string')
|
|
261
|
+
return JSON.stringify({ base64: res.base64.slice(0, 20000) + '...' });
|
|
262
|
+
}
|
|
263
|
+
catch { }
|
|
264
|
+
}
|
|
265
|
+
// Try puppeteer
|
|
266
|
+
try {
|
|
267
|
+
// @ts-ignore
|
|
268
|
+
const puppeteer = await import('puppeteer').catch(() => null);
|
|
269
|
+
if (puppeteer && typeof puppeteer.launch === 'function') {
|
|
270
|
+
const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] });
|
|
271
|
+
const page = await browser.newPage();
|
|
272
|
+
await page.goto(url, { waitUntil: 'networkidle2', timeout: 20000 });
|
|
273
|
+
const buf = await page.screenshot({ encoding: 'base64' });
|
|
274
|
+
await page.close();
|
|
275
|
+
await browser.close();
|
|
276
|
+
return JSON.stringify({ base64: String(buf).slice(0, 20000) });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
catch { }
|
|
280
|
+
if (dir) {
|
|
281
|
+
const install = await ensurePuppeteerInstalled(dir);
|
|
282
|
+
if (install.ok)
|
|
283
|
+
return JSON.stringify({ note: 'puppeteer installed, retry screenshot', detail: install.message });
|
|
284
|
+
}
|
|
285
|
+
return JSON.stringify({ note: 'browser.screenshot requires a connected browser devtools or puppeteer — will auto-install on next call' });
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
];
|
|
289
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vesk/agentic — command tool
|
|
3
|
+
*
|
|
4
|
+
* Zero-deps. Single tool `command.execute` that is allowlist-checked before
|
|
5
|
+
* delegating to the injected `runCommand` runner. Every `execute` returns a
|
|
6
|
+
* JSON string and never throws.
|
|
7
|
+
*/
|
|
8
|
+
import type { Tool } from '../loop.js';
|
|
9
|
+
export declare function createCommandTools(allowlist: RegExp[], runCommand: (argv: string[]) => Promise<{
|
|
10
|
+
stdout: string;
|
|
11
|
+
stderr: string;
|
|
12
|
+
code: number;
|
|
13
|
+
}>): Tool[];
|
|
14
|
+
//# sourceMappingURL=command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/tools/command.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EAAE,EACnB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GACxF,IAAI,EAAE,CAiDR"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vesk/agentic — command tool
|
|
3
|
+
*
|
|
4
|
+
* Zero-deps. Single tool `command.execute` that is allowlist-checked before
|
|
5
|
+
* delegating to the injected `runCommand` runner. Every `execute` returns a
|
|
6
|
+
* JSON string and never throws.
|
|
7
|
+
*/
|
|
8
|
+
export function createCommandTools(allowlist, runCommand) {
|
|
9
|
+
const tool = {
|
|
10
|
+
name: 'command.execute',
|
|
11
|
+
description: 'Execute a shell command via the allowlisted runner. The command is checked against the allowlist before execution.',
|
|
12
|
+
parameters: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
argv: {
|
|
16
|
+
type: 'array',
|
|
17
|
+
description: 'Command and arguments as an array (e.g. ["npm","run","build"])',
|
|
18
|
+
items: { type: 'string' },
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
required: ['argv'],
|
|
22
|
+
additionalProperties: false,
|
|
23
|
+
},
|
|
24
|
+
async execute(args) {
|
|
25
|
+
const raw = args.argv;
|
|
26
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
27
|
+
return JSON.stringify({ ok: false, error: 'missing required "argv" parameter (expected non-empty string[])' });
|
|
28
|
+
}
|
|
29
|
+
const argv = raw.filter((v) => typeof v === 'string');
|
|
30
|
+
if (argv.length !== raw.length) {
|
|
31
|
+
return JSON.stringify({ ok: false, error: '"argv" must be an array of strings' });
|
|
32
|
+
}
|
|
33
|
+
if (argv.length === 0) {
|
|
34
|
+
return JSON.stringify({ ok: false, error: 'missing required "argv" parameter (expected non-empty string[])' });
|
|
35
|
+
}
|
|
36
|
+
// Allowlist check — join with space to match the dev-api convention.
|
|
37
|
+
const joined = argv.join(' ');
|
|
38
|
+
const allowed = allowlist.some((re) => re.test(joined));
|
|
39
|
+
if (!allowed) {
|
|
40
|
+
return JSON.stringify({ ok: false, error: 'command not in allowlist', argv });
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const result = await runCommand(argv);
|
|
44
|
+
const stdout = typeof result.stdout === 'string' ? result.stdout : String(result.stdout ?? '');
|
|
45
|
+
const stderr = typeof result.stderr === 'string' ? result.stderr : String(result.stderr ?? '');
|
|
46
|
+
const code = typeof result.code === 'number' ? result.code : Number(result.code) || 0;
|
|
47
|
+
return JSON.stringify({ ok: true, argv, stdout, stderr, code });
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
return JSON.stringify({ ok: false, error: e instanceof Error ? e.message : String(e), argv });
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
return [tool];
|
|
55
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vesk/agentic — filesystem tools
|
|
3
|
+
*
|
|
4
|
+
* Zero-deps, node:fs only. Containment-checked via a local `resolveWithin`
|
|
5
|
+
* helper (no import from @vesk/adapter). Every `execute` returns a JSON
|
|
6
|
+
* string and never throws.
|
|
7
|
+
*/
|
|
8
|
+
import type { Tool } from '../loop.js';
|
|
9
|
+
export declare function createFsTools(projectDir: string): Tool[];
|
|
10
|
+
//# sourceMappingURL=fs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/tools/fs.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AA+BvC,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,EAAE,CAoGxD"}
|
package/dist/tools/fs.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vesk/agentic — filesystem tools
|
|
3
|
+
*
|
|
4
|
+
* Zero-deps, node:fs only. Containment-checked via a local `resolveWithin`
|
|
5
|
+
* helper (no import from @vesk/adapter). Every `execute` returns a JSON
|
|
6
|
+
* string and never throws.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync, readdirSync, unlinkSync, rmSync, } from 'node:fs';
|
|
9
|
+
import { resolve, dirname, sep } from 'node:path';
|
|
10
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
11
|
+
// local containment helper — must stay in this file so the module is zero-deps
|
|
12
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
/**
|
|
14
|
+
* Resolve `relPath` against `baseDir` and return the absolute path ONLY if
|
|
15
|
+
* it stays strictly inside `baseDir` (never the directory itself, never
|
|
16
|
+
* outside). Returns null otherwise.
|
|
17
|
+
*/
|
|
18
|
+
function resolveWithin(baseDir, relPath) {
|
|
19
|
+
const base = resolve(baseDir);
|
|
20
|
+
const target = resolve(baseDir, relPath);
|
|
21
|
+
const prefix = base + sep;
|
|
22
|
+
if (target === base || !target.startsWith(prefix))
|
|
23
|
+
return null;
|
|
24
|
+
return target;
|
|
25
|
+
}
|
|
26
|
+
function jsonOk(data) {
|
|
27
|
+
return JSON.stringify(data);
|
|
28
|
+
}
|
|
29
|
+
function jsonError(message, extra) {
|
|
30
|
+
return JSON.stringify({ ok: false, error: message, ...(extra || {}) });
|
|
31
|
+
}
|
|
32
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
33
|
+
// public API
|
|
34
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
35
|
+
export function createFsTools(projectDir) {
|
|
36
|
+
const base = resolve(projectDir);
|
|
37
|
+
const readTool = {
|
|
38
|
+
name: 'filesystem.read',
|
|
39
|
+
description: 'Read a file or directory inside the project. Containment-checked; paths outside the project are rejected.',
|
|
40
|
+
parameters: {
|
|
41
|
+
type: 'object',
|
|
42
|
+
properties: {
|
|
43
|
+
path: { type: 'string', description: 'Project-relative path to read (e.g. "app/page.vsk" or "src/lib.ts")' },
|
|
44
|
+
},
|
|
45
|
+
required: ['path'],
|
|
46
|
+
additionalProperties: false,
|
|
47
|
+
},
|
|
48
|
+
async execute(args) {
|
|
49
|
+
const rel = String(args.path ?? '');
|
|
50
|
+
if (!rel)
|
|
51
|
+
return jsonError('missing required "path" parameter');
|
|
52
|
+
const resolved = resolveWithin(base, rel);
|
|
53
|
+
if (!resolved)
|
|
54
|
+
return jsonError('path escapes project root', { path: rel });
|
|
55
|
+
try {
|
|
56
|
+
if (!existsSync(resolved))
|
|
57
|
+
return jsonError('not found', { path: rel });
|
|
58
|
+
const st = statSync(resolved);
|
|
59
|
+
if (st.isDirectory()) {
|
|
60
|
+
const entries = readdirSync(resolved, { withFileTypes: true }).map((e) => e.isDirectory() ? e.name + '/' : e.name);
|
|
61
|
+
return jsonOk({ ok: true, path: rel, directory: true, entries });
|
|
62
|
+
}
|
|
63
|
+
if (st.isFile()) {
|
|
64
|
+
const content = readFileSync(resolved, 'utf-8');
|
|
65
|
+
return jsonOk({ ok: true, path: rel, directory: false, content });
|
|
66
|
+
}
|
|
67
|
+
return jsonError('unsupported file type', { path: rel });
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
return jsonError(e instanceof Error ? e.message : String(e), { path: rel });
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
const writeTool = {
|
|
75
|
+
name: 'filesystem.write',
|
|
76
|
+
description: 'Write (create or overwrite) a file inside the project. Containment-checked; paths outside the project are rejected. Creates parent directories as needed.',
|
|
77
|
+
parameters: {
|
|
78
|
+
type: 'object',
|
|
79
|
+
properties: {
|
|
80
|
+
path: { type: 'string', description: 'Project-relative path to write' },
|
|
81
|
+
content: { type: 'string', description: 'File content (utf-8)' },
|
|
82
|
+
},
|
|
83
|
+
required: ['path', 'content'],
|
|
84
|
+
additionalProperties: false,
|
|
85
|
+
},
|
|
86
|
+
async execute(args) {
|
|
87
|
+
const rel = String(args.path ?? '');
|
|
88
|
+
const content = String(args.content ?? '');
|
|
89
|
+
if (!rel)
|
|
90
|
+
return jsonError('missing required "path" parameter');
|
|
91
|
+
if (args.content === undefined)
|
|
92
|
+
return jsonError('missing required "content" parameter');
|
|
93
|
+
const resolved = resolveWithin(base, rel);
|
|
94
|
+
if (!resolved)
|
|
95
|
+
return jsonError('path escapes project root', { path: rel });
|
|
96
|
+
try {
|
|
97
|
+
mkdirSync(dirname(resolved), { recursive: true });
|
|
98
|
+
writeFileSync(resolved, content, 'utf-8');
|
|
99
|
+
return jsonOk({ ok: true, path: rel });
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
return jsonError(e instanceof Error ? e.message : String(e), { path: rel });
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
const deleteTool = {
|
|
107
|
+
name: 'filesystem.delete',
|
|
108
|
+
description: 'Delete a file or directory inside the project. Containment-checked; paths outside the project are rejected.',
|
|
109
|
+
parameters: {
|
|
110
|
+
type: 'object',
|
|
111
|
+
properties: {
|
|
112
|
+
path: { type: 'string', description: 'Project-relative path to delete' },
|
|
113
|
+
},
|
|
114
|
+
required: ['path'],
|
|
115
|
+
additionalProperties: false,
|
|
116
|
+
},
|
|
117
|
+
async execute(args) {
|
|
118
|
+
const rel = String(args.path ?? '');
|
|
119
|
+
if (!rel)
|
|
120
|
+
return jsonError('missing required "path" parameter');
|
|
121
|
+
const resolved = resolveWithin(base, rel);
|
|
122
|
+
if (!resolved)
|
|
123
|
+
return jsonError('path escapes project root', { path: rel });
|
|
124
|
+
try {
|
|
125
|
+
if (!existsSync(resolved))
|
|
126
|
+
return jsonError('not found', { path: rel });
|
|
127
|
+
const st = statSync(resolved);
|
|
128
|
+
if (st.isDirectory()) {
|
|
129
|
+
rmSync(resolved, { recursive: true, force: true });
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
unlinkSync(resolved);
|
|
133
|
+
}
|
|
134
|
+
return jsonOk({ ok: true, path: rel });
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
return jsonError(e instanceof Error ? e.message : String(e), { path: rel });
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
return [readTool, writeTool, deleteTool];
|
|
142
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vesk/agentic — Vesk-native tools
|
|
3
|
+
*
|
|
4
|
+
* Zero-deps, node:fs-only tools routed through the Dev Server capability gate.
|
|
5
|
+
* All file access is containment-checked via a local `resolveWithin` helper.
|
|
6
|
+
* Every `execute` returns a JSON string (never throws).
|
|
7
|
+
*
|
|
8
|
+
* Export: `createVeskTools(deps)` -> Tool[] (14 tools, covering the
|
|
9
|
+
* `plans/devtools.md` Vesk-Native Agent Tools list).
|
|
10
|
+
*/
|
|
11
|
+
import type { Tool } from '../loop.js';
|
|
12
|
+
export interface VeskToolsDeps {
|
|
13
|
+
projectDir: string;
|
|
14
|
+
appDir: string;
|
|
15
|
+
veskDir: string;
|
|
16
|
+
readConfig?: () => unknown | Promise<unknown>;
|
|
17
|
+
getDiagnostics?: () => unknown[] | Promise<unknown[]>;
|
|
18
|
+
runBuild?: () => Promise<unknown>;
|
|
19
|
+
runTests?: () => Promise<unknown>;
|
|
20
|
+
}
|
|
21
|
+
export declare function createVeskTools(deps: VeskToolsDeps): Tool[];
|
|
22
|
+
//# sourceMappingURL=vesk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vesk.d.ts","sourceRoot":"","sources":["../../src/tools/vesk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AA4LvC,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9C,cAAc,CAAC,EAAE,MAAM,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACtD,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CACnC;AAUD,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI,EAAE,CAohB3D"}
|