@rmbk/compeek 0.2.0 → 0.2.3
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 +94 -48
- package/bin/compeek.mjs +124 -57
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,75 +1,91 @@
|
|
|
1
1
|
# compeek — AI Eyes & Hands for Any Desktop
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Tell the AI what to do. Point it at any software. Watch it work.
|
|
4
|
+
>
|
|
5
|
+
> No APIs. No plugins. No integrations. Just screen and keyboard.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
[Try the Dashboard](https://compeek.rmbk.me) | [Docker Image](https://ghcr.io/uburuntu/compeek) | [npm](https://www.npmjs.com/package/@rmbk/compeek)
|
|
6
8
|
|
|
7
|
-
[
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## What can it do?
|
|
12
|
+
|
|
13
|
+
**compeek** (компик + peek) turns Claude into a desktop agent that can use any application — just like a person sitting at a computer.
|
|
14
|
+
|
|
15
|
+
- **See** any application through screenshots
|
|
16
|
+
- **Think** through complex tasks step by step (you can watch it reason)
|
|
17
|
+
- **Act** with mouse clicks, keyboard typing, and scrolling
|
|
18
|
+
- **Read** documents like passports, IDs, and invoices
|
|
19
|
+
- **Validate** its own work by checking what it filled in
|
|
20
|
+
- **Show you everything** — a real-time dashboard shows what the AI sees and thinks
|
|
8
21
|
|
|
9
22
|
## Quick Start
|
|
10
23
|
|
|
24
|
+
### Option 1: One command (recommended)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx @rmbk/compeek start --open
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This downloads a virtual desktop, starts it, and opens the dashboard in your browser.
|
|
31
|
+
|
|
32
|
+
### Option 2: Install script
|
|
33
|
+
|
|
11
34
|
```bash
|
|
12
|
-
# One-line install (Linux/macOS/WSL2)
|
|
13
35
|
curl -fsSL https://compeek.rmbk.me/install.sh | bash
|
|
36
|
+
```
|
|
14
37
|
|
|
15
|
-
|
|
16
|
-
npx compeek start --open
|
|
38
|
+
### Option 3: Docker
|
|
17
39
|
|
|
18
|
-
|
|
40
|
+
```bash
|
|
19
41
|
docker run -d -p 3001:3000 -p 6081:6080 --shm-size=512m ghcr.io/uburuntu/compeek
|
|
20
42
|
```
|
|
21
43
|
|
|
22
|
-
|
|
44
|
+
After starting, check the terminal for a **clickable link** that connects the dashboard automatically. Or run `docker logs compeek-1` to see it.
|
|
23
45
|
|
|
24
46
|
Open the [dashboard](https://compeek.rmbk.me), paste your Anthropic API key in Settings, and start a workflow.
|
|
25
47
|
|
|
26
|
-
##
|
|
48
|
+
## How It Works
|
|
27
49
|
|
|
28
|
-
|
|
50
|
+

|
|
29
51
|
|
|
30
|
-
|
|
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
|
|
52
|
+
The AI runs **in your browser** — it looks at the virtual desktop, decides what to do, and sends mouse/keyboard commands. The virtual desktop is just a Linux computer in a container — no AI runs inside it.
|
|
36
53
|
|
|
37
|
-
|
|
54
|
+
<details>
|
|
55
|
+
<summary>Technical architecture details</summary>
|
|
38
56
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
└──────────────────────┘ └──────────────────────┘
|
|
49
|
-
```
|
|
57
|
+
The agent loop runs in the browser via `@anthropic-ai/sdk` with `dangerouslyAllowBrowser: true`.
|
|
58
|
+
It uses `computer_20250124`, `bash_20250124`, and `text_editor_20250728` tools.
|
|
59
|
+
Extended thinking is enabled with a 10240 token budget.
|
|
60
|
+
|
|
61
|
+
Each container runs Ubuntu 24.04 with Xvfb (1280x720), XFWM4, x11vnc, noVNC, and Firefox (with uBlock Origin).
|
|
62
|
+
The container exposes a minimal Express tool server with endpoints:
|
|
63
|
+
`GET /api/health`, `GET /api/info`, `POST /api/tool`, `POST /api/bash`.
|
|
64
|
+
|
|
65
|
+
Communication is HTTP-only. No WebSocket, no state in containers.
|
|
50
66
|
|
|
51
|
-
|
|
67
|
+
</details>
|
|
52
68
|
|
|
53
69
|
## Desktop Modes
|
|
54
70
|
|
|
55
71
|
Set `DESKTOP_MODE` when starting a container:
|
|
56
72
|
|
|
57
|
-
| Mode | What
|
|
73
|
+
| Mode | What you get | Best for |
|
|
58
74
|
|------|-------------|----------|
|
|
59
|
-
| `full` (default) |
|
|
60
|
-
| `browser` |
|
|
61
|
-
| `minimal` |
|
|
62
|
-
| `headless` |
|
|
75
|
+
| `full` (default) | Desktop + browser + sample app | Testing forms and web apps |
|
|
76
|
+
| `browser` | Desktop + browser | General web browsing |
|
|
77
|
+
| `minimal` | Desktop only | Let the AI install what it needs |
|
|
78
|
+
| `headless` | No visual — commands only | Automated scripts |
|
|
63
79
|
|
|
64
80
|
```bash
|
|
65
|
-
npx compeek start --mode browser
|
|
81
|
+
npx @rmbk/compeek start --mode browser
|
|
66
82
|
# or
|
|
67
83
|
docker run -d -e DESKTOP_MODE=browser -p 3001:3000 -p 6081:6080 --shm-size=512m ghcr.io/uburuntu/compeek
|
|
68
84
|
```
|
|
69
85
|
|
|
70
|
-
##
|
|
86
|
+
## Connecting to a Desktop
|
|
71
87
|
|
|
72
|
-
|
|
88
|
+
The container prints a **connection code** and a **clickable link** when it starts:
|
|
73
89
|
|
|
74
90
|
```
|
|
75
91
|
Connection string: eyJuYW1lIj...
|
|
@@ -77,23 +93,53 @@ Dashboard link: https://compeek.rmbk.me/#config=eyJuYW1lIj...
|
|
|
77
93
|
```
|
|
78
94
|
|
|
79
95
|
Three ways to connect:
|
|
80
|
-
1. **Click the link** — auto-adds the session
|
|
81
|
-
2. **Paste the
|
|
82
|
-
3. **
|
|
96
|
+
1. **Click the link** in your terminal — auto-adds the session
|
|
97
|
+
2. **Paste the code** in the Add Session dialog
|
|
98
|
+
3. **Type the address** manually (host + ports)
|
|
83
99
|
|
|
84
100
|
## CLI
|
|
85
101
|
|
|
86
102
|
```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
|
|
103
|
+
npx @rmbk/compeek start # Pull image, start container, print connection info
|
|
104
|
+
npx @rmbk/compeek start --open # Same + open dashboard in browser
|
|
105
|
+
npx @rmbk/compeek stop # Stop all compeek containers
|
|
106
|
+
npx @rmbk/compeek stop 1 # Stop compeek-1
|
|
107
|
+
npx @rmbk/compeek status # List running containers
|
|
108
|
+
npx @rmbk/compeek logs # Follow container logs
|
|
109
|
+
npx @rmbk/compeek open # Open dashboard with auto-connect URL
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Flags for `start`: `--name`, `--api-port`, `--vnc-port`, `--mode`, `--persist`, `--password`, `--tunnel`, `--no-pull`, `--open`.
|
|
113
|
+
|
|
114
|
+
| Flag | Description |
|
|
115
|
+
|------|-------------|
|
|
116
|
+
| `--open` | Open dashboard in browser after starting |
|
|
117
|
+
| `--mode <m>` | Desktop mode: `full`, `browser`, `minimal`, `headless` |
|
|
118
|
+
| `--persist` | Mount a named Docker volume so files survive container restarts |
|
|
119
|
+
| `--password <pw>` | Set a custom VNC password (auto-generated if omitted) |
|
|
120
|
+
| `--tunnel` | Create public URLs via localtunnel for remote access |
|
|
121
|
+
|
|
122
|
+
## Security
|
|
123
|
+
|
|
124
|
+
Each container auto-generates a **VNC password** on startup. The password is included in the connection link so the dashboard connects seamlessly — you don't need to type it.
|
|
125
|
+
|
|
126
|
+
You can set your own password with `--password`:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
npx @rmbk/compeek start --password mysecret
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Remote access
|
|
133
|
+
|
|
134
|
+
If you're running compeek on the same machine as your browser, everything works locally — no tunneling needed.
|
|
135
|
+
|
|
136
|
+
To access a container from another machine (e.g. a remote server), use `--tunnel` to create public URLs:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
npx @rmbk/compeek start --tunnel
|
|
94
140
|
```
|
|
95
141
|
|
|
96
|
-
|
|
142
|
+
This uses [localtunnel](https://theboroer.github.io/localtunnel-www/) to make the container reachable over the internet. The VNC desktop is password-protected, but the tool API currently has no authentication — use a VPN or firewall for sensitive environments.
|
|
97
143
|
|
|
98
144
|
## Development
|
|
99
145
|
|
package/bin/compeek.mjs
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { execSync, spawn } from 'node:child_process';
|
|
7
7
|
import http from 'node:http';
|
|
8
|
+
import crypto from 'node:crypto';
|
|
8
9
|
|
|
9
10
|
const IMAGE = 'ghcr.io/uburuntu/compeek:latest';
|
|
10
11
|
const CONTAINER_PREFIX = 'compeek-';
|
|
@@ -12,6 +13,22 @@ const DASHBOARD_URL = 'https://compeek.rmbk.me';
|
|
|
12
13
|
const HEALTH_TIMEOUT = 30_000;
|
|
13
14
|
const HEALTH_INTERVAL = 1_000;
|
|
14
15
|
|
|
16
|
+
// ── ANSI Colors ──────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
const isColorSupported = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
19
|
+
const c = isColorSupported ? {
|
|
20
|
+
reset: '\x1b[0m',
|
|
21
|
+
bold: '\x1b[1m',
|
|
22
|
+
dim: '\x1b[2m',
|
|
23
|
+
cyan: '\x1b[36m',
|
|
24
|
+
green: '\x1b[32m',
|
|
25
|
+
yellow: '\x1b[33m',
|
|
26
|
+
red: '\x1b[31m',
|
|
27
|
+
magenta: '\x1b[35m',
|
|
28
|
+
white: '\x1b[97m',
|
|
29
|
+
gray: '\x1b[90m',
|
|
30
|
+
} : { reset: '', bold: '', dim: '', cyan: '', green: '', yellow: '', red: '', magenta: '', white: '', gray: '' };
|
|
31
|
+
|
|
15
32
|
// ── Helpers ──────────────────────────────────────────────
|
|
16
33
|
|
|
17
34
|
function run(cmd, opts = {}) {
|
|
@@ -70,6 +87,19 @@ function findNextName() {
|
|
|
70
87
|
}
|
|
71
88
|
|
|
72
89
|
function waitForHealth(host, port, timeout) {
|
|
90
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
91
|
+
let frame = 0;
|
|
92
|
+
let spinner;
|
|
93
|
+
|
|
94
|
+
if (isColorSupported) {
|
|
95
|
+
spinner = setInterval(() => {
|
|
96
|
+
process.stdout.write(`\r ${c.cyan}${frames[frame]}${c.reset} Waiting for container...`);
|
|
97
|
+
frame = (frame + 1) % frames.length;
|
|
98
|
+
}, 80);
|
|
99
|
+
} else {
|
|
100
|
+
console.log(' Waiting for container...');
|
|
101
|
+
}
|
|
102
|
+
|
|
73
103
|
return new Promise((resolve, reject) => {
|
|
74
104
|
const start = Date.now();
|
|
75
105
|
const check = () => {
|
|
@@ -79,7 +109,13 @@ function waitForHealth(host, port, timeout) {
|
|
|
79
109
|
res.on('end', () => {
|
|
80
110
|
try {
|
|
81
111
|
const data = JSON.parse(body);
|
|
82
|
-
if (data.status === 'ok')
|
|
112
|
+
if (data.status === 'ok') {
|
|
113
|
+
if (spinner) {
|
|
114
|
+
clearInterval(spinner);
|
|
115
|
+
process.stdout.write(`\r ${c.green}✓${c.reset} Container ready \n`);
|
|
116
|
+
}
|
|
117
|
+
return resolve(data);
|
|
118
|
+
}
|
|
83
119
|
} catch { /* retry */ }
|
|
84
120
|
retry();
|
|
85
121
|
});
|
|
@@ -88,16 +124,23 @@ function waitForHealth(host, port, timeout) {
|
|
|
88
124
|
req.on('timeout', () => { req.destroy(); retry(); });
|
|
89
125
|
};
|
|
90
126
|
const retry = () => {
|
|
91
|
-
if (Date.now() - start > timeout)
|
|
127
|
+
if (Date.now() - start > timeout) {
|
|
128
|
+
if (spinner) {
|
|
129
|
+
clearInterval(spinner);
|
|
130
|
+
process.stdout.write(`\r ${c.red}✗${c.reset} Health check timed out \n`);
|
|
131
|
+
}
|
|
132
|
+
return reject(new Error('Health check timed out'));
|
|
133
|
+
}
|
|
92
134
|
setTimeout(check, HEALTH_INTERVAL);
|
|
93
135
|
};
|
|
94
136
|
check();
|
|
95
137
|
});
|
|
96
138
|
}
|
|
97
139
|
|
|
98
|
-
function buildConnectionString(name, apiHost, apiPort, vncHost, vncPort) {
|
|
99
|
-
const config =
|
|
100
|
-
|
|
140
|
+
function buildConnectionString(name, apiHost, apiPort, vncHost, vncPort, vncPassword) {
|
|
141
|
+
const config = { name, type: 'compeek', apiHost, apiPort, vncHost, vncPort };
|
|
142
|
+
if (vncPassword) config.vncPassword = vncPassword;
|
|
143
|
+
return Buffer.from(JSON.stringify(config)).toString('base64');
|
|
101
144
|
}
|
|
102
145
|
|
|
103
146
|
function openUrl(url) {
|
|
@@ -115,7 +158,7 @@ function openUrl(url) {
|
|
|
115
158
|
|
|
116
159
|
async function cmdStart(args) {
|
|
117
160
|
if (!hasDocker()) {
|
|
118
|
-
console.error(
|
|
161
|
+
console.error(`${c.red}Docker is not available.${c.reset} Install Docker first: https://docs.docker.com/get-docker/`);
|
|
119
162
|
process.exit(1);
|
|
120
163
|
}
|
|
121
164
|
|
|
@@ -125,18 +168,32 @@ async function cmdStart(args) {
|
|
|
125
168
|
const apiPort = parseInt(flags['api-port']) || defaultApi;
|
|
126
169
|
const vncPort = parseInt(flags['vnc-port']) || defaultVnc;
|
|
127
170
|
const mode = flags.mode || 'full';
|
|
171
|
+
const vncPassword = flags.password || crypto.randomBytes(6).toString('base64url').slice(0, 8);
|
|
128
172
|
const sessionName = name.replace(CONTAINER_PREFIX, '').replace(/^(\d+)$/, 'Desktop $1');
|
|
129
173
|
|
|
174
|
+
console.log('');
|
|
175
|
+
console.log(` ${c.bold}${c.cyan}compeek${c.reset}`);
|
|
176
|
+
console.log('');
|
|
177
|
+
|
|
130
178
|
if (!flags['no-pull']) {
|
|
131
|
-
console.log(`Pulling
|
|
179
|
+
console.log(` ${c.dim}Pulling image...${c.reset}`);
|
|
132
180
|
try {
|
|
133
181
|
run(`docker pull ${IMAGE}`, { stdio: 'inherit' });
|
|
134
182
|
} catch {
|
|
135
|
-
console.log(
|
|
183
|
+
console.log(` ${c.yellow}Pull failed, using cached image.${c.reset}`);
|
|
136
184
|
}
|
|
185
|
+
console.log('');
|
|
137
186
|
}
|
|
138
187
|
|
|
139
|
-
|
|
188
|
+
const info = [
|
|
189
|
+
`mode:${c.white}${mode}${c.reset}`,
|
|
190
|
+
`api:${c.white}${apiPort}${c.reset}`,
|
|
191
|
+
`vnc:${c.white}${vncPort}${c.reset}`,
|
|
192
|
+
];
|
|
193
|
+
if (flags.persist) info.push(`${c.green}persist${c.reset}`);
|
|
194
|
+
if (flags.tunnel) info.push(`${c.yellow}tunnel${c.reset}`);
|
|
195
|
+
|
|
196
|
+
console.log(` ${c.cyan}▸${c.reset} Starting ${c.bold}${name}${c.reset} ${c.dim}${info.join(' · ')}${c.reset}`);
|
|
140
197
|
|
|
141
198
|
// Remove existing container with same name if stopped
|
|
142
199
|
run(`docker rm -f ${name}`, { allowFail: true });
|
|
@@ -149,41 +206,40 @@ async function cmdStart(args) {
|
|
|
149
206
|
`--shm-size=512m`,
|
|
150
207
|
`-e DISPLAY=:1`,
|
|
151
208
|
`-e DESKTOP_MODE=${mode}`,
|
|
152
|
-
`-e COMPEEK_SESSION_NAME
|
|
209
|
+
`-e COMPEEK_SESSION_NAME="${sessionName}"`,
|
|
210
|
+
`-e VNC_PASSWORD="${vncPassword}"`,
|
|
211
|
+
flags.tunnel ? '-e ENABLE_TUNNEL=true' : '',
|
|
212
|
+
flags.persist ? `-v ${name}-data:/home/compeek/data` : '',
|
|
153
213
|
`--security-opt seccomp=unconfined`,
|
|
154
214
|
IMAGE,
|
|
155
|
-
].join(' '));
|
|
156
|
-
|
|
157
|
-
console.log('Waiting for container to be ready...');
|
|
215
|
+
].filter(Boolean).join(' '));
|
|
158
216
|
|
|
159
217
|
try {
|
|
160
218
|
await waitForHealth('localhost', apiPort, HEALTH_TIMEOUT);
|
|
161
|
-
console.log('Container is ready.');
|
|
162
219
|
} catch {
|
|
163
|
-
console.error(
|
|
220
|
+
console.error(`\n ${c.red}Container did not start.${c.reset} Check logs: npx compeek logs`);
|
|
164
221
|
process.exit(1);
|
|
165
222
|
}
|
|
166
223
|
|
|
167
|
-
const connStr = buildConnectionString(sessionName, 'localhost', apiPort, 'localhost', vncPort);
|
|
224
|
+
const connStr = buildConnectionString(sessionName, 'localhost', apiPort, 'localhost', vncPort, vncPassword);
|
|
225
|
+
const dashboardLink = `${DASHBOARD_URL}/#config=${connStr}`;
|
|
168
226
|
|
|
169
227
|
console.log('');
|
|
170
|
-
console.log(
|
|
171
|
-
console.log(
|
|
172
|
-
console.log(
|
|
173
|
-
console.log(` Tool API
|
|
228
|
+
console.log(` ${c.dim}──── Links ─────────────────────────────────────${c.reset}`);
|
|
229
|
+
console.log('');
|
|
230
|
+
console.log(` ${c.bold}Dashboard${c.reset} ${c.cyan}${dashboardLink}${c.reset}`);
|
|
231
|
+
console.log(` ${c.dim}Tool API${c.reset} http://localhost:${apiPort}`);
|
|
174
232
|
if (mode !== 'headless') {
|
|
175
|
-
console.log(` noVNC
|
|
233
|
+
console.log(` ${c.dim}noVNC${c.reset} http://localhost:${vncPort}`);
|
|
234
|
+
console.log(` ${c.dim}Password${c.reset} ${vncPassword}`);
|
|
176
235
|
}
|
|
177
236
|
console.log('');
|
|
178
|
-
console.log(
|
|
179
|
-
console.log(` ${connStr}`);
|
|
237
|
+
console.log(` ${c.dim}──── Connection string ──────────────────────────${c.reset}`);
|
|
238
|
+
console.log(` ${c.dim}${connStr}${c.reset}`);
|
|
180
239
|
console.log('');
|
|
181
|
-
console.log(' Dashboard link:');
|
|
182
|
-
console.log(` ${DASHBOARD_URL}/#config=${connStr}`);
|
|
183
|
-
console.log('=========================================');
|
|
184
240
|
|
|
185
241
|
if (flags.open) {
|
|
186
|
-
openUrl(
|
|
242
|
+
openUrl(dashboardLink);
|
|
187
243
|
}
|
|
188
244
|
}
|
|
189
245
|
|
|
@@ -191,33 +247,41 @@ function cmdStop(args) {
|
|
|
191
247
|
const target = args[0];
|
|
192
248
|
if (target) {
|
|
193
249
|
const name = target.startsWith(CONTAINER_PREFIX) ? target : `${CONTAINER_PREFIX}${target}`;
|
|
194
|
-
console.log(`Stopping ${name}...`);
|
|
250
|
+
console.log(` ${c.cyan}▸${c.reset} Stopping ${c.bold}${name}${c.reset}...`);
|
|
195
251
|
run(`docker rm -f ${name}`, { allowFail: true, stdio: 'inherit' });
|
|
252
|
+
console.log(` ${c.green}✓${c.reset} Stopped`);
|
|
196
253
|
} else {
|
|
197
254
|
const containers = listContainers();
|
|
198
255
|
if (containers.length === 0) {
|
|
199
|
-
console.log(
|
|
256
|
+
console.log(` ${c.dim}No compeek containers running.${c.reset}`);
|
|
200
257
|
return;
|
|
201
258
|
}
|
|
202
|
-
for (const
|
|
203
|
-
console.log(`Stopping ${c.name}...`);
|
|
204
|
-
run(`docker rm -f ${
|
|
259
|
+
for (const ctr of containers) {
|
|
260
|
+
console.log(` ${c.cyan}▸${c.reset} Stopping ${c.bold}${ctr.name}${c.reset}...`);
|
|
261
|
+
run(`docker rm -f ${ctr.name}`, { allowFail: true });
|
|
205
262
|
}
|
|
206
|
-
console.log(`Stopped ${containers.length} container(s)
|
|
263
|
+
console.log(` ${c.green}✓${c.reset} Stopped ${containers.length} container(s)`);
|
|
207
264
|
}
|
|
208
265
|
}
|
|
209
266
|
|
|
210
267
|
function cmdStatus() {
|
|
211
268
|
const containers = listContainers();
|
|
212
269
|
if (containers.length === 0) {
|
|
213
|
-
console.log(
|
|
270
|
+
console.log(` ${c.dim}No compeek containers found.${c.reset}`);
|
|
214
271
|
return;
|
|
215
272
|
}
|
|
216
|
-
console.log('
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
273
|
+
console.log('');
|
|
274
|
+
for (const ctr of containers) {
|
|
275
|
+
const isUp = ctr.status.startsWith('Up');
|
|
276
|
+
const dot = isUp ? `${c.green}●${c.reset}` : `${c.red}●${c.reset}`;
|
|
277
|
+
const statusText = isUp ? `${c.green}${ctr.status}${c.reset}` : `${c.dim}${ctr.status}${c.reset}`;
|
|
278
|
+
console.log(` ${dot} ${c.bold}${ctr.name}${c.reset} ${statusText}`);
|
|
279
|
+
if (ctr.ports) {
|
|
280
|
+
const portList = ctr.ports.replace(/0\.0\.0\.0:/g, ':').replace(/:::(\d+)/g, ':$1');
|
|
281
|
+
console.log(` ${c.dim}${portList}${c.reset}`);
|
|
282
|
+
}
|
|
220
283
|
}
|
|
284
|
+
console.log('');
|
|
221
285
|
}
|
|
222
286
|
|
|
223
287
|
function cmdLogs(args) {
|
|
@@ -284,7 +348,7 @@ function parseFlags(args) {
|
|
|
284
348
|
const arg = args[i];
|
|
285
349
|
if (arg.startsWith('--')) {
|
|
286
350
|
const key = arg.slice(2);
|
|
287
|
-
if (key === 'open' || key === 'no-pull') {
|
|
351
|
+
if (key === 'open' || key === 'no-pull' || key === 'persist' || key === 'tunnel') {
|
|
288
352
|
flags[key] = true;
|
|
289
353
|
} else if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
|
|
290
354
|
flags[key] = args[++i];
|
|
@@ -318,24 +382,27 @@ switch (command) {
|
|
|
318
382
|
case '-h':
|
|
319
383
|
case 'help':
|
|
320
384
|
console.log(`
|
|
321
|
-
compeek — AI desktop
|
|
322
|
-
|
|
323
|
-
Usage
|
|
324
|
-
|
|
325
|
-
Commands
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
385
|
+
${c.bold}${c.cyan}compeek${c.reset} ${c.dim}— AI eyes & hands for any desktop${c.reset}
|
|
386
|
+
|
|
387
|
+
${c.bold}Usage${c.reset} npx @rmbk/compeek ${c.dim}[command] [options]${c.reset}
|
|
388
|
+
|
|
389
|
+
${c.bold}Commands${c.reset}
|
|
390
|
+
start ${c.dim}............${c.reset} Start a new virtual desktop ${c.dim}(default)${c.reset}
|
|
391
|
+
stop ${c.dim}[name]${c.reset} ${c.dim}....${c.reset} Stop one or all containers
|
|
392
|
+
status ${c.dim}...........${c.reset} List running containers
|
|
393
|
+
logs ${c.dim}[name]${c.reset} ${c.dim}....${c.reset} Follow container logs
|
|
394
|
+
open ${c.dim}[name]${c.reset} ${c.dim}....${c.reset} Open dashboard in browser
|
|
395
|
+
|
|
396
|
+
${c.bold}Options${c.reset}
|
|
397
|
+
--open ${c.dim}..........${c.reset} Open dashboard after start
|
|
398
|
+
--mode ${c.dim}<m>${c.reset} ${c.dim}......${c.reset} full ${c.dim}|${c.reset} browser ${c.dim}|${c.reset} minimal ${c.dim}|${c.reset} headless
|
|
399
|
+
--persist ${c.dim}.......${c.reset} Mount volume for persistent data
|
|
400
|
+
--password ${c.dim}<pw>${c.reset} ${c.dim}.${c.reset} Custom VNC password ${c.dim}(auto-generated if omitted)${c.reset}
|
|
401
|
+
--tunnel ${c.dim}........${c.reset} Enable localtunnel for remote access
|
|
402
|
+
--no-pull ${c.dim}.......${c.reset} Skip pulling latest Docker image
|
|
403
|
+
--name ${c.dim}<n>${c.reset} ${c.dim}......${c.reset} Custom container name
|
|
404
|
+
--api-port ${c.dim}<p>${c.reset} ${c.dim}.${c.reset} Host port for tool API
|
|
405
|
+
--vnc-port ${c.dim}<p>${c.reset} ${c.dim}.${c.reset} Host port for noVNC
|
|
339
406
|
`);
|
|
340
407
|
break;
|
|
341
408
|
default:
|
package/package.json
CHANGED