@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 +21 -0
- package/README.md +130 -0
- package/bin/compeek.mjs +344 -0
- package/package.json +68 -0
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
|
package/bin/compeek.mjs
ADDED
|
@@ -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
|
+
}
|