@rmbk/compeek 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 compeek contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # compeek — AI Eyes & Hands for Any Desktop
2
+
3
+ > A computer use agent framework powered by Claude. Define goals in natural language, point at any software, watch the agent work.
4
+
5
+ **No APIs. No plugins. No integrations. Just screen and keyboard.**
6
+
7
+ [Dashboard](https://compeek.rmbk.me) | [Docker Image](https://ghcr.io/uburuntu/compeek) | [npm](https://www.npmjs.com/package/compeek)
8
+
9
+ ## Quick Start
10
+
11
+ ```bash
12
+ # One-line install (Linux/macOS/WSL2)
13
+ curl -fsSL https://compeek.rmbk.me/install.sh | bash
14
+
15
+ # Or via npx
16
+ npx compeek start --open
17
+
18
+ # Or docker directly
19
+ docker run -d -p 3001:3000 -p 6081:6080 --shm-size=512m ghcr.io/uburuntu/compeek
20
+ ```
21
+
22
+ The container prints a **connection string** and a **clickable dashboard link** — no manual port entry needed. Check `docker logs compeek-1`.
23
+
24
+ Open the [dashboard](https://compeek.rmbk.me), paste your Anthropic API key in Settings, and start a workflow.
25
+
26
+ ## What is compeek?
27
+
28
+ **compeek** (компик + peek) turns Claude into an autonomous desktop agent. It sees any application through screenshots, interacts via mouse and keyboard, and validates its own work — all without requiring any integration with the target software.
29
+
30
+ - **See** — screenshot any application + zoom into details
31
+ - **Think** — extended thinking for transparent reasoning
32
+ - **Act** — mouse clicks, keyboard input, scrolling
33
+ - **Read** — extract data from document photos (passports, IDs, invoices)
34
+ - **Validate** — self-check by comparing filled forms against expected data
35
+ - **Observe** — real-time dashboard showing what the AI sees and thinks
36
+
37
+ ## Architecture
38
+
39
+ ```
40
+ Browser (React dashboard) Docker Container
41
+ ┌──────────────────────┐ ┌──────────────────────┐
42
+ │ Agent Loop │ │ Xvfb + Mutter │
43
+ │ ├─ Anthropic API │ HTTP │ ├─ Firefox │
44
+ │ └─ Tool dispatch ───┼─────────┼─▸ Tool Server :3000 │
45
+ │ │ │ │ └─ xdotool/scrot │
46
+ │ Session Manager │ │ ├─ noVNC :6080 │
47
+ │ Settings (API key) │ │ └─ VNC :5900 │
48
+ └──────────────────────┘ └──────────────────────┘
49
+ ```
50
+
51
+ The agent loop runs **in the browser** — it calls the Anthropic API directly and sends mouse/keyboard commands to Docker containers via HTTP. Each container is a stateless virtual desktop with a lightweight tool server. No backend needed.
52
+
53
+ ## Desktop Modes
54
+
55
+ Set `DESKTOP_MODE` when starting a container:
56
+
57
+ | Mode | What starts | Use case |
58
+ |------|-------------|----------|
59
+ | `full` (default) | Xvfb + Mutter + Tint2 + Firefox + target app | QA testing with pre-loaded app |
60
+ | `browser` | Xvfb + Mutter + Firefox | General web browsing agent |
61
+ | `minimal` | Xvfb + Mutter only | Agent launches everything itself |
62
+ | `headless` | Xvfb + tool server only | API-only, bash commands only |
63
+
64
+ ```bash
65
+ npx compeek start --mode browser
66
+ # or
67
+ docker run -d -e DESKTOP_MODE=browser -p 3001:3000 -p 6081:6080 --shm-size=512m ghcr.io/uburuntu/compeek
68
+ ```
69
+
70
+ ## Connection Strings
71
+
72
+ Containers print a base64-encoded config and a dashboard link on startup:
73
+
74
+ ```
75
+ Connection string: eyJuYW1lIj...
76
+ Dashboard link: https://compeek.rmbk.me/#config=eyJuYW1lIj...
77
+ ```
78
+
79
+ Three ways to connect:
80
+ 1. **Click the link** — auto-adds the session
81
+ 2. **Paste the string** in the Add Session dialog
82
+ 3. **Manual entry** — type host and ports
83
+
84
+ ## CLI
85
+
86
+ ```bash
87
+ npx compeek start # Pull image, start container, print connection info
88
+ npx compeek start --open # Same + open dashboard in browser
89
+ npx compeek stop # Stop all compeek containers
90
+ npx compeek stop 1 # Stop compeek-1
91
+ npx compeek status # List running containers
92
+ npx compeek logs # Follow container logs
93
+ npx compeek open # Open dashboard with auto-connect URL
94
+ ```
95
+
96
+ Flags for `start`: `--name`, `--api-port`, `--vnc-port`, `--mode`, `--no-pull`, `--open`.
97
+
98
+ ## Development
99
+
100
+ ```bash
101
+ npm install
102
+ npm run dev:client # Vite dev server on :5173
103
+ npm run build # tsc + vite build
104
+ npm test # 19 tests
105
+ docker compose up --build # 3 containers on ports 3001-3003 / 6081-6083
106
+ ```
107
+
108
+ ## Project Structure
109
+
110
+ ```
111
+ compeek/
112
+ ├── src/
113
+ │ ├── agent/ # Shared tools, types, prompts
114
+ │ ├── app/ # React dashboard (Vite)
115
+ │ ├── container/ # Express tool server (Docker)
116
+ │ └── lib/ # Logger
117
+ ├── bin/compeek.mjs # CLI (npx compeek)
118
+ ├── install.sh # One-line installer
119
+ ├── docker/ # Dockerfile + entrypoint
120
+ ├── target-app/ # Demo form application
121
+ └── docker-compose.yml
122
+ ```
123
+
124
+ ## Built for
125
+
126
+ **"Built with Opus 4.6: a Claude Code Hackathon"** by Anthropic (Feb 2026)
127
+
128
+ ## License
129
+
130
+ MIT
@@ -0,0 +1,344 @@
1
+ #!/usr/bin/env node
2
+
3
+ // compeek CLI — zero dependencies, Node.js built-ins only
4
+ // Usage: npx compeek [start|stop|status|logs|open]
5
+
6
+ import { execSync, spawn } from 'node:child_process';
7
+ import http from 'node:http';
8
+
9
+ const IMAGE = 'ghcr.io/uburuntu/compeek:latest';
10
+ const CONTAINER_PREFIX = 'compeek-';
11
+ const DASHBOARD_URL = 'https://compeek.rmbk.me';
12
+ const HEALTH_TIMEOUT = 30_000;
13
+ const HEALTH_INTERVAL = 1_000;
14
+
15
+ // ── Helpers ──────────────────────────────────────────────
16
+
17
+ function run(cmd, opts = {}) {
18
+ try {
19
+ return execSync(cmd, { encoding: 'utf-8', stdio: opts.stdio || 'pipe', ...opts }).trim();
20
+ } catch (e) {
21
+ if (opts.allowFail) return '';
22
+ throw e;
23
+ }
24
+ }
25
+
26
+ function hasDocker() {
27
+ try {
28
+ run('docker info', { allowFail: false });
29
+ return true;
30
+ } catch {
31
+ return false;
32
+ }
33
+ }
34
+
35
+ function listContainers() {
36
+ const out = run(
37
+ `docker ps -a --filter "name=^${CONTAINER_PREFIX}" --format "{{.Names}}\\t{{.Status}}\\t{{.Ports}}"`,
38
+ { allowFail: true },
39
+ );
40
+ if (!out) return [];
41
+ return out.split('\n').map(line => {
42
+ const [name, ...rest] = line.split('\t');
43
+ return { name, status: rest[0] || '', ports: rest[1] || '' };
44
+ });
45
+ }
46
+
47
+ function findNextPorts() {
48
+ const containers = listContainers();
49
+ const usedApi = new Set();
50
+ const usedVnc = new Set();
51
+ for (const c of containers) {
52
+ const apiMatch = c.ports.match(/0\.0\.0\.0:(\d+)->3000/);
53
+ const vncMatch = c.ports.match(/0\.0\.0\.0:(\d+)->6080/);
54
+ if (apiMatch) usedApi.add(parseInt(apiMatch[1]));
55
+ if (vncMatch) usedVnc.add(parseInt(vncMatch[1]));
56
+ }
57
+ let apiPort = 3001;
58
+ while (usedApi.has(apiPort)) apiPort++;
59
+ let vncPort = 6081;
60
+ while (usedVnc.has(vncPort)) vncPort++;
61
+ return { apiPort, vncPort };
62
+ }
63
+
64
+ function findNextName() {
65
+ const containers = listContainers();
66
+ let n = 1;
67
+ const names = new Set(containers.map(c => c.name));
68
+ while (names.has(`${CONTAINER_PREFIX}${n}`)) n++;
69
+ return `${CONTAINER_PREFIX}${n}`;
70
+ }
71
+
72
+ function waitForHealth(host, port, timeout) {
73
+ return new Promise((resolve, reject) => {
74
+ const start = Date.now();
75
+ const check = () => {
76
+ const req = http.get(`http://${host}:${port}/api/health`, { timeout: 2000 }, (res) => {
77
+ let body = '';
78
+ res.on('data', d => body += d);
79
+ res.on('end', () => {
80
+ try {
81
+ const data = JSON.parse(body);
82
+ if (data.status === 'ok') return resolve(data);
83
+ } catch { /* retry */ }
84
+ retry();
85
+ });
86
+ });
87
+ req.on('error', retry);
88
+ req.on('timeout', () => { req.destroy(); retry(); });
89
+ };
90
+ const retry = () => {
91
+ if (Date.now() - start > timeout) return reject(new Error('Health check timed out'));
92
+ setTimeout(check, HEALTH_INTERVAL);
93
+ };
94
+ check();
95
+ });
96
+ }
97
+
98
+ function buildConnectionString(name, apiHost, apiPort, vncHost, vncPort) {
99
+ const config = JSON.stringify({ name, type: 'compeek', apiHost, apiPort, vncHost, vncPort });
100
+ return Buffer.from(config).toString('base64');
101
+ }
102
+
103
+ function openUrl(url) {
104
+ const cmd = process.platform === 'darwin' ? 'open'
105
+ : process.platform === 'win32' ? 'start'
106
+ : 'xdg-open';
107
+ try {
108
+ execSync(`${cmd} "${url}"`, { stdio: 'ignore' });
109
+ } catch {
110
+ console.log(` Open manually: ${url}`);
111
+ }
112
+ }
113
+
114
+ // ── Commands ─────────────────────────────────────────────
115
+
116
+ async function cmdStart(args) {
117
+ if (!hasDocker()) {
118
+ console.error('Docker is not available. Install Docker first: https://docs.docker.com/get-docker/');
119
+ process.exit(1);
120
+ }
121
+
122
+ const flags = parseFlags(args);
123
+ const name = flags.name || findNextName();
124
+ const { apiPort: defaultApi, vncPort: defaultVnc } = findNextPorts();
125
+ const apiPort = parseInt(flags['api-port']) || defaultApi;
126
+ const vncPort = parseInt(flags['vnc-port']) || defaultVnc;
127
+ const mode = flags.mode || 'full';
128
+ const sessionName = name.replace(CONTAINER_PREFIX, '').replace(/^(\d+)$/, 'Desktop $1');
129
+
130
+ if (!flags['no-pull']) {
131
+ console.log(`Pulling ${IMAGE}...`);
132
+ try {
133
+ run(`docker pull ${IMAGE}`, { stdio: 'inherit' });
134
+ } catch {
135
+ console.log('Pull failed, using cached image if available.');
136
+ }
137
+ }
138
+
139
+ console.log(`Starting ${name} (api:${apiPort}, vnc:${vncPort}, mode:${mode})...`);
140
+
141
+ // Remove existing container with same name if stopped
142
+ run(`docker rm -f ${name}`, { allowFail: true });
143
+
144
+ run([
145
+ 'docker run -d',
146
+ `--name ${name}`,
147
+ `-p ${apiPort}:3000`,
148
+ `-p ${vncPort}:6080`,
149
+ `--shm-size=512m`,
150
+ `-e DISPLAY=:1`,
151
+ `-e DESKTOP_MODE=${mode}`,
152
+ `-e COMPEEK_SESSION_NAME=${sessionName}`,
153
+ `--security-opt seccomp=unconfined`,
154
+ IMAGE,
155
+ ].join(' '));
156
+
157
+ console.log('Waiting for container to be ready...');
158
+
159
+ try {
160
+ await waitForHealth('localhost', apiPort, HEALTH_TIMEOUT);
161
+ console.log('Container is ready.');
162
+ } catch {
163
+ console.error('Container did not become healthy. Check logs: npx compeek logs');
164
+ process.exit(1);
165
+ }
166
+
167
+ const connStr = buildConnectionString(sessionName, 'localhost', apiPort, 'localhost', vncPort);
168
+
169
+ console.log('');
170
+ console.log('=========================================');
171
+ console.log(` ${name}`);
172
+ console.log('=========================================');
173
+ console.log(` Tool API : http://localhost:${apiPort}`);
174
+ if (mode !== 'headless') {
175
+ console.log(` noVNC : http://localhost:${vncPort}`);
176
+ }
177
+ console.log('');
178
+ console.log(' Connection string:');
179
+ console.log(` ${connStr}`);
180
+ console.log('');
181
+ console.log(' Dashboard link:');
182
+ console.log(` ${DASHBOARD_URL}/#config=${connStr}`);
183
+ console.log('=========================================');
184
+
185
+ if (flags.open) {
186
+ openUrl(`${DASHBOARD_URL}/#config=${connStr}`);
187
+ }
188
+ }
189
+
190
+ function cmdStop(args) {
191
+ const target = args[0];
192
+ if (target) {
193
+ const name = target.startsWith(CONTAINER_PREFIX) ? target : `${CONTAINER_PREFIX}${target}`;
194
+ console.log(`Stopping ${name}...`);
195
+ run(`docker rm -f ${name}`, { allowFail: true, stdio: 'inherit' });
196
+ } else {
197
+ const containers = listContainers();
198
+ if (containers.length === 0) {
199
+ console.log('No compeek containers running.');
200
+ return;
201
+ }
202
+ for (const c of containers) {
203
+ console.log(`Stopping ${c.name}...`);
204
+ run(`docker rm -f ${c.name}`, { allowFail: true });
205
+ }
206
+ console.log(`Stopped ${containers.length} container(s).`);
207
+ }
208
+ }
209
+
210
+ function cmdStatus() {
211
+ const containers = listContainers();
212
+ if (containers.length === 0) {
213
+ console.log('No compeek containers found.');
214
+ return;
215
+ }
216
+ console.log('NAME\t\t\tSTATUS\t\t\t\tPORTS');
217
+ console.log('─'.repeat(80));
218
+ for (const c of containers) {
219
+ console.log(`${c.name}\t\t${c.status}\t\t${c.ports}`);
220
+ }
221
+ }
222
+
223
+ function cmdLogs(args) {
224
+ const target = args[0];
225
+ let name;
226
+ if (target) {
227
+ name = target.startsWith(CONTAINER_PREFIX) ? target : `${CONTAINER_PREFIX}${target}`;
228
+ } else {
229
+ const containers = listContainers();
230
+ if (containers.length === 0) {
231
+ console.error('No compeek containers found.');
232
+ process.exit(1);
233
+ }
234
+ name = containers[0].name;
235
+ }
236
+ const child = spawn('docker', ['logs', '-f', '--tail', '50', name], { stdio: 'inherit' });
237
+ child.on('exit', code => process.exit(code || 0));
238
+ }
239
+
240
+ function cmdOpen(args) {
241
+ const target = args[0];
242
+ let name;
243
+ if (target) {
244
+ name = target.startsWith(CONTAINER_PREFIX) ? target : `${CONTAINER_PREFIX}${target}`;
245
+ } else {
246
+ const containers = listContainers().filter(c => c.status.startsWith('Up'));
247
+ if (containers.length === 0) {
248
+ console.error('No running compeek containers found.');
249
+ process.exit(1);
250
+ }
251
+ name = containers[0].name;
252
+ }
253
+
254
+ // Extract ports from docker inspect
255
+ const inspect = run(`docker inspect --format '{{json .NetworkSettings.Ports}}' ${name}`, { allowFail: true });
256
+ if (!inspect) {
257
+ console.error(`Container ${name} not found.`);
258
+ process.exit(1);
259
+ }
260
+
261
+ try {
262
+ const ports = JSON.parse(inspect);
263
+ const apiBinding = ports['3000/tcp']?.[0];
264
+ const vncBinding = ports['6080/tcp']?.[0];
265
+ const apiPort = apiBinding ? parseInt(apiBinding.HostPort) : 3001;
266
+ const vncPort = vncBinding ? parseInt(vncBinding.HostPort) : 6081;
267
+ const sessionName = name.replace(CONTAINER_PREFIX, '').replace(/^(\d+)$/, 'Desktop $1');
268
+
269
+ const connStr = buildConnectionString(sessionName, 'localhost', apiPort, 'localhost', vncPort);
270
+ const url = `${DASHBOARD_URL}/#config=${connStr}`;
271
+ console.log(`Opening ${url}`);
272
+ openUrl(url);
273
+ } catch (e) {
274
+ console.error('Failed to read container ports:', e.message);
275
+ process.exit(1);
276
+ }
277
+ }
278
+
279
+ // ── Flag parsing ─────────────────────────────────────────
280
+
281
+ function parseFlags(args) {
282
+ const flags = {};
283
+ for (let i = 0; i < args.length; i++) {
284
+ const arg = args[i];
285
+ if (arg.startsWith('--')) {
286
+ const key = arg.slice(2);
287
+ if (key === 'open' || key === 'no-pull') {
288
+ flags[key] = true;
289
+ } else if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
290
+ flags[key] = args[++i];
291
+ }
292
+ }
293
+ }
294
+ return flags;
295
+ }
296
+
297
+ // ── Main ─────────────────────────────────────────────────
298
+
299
+ const [command = 'start', ...rest] = process.argv.slice(2);
300
+
301
+ switch (command) {
302
+ case 'start':
303
+ cmdStart(rest);
304
+ break;
305
+ case 'stop':
306
+ cmdStop(rest);
307
+ break;
308
+ case 'status':
309
+ cmdStatus();
310
+ break;
311
+ case 'logs':
312
+ cmdLogs(rest);
313
+ break;
314
+ case 'open':
315
+ cmdOpen(rest);
316
+ break;
317
+ case '--help':
318
+ case '-h':
319
+ case 'help':
320
+ console.log(`
321
+ compeek — AI desktop agent
322
+
323
+ Usage: npx compeek [command] [options]
324
+
325
+ Commands:
326
+ start Start a new container (default)
327
+ stop [name] Stop one or all compeek containers
328
+ status List running containers
329
+ logs [name] Follow container logs
330
+ open [name] Open dashboard with auto-connect URL
331
+
332
+ Start options:
333
+ --name <n> Container name (default: compeek-N)
334
+ --api-port <p> Host port for tool API (default: auto)
335
+ --vnc-port <p> Host port for noVNC (default: auto)
336
+ --mode <m> Desktop mode: full|browser|minimal|headless
337
+ --no-pull Skip docker pull
338
+ --open Open dashboard after start
339
+ `);
340
+ break;
341
+ default:
342
+ console.error(`Unknown command: ${command}. Run "npx compeek --help" for usage.`);
343
+ process.exit(1);
344
+ }
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@rmbk/compeek",
3
+ "version": "0.2.0",
4
+ "description": "AI eyes and hands for any desktop application — a general-purpose computer use agent framework powered by Claude Opus 4.6",
5
+ "license": "MIT",
6
+ "author": "rmbk",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/uburuntu/compeek.git"
10
+ },
11
+ "engines": {
12
+ "node": ">=20"
13
+ },
14
+ "keywords": [
15
+ "computer-use",
16
+ "ai-agent",
17
+ "claude",
18
+ "opus",
19
+ "anthropic",
20
+ "desktop-automation",
21
+ "rpa",
22
+ "vision"
23
+ ],
24
+ "type": "module",
25
+ "bin": {
26
+ "compeek": "bin/compeek.mjs"
27
+ },
28
+ "files": [
29
+ "bin/"
30
+ ],
31
+ "scripts": {
32
+ "dev:client": "vite",
33
+ "build": "tsc && vite build",
34
+ "build:server": "tsc",
35
+ "build:client": "vite build",
36
+ "start": "node dist/container/server.js",
37
+ "docker:build": "docker build -f docker/Dockerfile -t compeek .",
38
+ "docker:run": "docker run -p 3000:3000 -p 6080:6080 -p 5900:5900 --shm-size=512m -it compeek",
39
+ "test": "vitest run",
40
+ "test:watch": "vitest"
41
+ },
42
+ "dependencies": {
43
+ "@anthropic-ai/sdk": "^0.74.0",
44
+ "cors": "^2.8.5",
45
+ "express": "^4.21.0",
46
+ "sharp": "^0.34.5"
47
+ },
48
+ "devDependencies": {
49
+ "@testing-library/jest-dom": "^6.9.1",
50
+ "@testing-library/react": "^16.3.2",
51
+ "@types/cors": "^2.8.17",
52
+ "@types/express": "^5.0.0",
53
+ "@types/node": "^22.10.0",
54
+ "@types/react": "^19.0.0",
55
+ "@types/react-dom": "^19.0.0",
56
+ "@vitejs/plugin-react": "^4.3.4",
57
+ "autoprefixer": "^10.4.20",
58
+ "jsdom": "^28.0.0",
59
+ "postcss": "^8.4.49",
60
+ "react": "^19.0.0",
61
+ "react-dom": "^19.0.0",
62
+ "tailwindcss": "^3.4.15",
63
+ "tsx": "^4.19.0",
64
+ "typescript": "^5.7.0",
65
+ "vite": "^6.0.0",
66
+ "vitest": "^4.0.18"
67
+ }
68
+ }