@stevederico/dotbot 0.18.0 → 0.20.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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(grep -r \"grok-3\" /Users/sd/Desktop/projects/dotbot --include=\"*.js\" --include=\"*.md\" 2>/dev/null | grep -v node_modules)"
5
+ ]
6
+ }
7
+ }
package/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ 0.20
2
+
3
+ Zero npm dependencies
4
+ Remove MongoDB support
5
+ Add custom CDP client
6
+ Add Chrome auto-download
7
+ Require Node.js 22+
8
+
9
+ 0.19
10
+
11
+ Add test suite (node:test)
12
+ Zero test dependencies
13
+
1
14
  0.18
2
15
 
3
16
  Add --verbose flag
package/README.md CHANGED
@@ -2,7 +2,8 @@
2
2
  <img src="https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExaGNjeWoweGx4bGYxZXNvYmtsYW80MjlxODFmeTN0cHE3cHN6emFoNiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/gYWeVOiMmbg3kzCTq5/giphy.gif" alt="dotbot" width="200">
3
3
  <h1 align="center" style="border-bottom: none; margin-bottom: 0;">dotbot</h1>
4
4
  <h3 align="center" style="margin-top: 0; font-weight: normal;">
5
- The ultra-lean AI agent. 11k lines. 47 tools. Zero bloat.
5
+ The ultra-lean AI agent.<br>
6
+ 11k lines. 47 tools. 0 dependencies.
6
7
  </h3>
7
8
  <p align="center">
8
9
  <a href="https://opensource.org/licenses/mit">
@@ -12,7 +13,7 @@
12
13
  <img src="https://img.shields.io/github/stars/stevederico/dotbot?style=social" alt="GitHub stars">
13
14
  </a>
14
15
  <a href="https://github.com/stevederico/dotbot">
15
- <img src="https://img.shields.io/badge/version-0.18-green" alt="version">
16
+ <img src="https://img.shields.io/badge/version-0.20-green" alt="version">
16
17
  </a>
17
18
  <img src="https://img.shields.io/badge/LOC-11k-orange" alt="Lines of Code">
18
19
  </p>
@@ -147,7 +148,7 @@ for await (const event of agent.chat({
147
148
  ## CLI Reference
148
149
 
149
150
  ```
150
- dotbot v0.18 — AI agent CLI
151
+ dotbot v0.19 — AI agent CLI
151
152
 
152
153
  Usage:
153
154
  dotbot "message" Send a message (default)
package/bin/dotbot.js CHANGED
@@ -50,7 +50,7 @@ async function loadModules() {
50
50
  agentLoop = mod.agentLoop;
51
51
  }
52
52
 
53
- const VERSION = '0.18';
53
+ const VERSION = '0.19';
54
54
  const DEFAULT_PORT = 3000;
55
55
  const DEFAULT_DB = './dotbot.db';
56
56
 
@@ -0,0 +1,246 @@
1
+ // core/browser-launcher.js
2
+ // Browser launcher and downloader for CDP automation.
3
+ // Downloads Chrome for Testing on first use, zero npm dependencies.
4
+
5
+ import { spawn, execSync } from 'node:child_process';
6
+ import { existsSync, mkdirSync, createWriteStream, rmSync, chmodSync, readdirSync } from 'node:fs';
7
+ import { homedir, platform } from 'node:os';
8
+ import { join, dirname } from 'node:path';
9
+ import { pipeline } from 'node:stream/promises';
10
+ import { createGunzip } from 'node:zlib';
11
+
12
+ // Chrome for Testing version (stable channel)
13
+ const CHROME_VERSION = '131.0.6778.204';
14
+
15
+ const DOWNLOAD_URLS = {
16
+ darwin: `https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VERSION}/mac-x64/chrome-headless-shell-mac-x64.zip`,
17
+ 'darwin-arm64': `https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VERSION}/mac-arm64/chrome-headless-shell-mac-arm64.zip`,
18
+ linux: `https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VERSION}/linux64/chrome-headless-shell-linux64.zip`,
19
+ win32: `https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VERSION}/win64/chrome-headless-shell-win64.zip`
20
+ };
21
+
22
+ const BROWSER_DIR = join(homedir(), '.dotbot', 'browsers');
23
+
24
+ /**
25
+ * Get the expected Chrome executable path for this platform.
26
+ * @returns {string} Path to Chrome executable
27
+ */
28
+ function getChromePath() {
29
+ const plat = platform();
30
+ const arch = process.arch;
31
+
32
+ if (plat === 'darwin') {
33
+ const archDir = arch === 'arm64' ? 'chrome-headless-shell-mac-arm64' : 'chrome-headless-shell-mac-x64';
34
+ return join(BROWSER_DIR, archDir, 'chrome-headless-shell');
35
+ } else if (plat === 'linux') {
36
+ return join(BROWSER_DIR, 'chrome-headless-shell-linux64', 'chrome-headless-shell');
37
+ } else if (plat === 'win32') {
38
+ return join(BROWSER_DIR, 'chrome-headless-shell-win64', 'chrome-headless-shell.exe');
39
+ }
40
+
41
+ throw new Error(`Unsupported platform: ${plat}`);
42
+ }
43
+
44
+ /**
45
+ * Get the download URL for this platform.
46
+ * @returns {string} Download URL
47
+ */
48
+ function getDownloadUrl() {
49
+ const plat = platform();
50
+ const arch = process.arch;
51
+
52
+ if (plat === 'darwin' && arch === 'arm64') {
53
+ return DOWNLOAD_URLS['darwin-arm64'];
54
+ }
55
+
56
+ return DOWNLOAD_URLS[plat];
57
+ }
58
+
59
+ /**
60
+ * Download and extract Chrome for Testing.
61
+ * @returns {Promise<string>} Path to Chrome executable
62
+ */
63
+ async function downloadChrome() {
64
+ const url = getDownloadUrl();
65
+ if (!url) {
66
+ throw new Error(`No Chrome download available for ${platform()}`);
67
+ }
68
+
69
+ console.log('[browser] Downloading Chrome for Testing (~50MB)...');
70
+
71
+ mkdirSync(BROWSER_DIR, { recursive: true });
72
+
73
+ const zipPath = join(BROWSER_DIR, 'chrome.zip');
74
+
75
+ // Download zip file
76
+ const response = await fetch(url);
77
+ if (!response.ok) {
78
+ throw new Error(`Failed to download Chrome: ${response.status}`);
79
+ }
80
+
81
+ const fileStream = createWriteStream(zipPath);
82
+ await pipeline(response.body, fileStream);
83
+
84
+ console.log('[browser] Extracting Chrome...');
85
+
86
+ // Extract using system unzip (available on all platforms)
87
+ try {
88
+ execSync(`unzip -o -q "${zipPath}" -d "${BROWSER_DIR}"`, { stdio: 'pipe' });
89
+ } catch (err) {
90
+ throw new Error(`Failed to extract Chrome: ${err.message}`);
91
+ }
92
+
93
+ // Clean up zip file
94
+ rmSync(zipPath, { force: true });
95
+
96
+ const chromePath = getChromePath();
97
+
98
+ // Make executable on Unix
99
+ if (platform() !== 'win32') {
100
+ chmodSync(chromePath, 0o755);
101
+ }
102
+
103
+ console.log('[browser] Chrome installed successfully');
104
+ return chromePath;
105
+ }
106
+
107
+ /**
108
+ * Ensure Chrome is available, downloading if necessary.
109
+ * @returns {Promise<string>} Path to Chrome executable
110
+ */
111
+ export async function ensureBrowser() {
112
+ const chromePath = getChromePath();
113
+
114
+ if (existsSync(chromePath)) {
115
+ return chromePath;
116
+ }
117
+
118
+ return downloadChrome();
119
+ }
120
+
121
+ /**
122
+ * Find a free port for Chrome debugging.
123
+ * @returns {Promise<number>} Available port number
124
+ */
125
+ async function findFreePort() {
126
+ const { createServer } = await import('node:net');
127
+
128
+ return new Promise((resolve, reject) => {
129
+ const server = createServer();
130
+ server.listen(0, () => {
131
+ const port = server.address().port;
132
+ server.close(() => resolve(port));
133
+ });
134
+ server.on('error', reject);
135
+ });
136
+ }
137
+
138
+ /**
139
+ * Launch Chrome with remote debugging enabled.
140
+ * @param {Object} options - Launch options
141
+ * @param {number} options.port - Debugging port (auto-assigned if not specified)
142
+ * @param {string} options.userDataDir - User data directory for isolation
143
+ * @returns {Promise<{process: ChildProcess, port: number, wsUrl: string}>}
144
+ */
145
+ export async function launchBrowser(options = {}) {
146
+ const chromePath = await ensureBrowser();
147
+ const port = options.port || await findFreePort();
148
+ const userDataDir = options.userDataDir || `/tmp/dotbot-browser-${Date.now()}`;
149
+
150
+ mkdirSync(userDataDir, { recursive: true });
151
+
152
+ const args = [
153
+ `--remote-debugging-port=${port}`,
154
+ `--user-data-dir=${userDataDir}`,
155
+ '--disable-gpu',
156
+ '--no-sandbox',
157
+ '--disable-setuid-sandbox',
158
+ '--disable-dev-shm-usage',
159
+ '--disable-extensions',
160
+ '--disable-background-networking',
161
+ '--disable-sync',
162
+ '--no-first-run',
163
+ '--disable-default-apps'
164
+ ];
165
+
166
+ const proc = spawn(chromePath, args, {
167
+ stdio: ['pipe', 'pipe', 'pipe'],
168
+ detached: false
169
+ });
170
+
171
+ // Wait for DevTools to be ready
172
+ const wsUrl = await waitForDevTools(port, 10000);
173
+
174
+ return {
175
+ process: proc,
176
+ port,
177
+ wsUrl,
178
+ userDataDir
179
+ };
180
+ }
181
+
182
+ /**
183
+ * Wait for Chrome DevTools to be ready and return WebSocket URL.
184
+ * @param {number} port - Debugging port
185
+ * @param {number} timeout - Timeout in ms
186
+ * @returns {Promise<string>} WebSocket debugger URL
187
+ */
188
+ async function waitForDevTools(port, timeout = 10000) {
189
+ const start = Date.now();
190
+ const url = `http://127.0.0.1:${port}/json/version`;
191
+
192
+ while (Date.now() - start < timeout) {
193
+ try {
194
+ const response = await fetch(url);
195
+ if (response.ok) {
196
+ const data = await response.json();
197
+ return data.webSocketDebuggerUrl;
198
+ }
199
+ } catch {
200
+ // Chrome not ready yet
201
+ }
202
+ await new Promise(r => setTimeout(r, 100));
203
+ }
204
+
205
+ throw new Error('Chrome DevTools did not start in time');
206
+ }
207
+
208
+ /**
209
+ * Create a new browser context (tab) via CDP.
210
+ * @param {string} wsUrl - Browser WebSocket URL
211
+ * @returns {Promise<string>} Target WebSocket URL for the new context
212
+ */
213
+ export async function createBrowserContext(wsUrl) {
214
+ // Connect to browser endpoint to create new target
215
+ const port = new URL(wsUrl).port;
216
+ const response = await fetch(`http://127.0.0.1:${port}/json/new`);
217
+
218
+ if (!response.ok) {
219
+ throw new Error('Failed to create browser context');
220
+ }
221
+
222
+ const target = await response.json();
223
+ return target.webSocketDebuggerUrl;
224
+ }
225
+
226
+ /**
227
+ * Close a browser context (tab) via CDP.
228
+ * @param {string} targetWsUrl - Target WebSocket URL
229
+ */
230
+ export async function closeBrowserContext(targetWsUrl) {
231
+ const url = new URL(targetWsUrl);
232
+ const port = url.port;
233
+ const targetId = url.pathname.split('/').pop();
234
+
235
+ await fetch(`http://127.0.0.1:${port}/json/close/${targetId}`);
236
+ }
237
+
238
+ /**
239
+ * Kill the browser process.
240
+ * @param {ChildProcess} proc - Browser process
241
+ */
242
+ export function killBrowser(proc) {
243
+ if (proc && !proc.killed) {
244
+ proc.kill('SIGTERM');
245
+ }
246
+ }