@rmbk/compeek 0.2.3 → 0.2.4

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.
Files changed (2) hide show
  1. package/bin/compeek.mjs +101 -10
  2. package/package.json +1 -1
package/bin/compeek.mjs CHANGED
@@ -137,6 +137,57 @@ function waitForHealth(host, port, timeout) {
137
137
  });
138
138
  }
139
139
 
140
+ function waitForTunnelUrls(host, port, timeout) {
141
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
142
+ let frame = 0;
143
+ let spinner;
144
+
145
+ if (isColorSupported) {
146
+ spinner = setInterval(() => {
147
+ process.stdout.write(`\r ${c.cyan}${frames[frame]}${c.reset} Waiting for tunnel URLs...`);
148
+ frame = (frame + 1) % frames.length;
149
+ }, 80);
150
+ } else {
151
+ console.log(' Waiting for tunnel URLs...');
152
+ }
153
+
154
+ return new Promise((resolve) => {
155
+ const start = Date.now();
156
+ const check = () => {
157
+ const req = http.get(`http://${host}:${port}/api/info`, { timeout: 2000 }, (res) => {
158
+ let body = '';
159
+ res.on('data', d => body += d);
160
+ res.on('end', () => {
161
+ try {
162
+ const data = JSON.parse(body);
163
+ if (data.tunnel?.apiUrl && data.tunnel?.vncUrl) {
164
+ if (spinner) {
165
+ clearInterval(spinner);
166
+ process.stdout.write(`\r ${c.green}✓${c.reset} Tunnels ready \n`);
167
+ }
168
+ return resolve(data.tunnel);
169
+ }
170
+ } catch { /* retry */ }
171
+ retry();
172
+ });
173
+ });
174
+ req.on('error', retry);
175
+ req.on('timeout', () => { req.destroy(); retry(); });
176
+ };
177
+ const retry = () => {
178
+ if (Date.now() - start > timeout) {
179
+ if (spinner) {
180
+ clearInterval(spinner);
181
+ process.stdout.write(`\r ${c.yellow}!${c.reset} Tunnel timed out (local-only mode)\n`);
182
+ }
183
+ return resolve(null);
184
+ }
185
+ setTimeout(check, HEALTH_INTERVAL);
186
+ };
187
+ check();
188
+ });
189
+ }
190
+
140
191
  function buildConnectionString(name, apiHost, apiPort, vncHost, vncPort, vncPassword) {
141
192
  const config = { name, type: 'compeek', apiHost, apiPort, vncHost, vncPort };
142
193
  if (vncPassword) config.vncPassword = vncPassword;
@@ -168,9 +219,15 @@ async function cmdStart(args) {
168
219
  const apiPort = parseInt(flags['api-port']) || defaultApi;
169
220
  const vncPort = parseInt(flags['vnc-port']) || defaultVnc;
170
221
  const mode = flags.mode || 'full';
171
- const vncPassword = flags.password || crypto.randomBytes(6).toString('base64url').slice(0, 8);
222
+ const vncPassword = flags.password || crypto.randomBytes(24).toString('base64url').slice(0, 24);
172
223
  const sessionName = name.replace(CONTAINER_PREFIX, '').replace(/^(\d+)$/, 'Desktop $1');
173
224
 
225
+ // Tunnel provider: cloudflare by default, --no-tunnel to disable, --tunnel <provider> to override
226
+ const tunnelProvider = flags['no-tunnel'] ? 'none'
227
+ : typeof flags.tunnel === 'string' ? flags.tunnel
228
+ : flags.tunnel === true ? 'cloudflare'
229
+ : 'cloudflare';
230
+
174
231
  console.log('');
175
232
  console.log(` ${c.bold}${c.cyan}compeek${c.reset}`);
176
233
  console.log('');
@@ -191,7 +248,7 @@ async function cmdStart(args) {
191
248
  `vnc:${c.white}${vncPort}${c.reset}`,
192
249
  ];
193
250
  if (flags.persist) info.push(`${c.green}persist${c.reset}`);
194
- if (flags.tunnel) info.push(`${c.yellow}tunnel${c.reset}`);
251
+ if (tunnelProvider !== 'none') info.push(`${c.yellow}${tunnelProvider}${c.reset}`);
195
252
 
196
253
  console.log(` ${c.cyan}▸${c.reset} Starting ${c.bold}${name}${c.reset} ${c.dim}${info.join(' · ')}${c.reset}`);
197
254
 
@@ -208,7 +265,7 @@ async function cmdStart(args) {
208
265
  `-e DESKTOP_MODE=${mode}`,
209
266
  `-e COMPEEK_SESSION_NAME="${sessionName}"`,
210
267
  `-e VNC_PASSWORD="${vncPassword}"`,
211
- flags.tunnel ? '-e ENABLE_TUNNEL=true' : '',
268
+ `-e TUNNEL_PROVIDER=${tunnelProvider}`,
212
269
  flags.persist ? `-v ${name}-data:/home/compeek/data` : '',
213
270
  `--security-opt seccomp=unconfined`,
214
271
  IMAGE,
@@ -221,21 +278,47 @@ async function cmdStart(args) {
221
278
  process.exit(1);
222
279
  }
223
280
 
224
- const connStr = buildConnectionString(sessionName, 'localhost', apiPort, 'localhost', vncPort, vncPassword);
225
- const dashboardLink = `${DASHBOARD_URL}/#config=${connStr}`;
281
+ // Wait for tunnel URLs if tunneling is enabled
282
+ let tunnel = null;
283
+ if (tunnelProvider !== 'none' && mode !== 'headless') {
284
+ tunnel = await waitForTunnelUrls('localhost', apiPort, 30_000);
285
+ }
286
+
287
+ // Build connection strings
288
+ const localConnStr = buildConnectionString(sessionName, 'localhost', apiPort, 'localhost', vncPort, vncPassword);
289
+ let tunnelConnStr = null;
290
+ let dashboardLink;
291
+
292
+ if (tunnel) {
293
+ const apiUrl = new URL(tunnel.apiUrl);
294
+ const vncUrl = new URL(tunnel.vncUrl);
295
+ tunnelConnStr = buildConnectionString(
296
+ sessionName,
297
+ apiUrl.hostname, 443,
298
+ vncUrl.hostname, 443,
299
+ vncPassword,
300
+ );
301
+ dashboardLink = `${DASHBOARD_URL}/#config=${tunnelConnStr}`;
302
+ } else {
303
+ dashboardLink = `${DASHBOARD_URL}/#config=${localConnStr}`;
304
+ }
226
305
 
227
306
  console.log('');
228
307
  console.log(` ${c.dim}──── Links ─────────────────────────────────────${c.reset}`);
229
308
  console.log('');
230
309
  console.log(` ${c.bold}Dashboard${c.reset} ${c.cyan}${dashboardLink}${c.reset}`);
231
- console.log(` ${c.dim}Tool API${c.reset} http://localhost:${apiPort}`);
310
+ if (tunnel) {
311
+ console.log(` ${c.dim}API${c.reset} ${tunnel.apiUrl}`);
312
+ console.log(` ${c.dim}noVNC${c.reset} ${tunnel.vncUrl}`);
313
+ }
314
+ console.log(` ${c.dim}Local API${c.reset} http://localhost:${apiPort}`);
232
315
  if (mode !== 'headless') {
233
- console.log(` ${c.dim}noVNC${c.reset} http://localhost:${vncPort}`);
316
+ console.log(` ${c.dim}Local VNC${c.reset} http://localhost:${vncPort}`);
234
317
  console.log(` ${c.dim}Password${c.reset} ${vncPassword}`);
235
318
  }
236
319
  console.log('');
237
320
  console.log(` ${c.dim}──── Connection string ──────────────────────────${c.reset}`);
238
- console.log(` ${c.dim}${connStr}${c.reset}`);
321
+ console.log(` ${c.dim}${tunnelConnStr || localConnStr}${c.reset}`);
239
322
  console.log('');
240
323
 
241
324
  if (flags.open) {
@@ -348,8 +431,15 @@ function parseFlags(args) {
348
431
  const arg = args[i];
349
432
  if (arg.startsWith('--')) {
350
433
  const key = arg.slice(2);
351
- if (key === 'open' || key === 'no-pull' || key === 'persist' || key === 'tunnel') {
434
+ if (key === 'open' || key === 'no-pull' || key === 'persist' || key === 'no-tunnel') {
352
435
  flags[key] = true;
436
+ } else if (key === 'tunnel') {
437
+ // --tunnel (default provider) or --tunnel cloudflare / --tunnel localtunnel
438
+ if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
439
+ flags[key] = args[++i];
440
+ } else {
441
+ flags[key] = true;
442
+ }
353
443
  } else if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
354
444
  flags[key] = args[++i];
355
445
  }
@@ -398,7 +488,8 @@ switch (command) {
398
488
  --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
489
  --persist ${c.dim}.......${c.reset} Mount volume for persistent data
400
490
  --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
491
+ --no-tunnel ${c.dim}.....${c.reset} Disable tunneling ${c.dim}(local-only mode)${c.reset}
492
+ --tunnel ${c.dim}<p>${c.reset} ${c.dim}.....${c.reset} cloudflare ${c.dim}(default)${c.reset} ${c.dim}|${c.reset} localtunnel
402
493
  --no-pull ${c.dim}.......${c.reset} Skip pulling latest Docker image
403
494
  --name ${c.dim}<n>${c.reset} ${c.dim}......${c.reset} Custom container name
404
495
  --api-port ${c.dim}<p>${c.reset} ${c.dim}.${c.reset} Host port for tool API
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmbk/compeek",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "AI eyes and hands for any desktop application — a general-purpose computer use agent framework powered by Claude Opus 4.6",
5
5
  "license": "MIT",
6
6
  "author": "rmbk",