glidercli 0.1.5 → 0.3.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/README.md +70 -9
- package/bin/glider.js +785 -57
- package/lib/bcdp.js +482 -0
- package/lib/beval.js +78 -0
- package/lib/bexplore.js +815 -0
- package/lib/bextract.js +236 -0
- package/lib/bfetch.js +274 -0
- package/lib/bserve.js +347 -0
- package/lib/bspawn.js +154 -0
- package/lib/bwindow.js +335 -0
- package/lib/cdp-direct.js +305 -0
- package/lib/glider-daemon.sh +31 -0
- package/package.json +8 -2
- package/.git-personal-enforced +0 -4
- package/.github/hooks/post-checkout +0 -24
- package/.github/hooks/post-commit +0 -13
- package/.github/hooks/pre-commit +0 -30
- package/.github/hooks/pre-push +0 -13
- package/.github/scripts/health-check.sh +0 -127
- package/.github/scripts/setup.sh +0 -19
- package/.github/workflows/release.yml +0 -19
- package/assets/icons/.gitkeep +0 -0
- package/assets/icons/claude.webp +0 -0
- package/assets/icons/glider-blue-squircle.webp +0 -0
- package/assets/icons/ralph-wiggum.webp +0 -0
- package/repo.config.json +0 -31
package/bin/glider.js
CHANGED
|
@@ -31,11 +31,16 @@ const YAML = require('yaml');
|
|
|
31
31
|
|
|
32
32
|
// Config
|
|
33
33
|
const PORT = process.env.GLIDER_PORT || 19988;
|
|
34
|
+
const DEBUG_PORT = process.env.GLIDER_DEBUG_PORT || 9222;
|
|
34
35
|
const SERVER_URL = `http://127.0.0.1:${PORT}`;
|
|
35
|
-
const
|
|
36
|
+
const DEBUG_URL = `http://127.0.0.1:${DEBUG_PORT}`;
|
|
37
|
+
const LIB_DIR = path.join(__dirname, '..', 'lib');
|
|
36
38
|
const STATE_FILE = '/tmp/glider-state.json';
|
|
37
39
|
const LOG_FILE = '/tmp/glider.log';
|
|
38
40
|
|
|
41
|
+
// Direct CDP module
|
|
42
|
+
const { DirectCDP, checkChrome } = require(path.join(LIB_DIR, 'cdp-direct.js'));
|
|
43
|
+
|
|
39
44
|
// Domain extensions - load from ~/.cursor/glider/domains.json or ~/.glider/domains.json
|
|
40
45
|
const DOMAIN_CONFIG_PATHS = [
|
|
41
46
|
path.join(os.homedir(), '.cursor', 'glider', 'domains.json'),
|
|
@@ -51,7 +56,7 @@ for (const cfgPath of DOMAIN_CONFIG_PATHS) {
|
|
|
51
56
|
}
|
|
52
57
|
}
|
|
53
58
|
|
|
54
|
-
// Colors
|
|
59
|
+
// Colors - matching the deep blue gradient logo
|
|
55
60
|
const RED = '\x1b[31m';
|
|
56
61
|
const GREEN = '\x1b[32m';
|
|
57
62
|
const YELLOW = '\x1b[33m';
|
|
@@ -63,25 +68,30 @@ const BOLD = '\x1b[1m';
|
|
|
63
68
|
const DIM = '\x1b[2m';
|
|
64
69
|
const NC = '\x1b[0m';
|
|
65
70
|
|
|
66
|
-
//
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
const
|
|
71
|
+
// Deep blue gradient (matching logo)
|
|
72
|
+
const B1 = '\x1b[38;5;17m'; // darkest navy
|
|
73
|
+
const B2 = '\x1b[38;5;18m'; // dark navy
|
|
74
|
+
const B3 = '\x1b[38;5;19m'; // navy
|
|
75
|
+
const B4 = '\x1b[38;5;20m'; // blue
|
|
76
|
+
const B5 = '\x1b[38;5;27m'; // bright blue
|
|
77
|
+
const B6 = '\x1b[38;5;33m'; // sky blue
|
|
78
|
+
const BW = '\x1b[38;5;255m'; // white (for glider icon)
|
|
73
79
|
|
|
74
|
-
// Banner -
|
|
80
|
+
// Banner - hang glider ASCII art matching logo
|
|
75
81
|
const BANNER = `
|
|
76
|
-
${
|
|
77
|
-
${
|
|
78
|
-
${
|
|
79
|
-
${
|
|
80
|
-
${
|
|
81
|
-
${
|
|
82
|
-
${
|
|
83
|
-
${
|
|
84
|
-
${
|
|
82
|
+
${B1} ╔══════════════════════════════════════════════════════════╗${NC}
|
|
83
|
+
${B2} ║${NC} ${B2}║${NC}
|
|
84
|
+
${B3} ║${NC} ${BW} ___________________________________${NC} ${B3}║${NC}
|
|
85
|
+
${B4} ║${NC} ${BW} ╲ ╲${NC} ${B4}║${NC}
|
|
86
|
+
${B5} ║${NC} ${BW} ╲___________________________________╲${NC} ${B5}║${NC}
|
|
87
|
+
${B5} ║${NC} ${BW} ╲ ╱${NC} ${B5}║${NC}
|
|
88
|
+
${B6} ║${NC} ${BW} ╲_______________________________╱${NC} ${B6}║${NC}
|
|
89
|
+
${B6} ║${NC} ${B6}║${NC}
|
|
90
|
+
${B5} ║${NC} ${BW}${BOLD}G L I D E R${NC} ${B5}║${NC}
|
|
91
|
+
${B4} ║${NC} ${DIM}Browser Automation CLI${NC} ${B5}v${require('../package.json').version}${NC} ${B4}║${NC}
|
|
92
|
+
${B3} ║${NC} ${DIM}github.com/vdutts7/glidercli${NC} ${B3}║${NC}
|
|
93
|
+
${B2} ║${NC} ${B2}║${NC}
|
|
94
|
+
${B1} ╚══════════════════════════════════════════════════════════╝${NC}
|
|
85
95
|
`;
|
|
86
96
|
|
|
87
97
|
function showBanner() {
|
|
@@ -91,12 +101,26 @@ function showBanner() {
|
|
|
91
101
|
const log = {
|
|
92
102
|
ok: (msg) => console.error(`${GREEN}✓${NC} ${msg}`),
|
|
93
103
|
fail: (msg) => console.error(`${RED}✗${NC} ${msg}`),
|
|
94
|
-
info: (msg) => console.error(`${
|
|
95
|
-
warn: (msg) => console.error(`${YELLOW}
|
|
96
|
-
step: (msg) => console.error(`${
|
|
104
|
+
info: (msg) => console.error(`${B5}→${NC} ${msg}`),
|
|
105
|
+
warn: (msg) => console.error(`${YELLOW}⚠${NC} ${msg}`),
|
|
106
|
+
step: (msg) => console.error(`${B6}▸${NC} ${msg}`),
|
|
97
107
|
result: (msg) => console.log(msg),
|
|
108
|
+
box: (title) => {
|
|
109
|
+
const line = '─'.repeat(50);
|
|
110
|
+
console.log(`${B3}┌${line}┐${NC}`);
|
|
111
|
+
console.log(`${B4}│${NC} ${BW}${BOLD}${title.padEnd(48)}${NC} ${B4}│${NC}`);
|
|
112
|
+
console.log(`${B5}└${line}┘${NC}`);
|
|
113
|
+
},
|
|
98
114
|
};
|
|
99
115
|
|
|
116
|
+
// macOS notification helper
|
|
117
|
+
function notify(title, message, sound = false) {
|
|
118
|
+
try {
|
|
119
|
+
const soundFlag = sound ? 'sound name "Ping"' : '';
|
|
120
|
+
execSync(`osascript -e 'display notification "${message}" with title "${title}" ${soundFlag}'`, { stdio: 'ignore' });
|
|
121
|
+
} catch {}
|
|
122
|
+
}
|
|
123
|
+
|
|
100
124
|
// HTTP helpers
|
|
101
125
|
function httpGet(urlPath) {
|
|
102
126
|
return new Promise((resolve, reject) => {
|
|
@@ -179,31 +203,32 @@ async function getTargets() {
|
|
|
179
203
|
// Commands
|
|
180
204
|
async function cmdStatus() {
|
|
181
205
|
showBanner();
|
|
182
|
-
|
|
183
|
-
console.log(' STATUS');
|
|
184
|
-
console.log('═══════════════════════════════════════');
|
|
206
|
+
log.box('STATUS');
|
|
185
207
|
|
|
186
208
|
const serverOk = await checkServer();
|
|
187
|
-
console.log(serverOk ?
|
|
209
|
+
console.log(serverOk ? ` ${GREEN}✓${NC} Server running on port ${PORT}` : ` ${RED}✗${NC} Server not running`);
|
|
188
210
|
|
|
189
211
|
if (serverOk) {
|
|
190
212
|
const extOk = await checkExtension();
|
|
191
|
-
console.log(extOk ?
|
|
213
|
+
console.log(extOk ? ` ${GREEN}✓${NC} Extension connected` : ` ${RED}✗${NC} Extension not connected`);
|
|
192
214
|
|
|
193
215
|
if (extOk) {
|
|
194
216
|
const targets = await getTargets();
|
|
195
217
|
if (targets.length > 0) {
|
|
196
|
-
console.log(
|
|
218
|
+
console.log(` ${GREEN}✓${NC} ${targets.length} tab(s) connected:`);
|
|
197
219
|
targets.forEach(t => {
|
|
198
220
|
const url = t.targetInfo?.url || 'unknown';
|
|
199
|
-
console.log(` ${
|
|
221
|
+
console.log(` ${B5}${url}${NC}`);
|
|
200
222
|
});
|
|
201
223
|
} else {
|
|
202
|
-
console.log(
|
|
224
|
+
console.log(` ${YELLOW}⚠${NC} No tabs connected`);
|
|
225
|
+
console.log(` ${DIM}Run: glider connect${NC}`);
|
|
203
226
|
}
|
|
204
227
|
}
|
|
228
|
+
} else {
|
|
229
|
+
console.log(` ${DIM}Run: glider install${NC}`);
|
|
205
230
|
}
|
|
206
|
-
console.log(
|
|
231
|
+
console.log();
|
|
207
232
|
}
|
|
208
233
|
|
|
209
234
|
async function cmdStart() {
|
|
@@ -213,7 +238,7 @@ async function cmdStart() {
|
|
|
213
238
|
}
|
|
214
239
|
|
|
215
240
|
log.info('Starting glider server...');
|
|
216
|
-
const bserve = path.join(
|
|
241
|
+
const bserve = path.join(LIB_DIR, 'bserve.js');
|
|
217
242
|
|
|
218
243
|
if (!fs.existsSync(bserve)) {
|
|
219
244
|
log.fail(`bserve not found at ${bserve}`);
|
|
@@ -401,6 +426,621 @@ async function cmdText() {
|
|
|
401
426
|
}
|
|
402
427
|
}
|
|
403
428
|
|
|
429
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
430
|
+
// NEW COMMANDS: restart, test, tabs, domains, open, html, title, url
|
|
431
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
432
|
+
|
|
433
|
+
async function cmdRestart() {
|
|
434
|
+
await cmdStop();
|
|
435
|
+
await new Promise(r => setTimeout(r, 500));
|
|
436
|
+
await cmdStart();
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Daemon management - runs forever, respawns on crash
|
|
440
|
+
async function cmdInstallDaemon() {
|
|
441
|
+
const home = os.homedir();
|
|
442
|
+
const daemonScript = path.join(LIB_DIR, 'glider-daemon.sh');
|
|
443
|
+
const logDir = path.join(home, '.glider');
|
|
444
|
+
const pidFile = path.join(logDir, 'daemon.pid');
|
|
445
|
+
|
|
446
|
+
// Create log directory
|
|
447
|
+
if (!fs.existsSync(logDir)) {
|
|
448
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Kill existing daemon
|
|
452
|
+
if (fs.existsSync(pidFile)) {
|
|
453
|
+
try {
|
|
454
|
+
const pid = fs.readFileSync(pidFile, 'utf8').trim();
|
|
455
|
+
execSync(`kill ${pid} 2>/dev/null || true`, { stdio: 'ignore' });
|
|
456
|
+
} catch {}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Start daemon in background, detached from terminal
|
|
460
|
+
const child = spawn('nohup', [daemonScript], {
|
|
461
|
+
detached: true,
|
|
462
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
|
463
|
+
cwd: home
|
|
464
|
+
});
|
|
465
|
+
child.unref();
|
|
466
|
+
|
|
467
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
468
|
+
|
|
469
|
+
if (fs.existsSync(pidFile)) {
|
|
470
|
+
log.ok('Daemon started');
|
|
471
|
+
log.info('Relay will auto-restart on crash');
|
|
472
|
+
log.info(`Logs: ${logDir}/daemon.log`);
|
|
473
|
+
log.info(`PID: ${fs.readFileSync(pidFile, 'utf8').trim()}`);
|
|
474
|
+
} else {
|
|
475
|
+
log.fail('Daemon failed to start');
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async function cmdUninstallDaemon() {
|
|
480
|
+
const home = os.homedir();
|
|
481
|
+
const pidFile = path.join(home, '.glider', 'daemon.pid');
|
|
482
|
+
|
|
483
|
+
if (!fs.existsSync(pidFile)) {
|
|
484
|
+
log.info('Daemon not running');
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
try {
|
|
489
|
+
const pid = fs.readFileSync(pidFile, 'utf8').trim();
|
|
490
|
+
execSync(`kill ${pid}`, { stdio: 'ignore' });
|
|
491
|
+
fs.unlinkSync(pidFile);
|
|
492
|
+
log.ok('Daemon stopped');
|
|
493
|
+
} catch (e) {
|
|
494
|
+
log.fail(`Failed to stop: ${e.message}`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
async function cmdConnect() {
|
|
499
|
+
// Bulletproof connect: relay + Chrome + trigger attach via HTTP
|
|
500
|
+
log.info('Connecting...');
|
|
501
|
+
|
|
502
|
+
// 1. Ensure relay is running
|
|
503
|
+
if (!await checkServer()) {
|
|
504
|
+
await cmdStart();
|
|
505
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// 2. Ensure Chrome is running
|
|
509
|
+
try {
|
|
510
|
+
execSync('pgrep -x "Google Chrome"', { stdio: 'ignore' });
|
|
511
|
+
} catch {
|
|
512
|
+
log.info('Starting Chrome...');
|
|
513
|
+
execSync('open -a "Google Chrome"');
|
|
514
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// 3. Wait for extension to connect to relay
|
|
518
|
+
for (let i = 0; i < 10; i++) {
|
|
519
|
+
if (await checkExtension()) break;
|
|
520
|
+
await new Promise(r => setTimeout(r, 500));
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (!await checkExtension()) {
|
|
524
|
+
log.fail('Extension not connected to relay');
|
|
525
|
+
log.info('Make sure Glider extension is installed in Chrome');
|
|
526
|
+
process.exit(1);
|
|
527
|
+
}
|
|
528
|
+
log.ok('Extension connected');
|
|
529
|
+
|
|
530
|
+
// Wait for extension to fully initialize
|
|
531
|
+
await new Promise(r => setTimeout(r, 500));
|
|
532
|
+
|
|
533
|
+
// 4. Check if already have targets
|
|
534
|
+
if (await checkTab()) {
|
|
535
|
+
log.ok('Already connected to tab(s)');
|
|
536
|
+
const targets = await getTargets();
|
|
537
|
+
targets.slice(0, 3).forEach(t => {
|
|
538
|
+
console.log(` ${CYAN}${t.targetInfo?.url || 'unknown'}${NC}`);
|
|
539
|
+
});
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// 5. Ensure we have a real tab (not chrome://)
|
|
544
|
+
try {
|
|
545
|
+
const tabUrl = execSync(`osascript -e 'tell application "Google Chrome" to return URL of active tab of front window'`).toString().trim();
|
|
546
|
+
if (tabUrl.startsWith('chrome://') || tabUrl.startsWith('chrome-extension://')) {
|
|
547
|
+
log.info('Creating new tab...');
|
|
548
|
+
execSync(`osascript -e 'tell application "Google Chrome" to make new tab at front window with properties {URL:"https://google.com"}'`);
|
|
549
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
550
|
+
}
|
|
551
|
+
} catch {
|
|
552
|
+
// No window, create one
|
|
553
|
+
log.info('Creating new window...');
|
|
554
|
+
execSync(`osascript -e 'tell application "Google Chrome" to make new window with properties {URL:"https://google.com"}'`);
|
|
555
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// 6. Trigger attach via HTTP endpoint (no pixel clicking needed!)
|
|
559
|
+
log.info('Attaching to tab...');
|
|
560
|
+
try {
|
|
561
|
+
const result = await fetch(`${SERVER_URL}/attach`, { method: 'POST' });
|
|
562
|
+
const data = await result.json();
|
|
563
|
+
|
|
564
|
+
if (data.attached > 0) {
|
|
565
|
+
log.ok('Connected!');
|
|
566
|
+
const targets = await getTargets();
|
|
567
|
+
targets.slice(0, 3).forEach(t => {
|
|
568
|
+
console.log(` ${CYAN}${t.targetInfo?.url || 'unknown'}${NC}`);
|
|
569
|
+
});
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
} catch (e) {
|
|
573
|
+
log.warn(`Attach failed: ${e.message}`);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// 7. Fallback: create fresh tab and retry
|
|
577
|
+
log.info('Creating fresh tab...');
|
|
578
|
+
execSync(`osascript -e 'tell application "Google Chrome" to make new tab at front window with properties {URL:"https://google.com"}'`);
|
|
579
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
580
|
+
|
|
581
|
+
try {
|
|
582
|
+
const result = await fetch(`${SERVER_URL}/attach`, { method: 'POST' });
|
|
583
|
+
const data = await result.json();
|
|
584
|
+
|
|
585
|
+
if (data.attached > 0) {
|
|
586
|
+
log.ok('Connected!');
|
|
587
|
+
const targets = await getTargets();
|
|
588
|
+
targets.slice(0, 3).forEach(t => {
|
|
589
|
+
console.log(` ${CYAN}${t.targetInfo?.url || 'unknown'}${NC}`);
|
|
590
|
+
});
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
} catch {}
|
|
594
|
+
|
|
595
|
+
// 8. Need manual click - open Chrome and show instructions
|
|
596
|
+
log.warn('Click the Glider extension icon in Chrome');
|
|
597
|
+
console.log(` ${B5}(on any real webpage, not chrome:// pages)${NC}`);
|
|
598
|
+
execSync(`osascript -e 'tell application "Google Chrome" to activate'`);
|
|
599
|
+
|
|
600
|
+
// Send macOS notification so user sees it even if not looking at terminal
|
|
601
|
+
notify('Glider', 'Click the extension icon in Chrome to connect', true);
|
|
602
|
+
|
|
603
|
+
// Wait for user to click
|
|
604
|
+
log.info('Waiting for connection...');
|
|
605
|
+
for (let i = 0; i < 30; i++) {
|
|
606
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
607
|
+
if (await checkTab()) {
|
|
608
|
+
log.ok('Connected!');
|
|
609
|
+
notify('Glider', 'Connected to browser');
|
|
610
|
+
const targets = await getTargets();
|
|
611
|
+
targets.slice(0, 3).forEach(t => {
|
|
612
|
+
console.log(` ${B5}${t.targetInfo?.url || 'unknown'}${NC}`);
|
|
613
|
+
});
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
log.fail('Timed out waiting for connection');
|
|
619
|
+
notify('Glider', 'Connection timed out - click extension icon', true);
|
|
620
|
+
log.info('Make sure you clicked the extension icon on a real webpage');
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
async function cmdTest() {
|
|
624
|
+
showBanner();
|
|
625
|
+
log.box('DIAGNOSTICS');
|
|
626
|
+
|
|
627
|
+
// Test 1: Server
|
|
628
|
+
const serverOk = await checkServer();
|
|
629
|
+
console.log(serverOk ? ` ${GREEN}✓${NC} ${B5}[1/4]${NC} Server` : ` ${RED}✗${NC} ${B5}[1/4]${NC} Server`);
|
|
630
|
+
if (!serverOk) {
|
|
631
|
+
log.info('Starting server...');
|
|
632
|
+
await cmdStart();
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Test 2: Extension
|
|
636
|
+
const extOk = await checkExtension();
|
|
637
|
+
console.log(extOk ? ` ${GREEN}✓${NC} ${B5}[2/4]${NC} Extension` : ` ${RED}✗${NC} ${B5}[2/4]${NC} Extension`);
|
|
638
|
+
|
|
639
|
+
// Test 3: Tab
|
|
640
|
+
const tabOk = await checkTab();
|
|
641
|
+
console.log(tabOk ? ` ${GREEN}✓${NC} ${B5}[3/4]${NC} Tab attached` : ` ${RED}✗${NC} ${B5}[3/4]${NC} No tabs`);
|
|
642
|
+
|
|
643
|
+
// Test 4: CDP command
|
|
644
|
+
if (tabOk) {
|
|
645
|
+
try {
|
|
646
|
+
const result = await httpPost('/cdp', {
|
|
647
|
+
method: 'Runtime.evaluate',
|
|
648
|
+
params: { expression: '1+1', returnByValue: true }
|
|
649
|
+
});
|
|
650
|
+
const cdpOk = result.result?.value === 2;
|
|
651
|
+
console.log(cdpOk ? `${GREEN}[4/4]${NC} CDP: OK` : `${RED}[4/4]${NC} CDP: FAIL`);
|
|
652
|
+
} catch {
|
|
653
|
+
console.log(`${RED}[4/4]${NC} CDP: FAIL`);
|
|
654
|
+
}
|
|
655
|
+
} else {
|
|
656
|
+
console.log(`${YELLOW}[4/4]${NC} CDP: SKIPPED (no tab)`);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
console.log('═══════════════════════════════════════');
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
async function cmdTabs() {
|
|
663
|
+
const targets = await getTargets();
|
|
664
|
+
if (targets.length === 0) {
|
|
665
|
+
log.warn('No tabs connected');
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
console.log(`${GREEN}${targets.length}${NC} tab(s) connected:\n`);
|
|
669
|
+
targets.forEach((t, i) => {
|
|
670
|
+
const url = t.targetInfo?.url || 'unknown';
|
|
671
|
+
const title = t.targetInfo?.title || '';
|
|
672
|
+
console.log(` ${CYAN}[${i + 1}]${NC} ${title}`);
|
|
673
|
+
console.log(` ${DIM}${url}${NC}`);
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
async function cmdWindow(args) {
|
|
678
|
+
const { WindowManager } = require(path.join(LIB_DIR, 'bwindow.js'));
|
|
679
|
+
const wm = new WindowManager();
|
|
680
|
+
|
|
681
|
+
try {
|
|
682
|
+
await wm.connect();
|
|
683
|
+
await wm.init();
|
|
684
|
+
|
|
685
|
+
const subcmd = args[0] || 'list';
|
|
686
|
+
|
|
687
|
+
switch (subcmd) {
|
|
688
|
+
case 'new':
|
|
689
|
+
case 'create': {
|
|
690
|
+
const url = args[1] || 'about:blank';
|
|
691
|
+
log.info(`Creating new window: ${url}`);
|
|
692
|
+
const result = await wm.createWindow(url);
|
|
693
|
+
log.ok(`Window created: ${result.targetId}`);
|
|
694
|
+
console.log(JSON.stringify(result, null, 2));
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
case 'tab': {
|
|
699
|
+
const url = args[1] || 'about:blank';
|
|
700
|
+
log.info(`Creating new tab: ${url}`);
|
|
701
|
+
const result = await wm.createTab(url);
|
|
702
|
+
log.ok(`Tab created: ${result.targetId}`);
|
|
703
|
+
console.log(JSON.stringify(result, null, 2));
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
case 'close': {
|
|
708
|
+
const targetId = args[1];
|
|
709
|
+
if (!targetId) {
|
|
710
|
+
log.fail('targetId required');
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
log.info(`Closing: ${targetId}`);
|
|
714
|
+
const result = await wm.closeTarget(targetId);
|
|
715
|
+
if (result.success) {
|
|
716
|
+
log.ok(`Closed: ${targetId}`);
|
|
717
|
+
} else {
|
|
718
|
+
log.fail(`Failed to close: ${result.error}`);
|
|
719
|
+
}
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
case 'closeall': {
|
|
724
|
+
log.info('Closing all Glider-created tabs...');
|
|
725
|
+
const results = await wm.closeAll();
|
|
726
|
+
const success = results.filter(r => r.success).length;
|
|
727
|
+
log.ok(`Closed ${success}/${results.length} tabs`);
|
|
728
|
+
break;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
case 'focus': {
|
|
732
|
+
const targetId = args[1];
|
|
733
|
+
if (!targetId) {
|
|
734
|
+
log.fail('targetId required');
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
const result = await wm.focusTarget(targetId);
|
|
738
|
+
if (result.success) {
|
|
739
|
+
log.ok(`Focused: ${targetId}`);
|
|
740
|
+
} else {
|
|
741
|
+
log.fail(`Failed to focus: ${result.error}`);
|
|
742
|
+
}
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
case 'list':
|
|
747
|
+
default: {
|
|
748
|
+
const targets = wm.list();
|
|
749
|
+
if (targets.length === 0) {
|
|
750
|
+
log.warn('No windows/tabs tracked');
|
|
751
|
+
} else {
|
|
752
|
+
console.log(`${GREEN}${targets.length}${NC} target(s):\n`);
|
|
753
|
+
targets.forEach((t, i) => {
|
|
754
|
+
const marker = t.createdByGlider ? `${GREEN}●${NC}` : `${DIM}○${NC}`;
|
|
755
|
+
console.log(` ${marker} ${CYAN}${t.targetId.substring(0, 16)}...${NC}`);
|
|
756
|
+
console.log(` ${DIM}${t.url || 'unknown'}${NC}`);
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
break;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
} catch (err) {
|
|
763
|
+
log.fail(err.message);
|
|
764
|
+
} finally {
|
|
765
|
+
wm.close();
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
async function cmdDomains() {
|
|
770
|
+
const domainKeys = Object.keys(DOMAINS);
|
|
771
|
+
if (domainKeys.length === 0) {
|
|
772
|
+
log.warn('No domains configured');
|
|
773
|
+
log.info('Add domains to ~/.cursor/glider/domains.json or ~/.glider/domains.json');
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
console.log(`${GREEN}${domainKeys.length}${NC} domain(s) configured:\n`);
|
|
777
|
+
for (const key of domainKeys) {
|
|
778
|
+
const d = DOMAINS[key];
|
|
779
|
+
const type = d.script ? 'script' : 'url';
|
|
780
|
+
const target = d.script || d.url || '';
|
|
781
|
+
console.log(` ${CYAN}${key}${NC} ${DIM}(${type})${NC}`);
|
|
782
|
+
if (d.description) console.log(` ${d.description}`);
|
|
783
|
+
console.log(` ${DIM}${target}${NC}`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
async function cmdOpen(url) {
|
|
788
|
+
if (!url) {
|
|
789
|
+
log.fail('Usage: glider open <url>');
|
|
790
|
+
process.exit(1);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Open URL in default browser (not in connected tab)
|
|
794
|
+
const { exec } = require('child_process');
|
|
795
|
+
const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
796
|
+
exec(`${cmd} "${url}"`, (err) => {
|
|
797
|
+
if (err) {
|
|
798
|
+
log.fail(`Failed to open: ${err.message}`);
|
|
799
|
+
process.exit(1);
|
|
800
|
+
}
|
|
801
|
+
log.ok(`Opened: ${url}`);
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
async function cmdHtml(selector) {
|
|
806
|
+
try {
|
|
807
|
+
const expression = selector
|
|
808
|
+
? `document.querySelector('${selector.replace(/'/g, "\\'")}')?.outerHTML || 'Element not found'`
|
|
809
|
+
: 'document.documentElement.outerHTML';
|
|
810
|
+
|
|
811
|
+
const result = await httpPost('/cdp', {
|
|
812
|
+
method: 'Runtime.evaluate',
|
|
813
|
+
params: { expression, returnByValue: true }
|
|
814
|
+
});
|
|
815
|
+
console.log(result.result?.value || '');
|
|
816
|
+
} catch (e) {
|
|
817
|
+
log.fail(`HTML extraction failed: ${e.message}`);
|
|
818
|
+
process.exit(1);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
async function cmdTitle() {
|
|
823
|
+
try {
|
|
824
|
+
const result = await httpPost('/cdp', {
|
|
825
|
+
method: 'Runtime.evaluate',
|
|
826
|
+
params: { expression: 'document.title', returnByValue: true }
|
|
827
|
+
});
|
|
828
|
+
console.log(result.result?.value || '');
|
|
829
|
+
} catch (e) {
|
|
830
|
+
log.fail(`Title extraction failed: ${e.message}`);
|
|
831
|
+
process.exit(1);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
async function cmdUrl() {
|
|
836
|
+
try {
|
|
837
|
+
const result = await httpPost('/cdp', {
|
|
838
|
+
method: 'Runtime.evaluate',
|
|
839
|
+
params: { expression: 'window.location.href', returnByValue: true }
|
|
840
|
+
});
|
|
841
|
+
console.log(result.result?.value || '');
|
|
842
|
+
} catch (e) {
|
|
843
|
+
log.fail(`URL extraction failed: ${e.message}`);
|
|
844
|
+
process.exit(1);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Fetch URL using browser session (authenticated)
|
|
849
|
+
async function cmdFetch(url, opts = []) {
|
|
850
|
+
if (!url) {
|
|
851
|
+
log.fail('Usage: glider fetch <url> [--output file]');
|
|
852
|
+
process.exit(1);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
log.info(`Fetching: ${url}`);
|
|
856
|
+
|
|
857
|
+
let outputFile = null;
|
|
858
|
+
for (let i = 0; i < opts.length; i++) {
|
|
859
|
+
if (opts[i] === '--output' || opts[i] === '-o') {
|
|
860
|
+
outputFile = opts[++i];
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
try {
|
|
865
|
+
const result = await httpPost('/cdp', {
|
|
866
|
+
method: 'Runtime.evaluate',
|
|
867
|
+
params: {
|
|
868
|
+
expression: `
|
|
869
|
+
(async () => {
|
|
870
|
+
const resp = await fetch(${JSON.stringify(url)});
|
|
871
|
+
const text = await resp.text();
|
|
872
|
+
try { return JSON.parse(text); } catch { return text; }
|
|
873
|
+
})()
|
|
874
|
+
`,
|
|
875
|
+
awaitPromise: true,
|
|
876
|
+
returnByValue: true
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
const data = result?.result?.value;
|
|
881
|
+
const output = typeof data === 'object' ? JSON.stringify(data, null, 2) : data;
|
|
882
|
+
|
|
883
|
+
if (outputFile) {
|
|
884
|
+
fs.writeFileSync(outputFile, output);
|
|
885
|
+
log.ok(`Saved to ${outputFile}`);
|
|
886
|
+
} else {
|
|
887
|
+
console.log(output);
|
|
888
|
+
}
|
|
889
|
+
} catch (e) {
|
|
890
|
+
log.fail(`Fetch failed: ${e.message}`);
|
|
891
|
+
process.exit(1);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// Spawn multiple tabs
|
|
896
|
+
async function cmdSpawn(urls) {
|
|
897
|
+
if (!urls || urls.length === 0) {
|
|
898
|
+
log.fail('Usage: glider spawn <url1> <url2> ...');
|
|
899
|
+
process.exit(1);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Handle file input
|
|
903
|
+
if (urls[0] === '-f' && urls[1]) {
|
|
904
|
+
const content = fs.readFileSync(urls[1], 'utf8');
|
|
905
|
+
urls = content.split('\n').filter(u => u.trim());
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
log.info(`Spawning ${urls.length} tab(s)...`);
|
|
909
|
+
|
|
910
|
+
const results = [];
|
|
911
|
+
for (const url of urls) {
|
|
912
|
+
try {
|
|
913
|
+
const result = await httpPost('/cdp', {
|
|
914
|
+
method: 'Target.createTarget',
|
|
915
|
+
params: { url }
|
|
916
|
+
});
|
|
917
|
+
results.push({ url, targetId: result?.targetId });
|
|
918
|
+
log.ok(`Spawned: ${url}`);
|
|
919
|
+
} catch (e) {
|
|
920
|
+
log.warn(`Failed: ${url} - ${e.message}`);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
console.log(JSON.stringify(results, null, 2));
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// Extract from multiple tabs
|
|
928
|
+
async function cmdExtract(opts = []) {
|
|
929
|
+
let js = 'document.body.innerText';
|
|
930
|
+
let selector = null;
|
|
931
|
+
let limit = 10000;
|
|
932
|
+
let asJson = false;
|
|
933
|
+
|
|
934
|
+
for (let i = 0; i < opts.length; i++) {
|
|
935
|
+
if (opts[i] === '--js') js = opts[++i];
|
|
936
|
+
else if (opts[i] === '--selector' || opts[i] === '-s') selector = opts[++i];
|
|
937
|
+
else if (opts[i] === '--limit' || opts[i] === '-l') limit = parseInt(opts[++i], 10);
|
|
938
|
+
else if (opts[i] === '--json') asJson = true;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
if (selector) {
|
|
942
|
+
js = `document.querySelector(${JSON.stringify(selector)})?.innerText || ''`;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
log.info('Extracting from connected tabs...');
|
|
946
|
+
|
|
947
|
+
try {
|
|
948
|
+
const targets = await getTargets();
|
|
949
|
+
if (targets.length === 0) {
|
|
950
|
+
log.warn('No tabs connected');
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
const results = [];
|
|
955
|
+
for (const target of targets) {
|
|
956
|
+
const url = target.targetInfo?.url || 'unknown';
|
|
957
|
+
try {
|
|
958
|
+
const result = await httpPost('/cdp', {
|
|
959
|
+
method: 'Runtime.evaluate',
|
|
960
|
+
params: {
|
|
961
|
+
expression: js,
|
|
962
|
+
returnByValue: true
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
const text = String(result?.result?.value || '').slice(0, limit);
|
|
966
|
+
results.push({ url, text });
|
|
967
|
+
if (!asJson) {
|
|
968
|
+
console.log(`\n--- ${url} ---`);
|
|
969
|
+
console.log(text);
|
|
970
|
+
}
|
|
971
|
+
} catch (e) {
|
|
972
|
+
results.push({ url, error: e.message });
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
if (asJson) {
|
|
977
|
+
console.log(JSON.stringify(results, null, 2));
|
|
978
|
+
}
|
|
979
|
+
} catch (e) {
|
|
980
|
+
log.fail(`Extract failed: ${e.message}`);
|
|
981
|
+
process.exit(1);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// Explore site (clicks around, captures network)
|
|
986
|
+
async function cmdExplore(url, opts = []) {
|
|
987
|
+
if (!url) {
|
|
988
|
+
log.fail('Usage: glider explore <url> [--depth N] [--output dir] [--har file]');
|
|
989
|
+
process.exit(1);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
let depth = 2;
|
|
993
|
+
let outputDir = '/tmp/glider-explore';
|
|
994
|
+
let harFile = null;
|
|
995
|
+
|
|
996
|
+
for (let i = 0; i < opts.length; i++) {
|
|
997
|
+
if (opts[i] === '--depth' || opts[i] === '-d') depth = parseInt(opts[++i], 10);
|
|
998
|
+
else if (opts[i] === '--output' || opts[i] === '-o') outputDir = opts[++i];
|
|
999
|
+
else if (opts[i] === '--har') harFile = opts[++i];
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
log.info(`Exploring: ${url} (depth: ${depth})`);
|
|
1003
|
+
|
|
1004
|
+
// Use the bexplore.js library
|
|
1005
|
+
const bexplorePath = path.join(LIB_DIR, 'bexplore.js');
|
|
1006
|
+
if (fs.existsSync(bexplorePath)) {
|
|
1007
|
+
const { spawn } = require('child_process');
|
|
1008
|
+
const spawnArgs = [bexplorePath, url, '--depth', String(depth), '--output', outputDir];
|
|
1009
|
+
if (harFile) spawnArgs.push('--har', harFile);
|
|
1010
|
+
|
|
1011
|
+
const child = spawn('node', spawnArgs, {
|
|
1012
|
+
stdio: 'inherit'
|
|
1013
|
+
});
|
|
1014
|
+
await new Promise((resolve, reject) => {
|
|
1015
|
+
child.on('close', code => code === 0 ? resolve() : reject(new Error(`Exit code: ${code}`)));
|
|
1016
|
+
});
|
|
1017
|
+
} else {
|
|
1018
|
+
// Fallback: simple exploration
|
|
1019
|
+
await cmdGoto(url);
|
|
1020
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
1021
|
+
|
|
1022
|
+
// Get all links
|
|
1023
|
+
const result = await httpPost('/cdp', {
|
|
1024
|
+
method: 'Runtime.evaluate',
|
|
1025
|
+
params: {
|
|
1026
|
+
expression: `Array.from(document.querySelectorAll('a[href]')).map(a => a.href).filter(h => h.startsWith('http'))`,
|
|
1027
|
+
returnByValue: true
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
const links = result?.result?.value || [];
|
|
1032
|
+
log.ok(`Found ${links.length} links`);
|
|
1033
|
+
|
|
1034
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
1035
|
+
fs.writeFileSync(path.join(outputDir, 'links.json'), JSON.stringify(links, null, 2));
|
|
1036
|
+
|
|
1037
|
+
// Screenshot
|
|
1038
|
+
await cmdScreenshot(path.join(outputDir, 'screenshot.png'));
|
|
1039
|
+
|
|
1040
|
+
log.ok(`Output saved to ${outputDir}`);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
404
1044
|
// YAML Task Runner
|
|
405
1045
|
async function cmdRun(taskFile) {
|
|
406
1046
|
if (!taskFile || !fs.existsSync(taskFile)) {
|
|
@@ -658,30 +1298,63 @@ async function cmdLoop(taskFileOrPrompt, options = {}) {
|
|
|
658
1298
|
function showHelp() {
|
|
659
1299
|
showBanner();
|
|
660
1300
|
console.log(`
|
|
661
|
-
${
|
|
1301
|
+
${B5}USAGE${NC}
|
|
662
1302
|
glider <command> [args]
|
|
663
1303
|
|
|
664
|
-
${
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
1304
|
+
${B5}SETUP${NC}
|
|
1305
|
+
${BW}install${NC} Install daemon ${DIM}(runs at login, auto-restarts)${NC}
|
|
1306
|
+
${BW}uninstall${NC} Remove daemon
|
|
1307
|
+
${BW}connect${NC} Connect to browser ${DIM}(run once per Chrome session)${NC}
|
|
1308
|
+
|
|
1309
|
+
${B5}STATUS${NC}
|
|
1310
|
+
${BW}status${NC} Check server, extension, tabs
|
|
1311
|
+
${BW}test${NC} Run diagnostics
|
|
1312
|
+
|
|
1313
|
+
${B5}NAVIGATION${NC}
|
|
1314
|
+
${BW}goto${NC} <url> Navigate to URL
|
|
1315
|
+
${BW}eval${NC} <js> Execute JavaScript
|
|
1316
|
+
${BW}click${NC} <selector> Click element
|
|
1317
|
+
${BW}type${NC} <sel> <text> Type into input
|
|
1318
|
+
${BW}screenshot${NC} [path] Take screenshot
|
|
1319
|
+
|
|
1320
|
+
${B5}PAGE INFO${NC}
|
|
1321
|
+
${BW}text${NC} Get page text
|
|
1322
|
+
${BW}html${NC} [selector] Get HTML
|
|
1323
|
+
${BW}title${NC} Get page title
|
|
1324
|
+
${BW}url${NC} Get current URL
|
|
1325
|
+
${BW}tabs${NC} List connected tabs
|
|
668
1326
|
|
|
669
|
-
${
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
1327
|
+
${B5}MULTI-WINDOW${NC}
|
|
1328
|
+
${BW}window new${NC} [url] Create new browser window ${DIM}(closeable)${NC}
|
|
1329
|
+
${BW}window tab${NC} [url] Create tab in current window
|
|
1330
|
+
${BW}window close${NC} <id> Close specific tab/window
|
|
1331
|
+
${BW}window closeall${NC} Close all Glider-created tabs
|
|
1332
|
+
${BW}window focus${NC} <id> Bring tab to foreground
|
|
1333
|
+
${BW}window list${NC} List all windows/tabs
|
|
676
1334
|
|
|
677
|
-
${
|
|
678
|
-
|
|
679
|
-
|
|
1335
|
+
${B5}MULTI-TAB${NC}
|
|
1336
|
+
${BW}fetch${NC} <url> Fetch URL with browser session ${DIM}(auth)${NC}
|
|
1337
|
+
${BW}spawn${NC} <urls...> Open multiple tabs
|
|
1338
|
+
${BW}extract${NC} [opts] Extract from all tabs
|
|
1339
|
+
${BW}explore${NC} <url> Crawl site, capture network
|
|
680
1340
|
|
|
681
|
-
${
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
1341
|
+
${B5}AUTOMATION${NC}
|
|
1342
|
+
${BW}run${NC} <task.yaml> Execute YAML task file
|
|
1343
|
+
${BW}loop${NC} <task> [opts] Autonomous loop ${DIM}(run until complete)${NC}
|
|
1344
|
+
${BW}ralph${NC} <task> ${DIM}Alias for loop${NC}
|
|
1345
|
+
|
|
1346
|
+
${B5}LOOP OPTIONS${NC}
|
|
1347
|
+
-n, --max-iterations N Max iterations ${DIM}(default: 10)${NC}
|
|
1348
|
+
-t, --timeout N Timeout in seconds ${DIM}(default: 3600)${NC}
|
|
1349
|
+
-m, --marker STRING Completion marker ${DIM}(default: LOOP_COMPLETE)${NC}
|
|
1350
|
+
|
|
1351
|
+
${B5}EXAMPLES${NC}
|
|
1352
|
+
${DIM}$${NC} glider install ${DIM}# one-time setup${NC}
|
|
1353
|
+
${DIM}$${NC} glider connect ${DIM}# connect to Chrome${NC}
|
|
1354
|
+
${DIM}$${NC} glider goto "https://x.com" ${DIM}# navigate${NC}
|
|
1355
|
+
${DIM}$${NC} glider eval "document.title"${DIM}# run JS${NC}
|
|
1356
|
+
${DIM}$${NC} glider run scrape.yaml ${DIM}# run task${NC}
|
|
1357
|
+
${DIM}$${NC} glider loop task.yaml -n 50 ${DIM}# autonomous loop${NC}
|
|
685
1358
|
|
|
686
1359
|
${YELLOW}TASK FILE FORMAT:${NC}
|
|
687
1360
|
name: "Task name"
|
|
@@ -700,6 +1373,7 @@ ${YELLOW}EXAMPLES:${NC}
|
|
|
700
1373
|
glider start
|
|
701
1374
|
glider goto "https://google.com"
|
|
702
1375
|
glider eval "document.title"
|
|
1376
|
+
glider html "div.main"
|
|
703
1377
|
glider run mytask.yaml
|
|
704
1378
|
glider loop mytask.yaml -n 20 -t 600
|
|
705
1379
|
|
|
@@ -712,7 +1386,6 @@ ${YELLOW}RALPH WIGGUM PATTERN:${NC}
|
|
|
712
1386
|
|
|
713
1387
|
${YELLOW}REQUIREMENTS:${NC}
|
|
714
1388
|
- Node.js 18+
|
|
715
|
-
- bserve relay server (~/scripts/bserve)
|
|
716
1389
|
- Glider Chrome extension connected
|
|
717
1390
|
|
|
718
1391
|
${YELLOW}DOMAIN EXTENSIONS:${NC}
|
|
@@ -721,14 +1394,14 @@ ${YELLOW}DOMAIN EXTENSIONS:${NC}
|
|
|
721
1394
|
"mysite": { "url": "https://mysite.com/dashboard" },
|
|
722
1395
|
"mytool": { "script": "~/.cursor/tools/scripts/mytool.sh" }
|
|
723
1396
|
}
|
|
724
|
-
Then: glider mysite
|
|
725
|
-
glider mytool
|
|
1397
|
+
Then: glider mysite -> navigates to that URL
|
|
1398
|
+
glider mytool -> runs that script
|
|
726
1399
|
`);
|
|
727
|
-
|
|
728
|
-
// Show loaded domains if any
|
|
1400
|
+
|
|
1401
|
+
// Show loaded domains if any (from local config)
|
|
729
1402
|
const domainKeys = Object.keys(DOMAINS);
|
|
730
1403
|
if (domainKeys.length > 0) {
|
|
731
|
-
console.log(`${YELLOW}LOADED DOMAINS:${NC} (from config)`);
|
|
1404
|
+
console.log(`${YELLOW}LOADED DOMAINS:${NC} (from local config)`);
|
|
732
1405
|
for (const key of domainKeys) {
|
|
733
1406
|
const d = DOMAINS[key];
|
|
734
1407
|
const desc = d.description || d.url || d.script || '';
|
|
@@ -757,6 +1430,11 @@ async function main() {
|
|
|
757
1430
|
}
|
|
758
1431
|
|
|
759
1432
|
switch (cmd) {
|
|
1433
|
+
case 'help':
|
|
1434
|
+
case '--help':
|
|
1435
|
+
case '-h':
|
|
1436
|
+
showHelp();
|
|
1437
|
+
break;
|
|
760
1438
|
case 'status':
|
|
761
1439
|
await cmdStatus();
|
|
762
1440
|
break;
|
|
@@ -766,10 +1444,38 @@ async function main() {
|
|
|
766
1444
|
case 'stop':
|
|
767
1445
|
await cmdStop();
|
|
768
1446
|
break;
|
|
1447
|
+
case 'restart':
|
|
1448
|
+
await cmdRestart();
|
|
1449
|
+
break;
|
|
1450
|
+
case 'install':
|
|
1451
|
+
await cmdInstallDaemon();
|
|
1452
|
+
break;
|
|
1453
|
+
case 'uninstall':
|
|
1454
|
+
await cmdUninstallDaemon();
|
|
1455
|
+
break;
|
|
1456
|
+
case 'connect':
|
|
1457
|
+
await cmdConnect();
|
|
1458
|
+
break;
|
|
1459
|
+
case 'test':
|
|
1460
|
+
await cmdTest();
|
|
1461
|
+
break;
|
|
1462
|
+
case 'tabs':
|
|
1463
|
+
await cmdTabs();
|
|
1464
|
+
break;
|
|
1465
|
+
case 'window':
|
|
1466
|
+
case 'win':
|
|
1467
|
+
await cmdWindow(args.slice(1));
|
|
1468
|
+
break;
|
|
1469
|
+
case 'domains':
|
|
1470
|
+
await cmdDomains();
|
|
1471
|
+
break;
|
|
769
1472
|
case 'goto':
|
|
770
1473
|
case 'navigate':
|
|
771
1474
|
await cmdGoto(args[1]);
|
|
772
1475
|
break;
|
|
1476
|
+
case 'open':
|
|
1477
|
+
await cmdOpen(args[1]);
|
|
1478
|
+
break;
|
|
773
1479
|
case 'eval':
|
|
774
1480
|
case 'js':
|
|
775
1481
|
await cmdEval(args.slice(1).join(' '));
|
|
@@ -786,10 +1492,32 @@ async function main() {
|
|
|
786
1492
|
case 'text':
|
|
787
1493
|
await cmdText();
|
|
788
1494
|
break;
|
|
1495
|
+
case 'html':
|
|
1496
|
+
await cmdHtml(args[1]);
|
|
1497
|
+
break;
|
|
1498
|
+
case 'title':
|
|
1499
|
+
await cmdTitle();
|
|
1500
|
+
break;
|
|
1501
|
+
case 'url':
|
|
1502
|
+
await cmdUrl();
|
|
1503
|
+
break;
|
|
789
1504
|
case 'run':
|
|
790
1505
|
await cmdRun(args[1]);
|
|
791
1506
|
break;
|
|
1507
|
+
case 'fetch':
|
|
1508
|
+
await cmdFetch(args[1], args.slice(2));
|
|
1509
|
+
break;
|
|
1510
|
+
case 'spawn':
|
|
1511
|
+
await cmdSpawn(args.slice(1));
|
|
1512
|
+
break;
|
|
1513
|
+
case 'extract':
|
|
1514
|
+
await cmdExtract(args.slice(1));
|
|
1515
|
+
break;
|
|
1516
|
+
case 'explore':
|
|
1517
|
+
await cmdExplore(args[1], args.slice(2));
|
|
1518
|
+
break;
|
|
792
1519
|
case 'loop':
|
|
1520
|
+
case 'ralph': // alias for loop - Ralph Wiggum pattern
|
|
793
1521
|
// Parse loop options
|
|
794
1522
|
const loopOpts = {
|
|
795
1523
|
maxIterations: 10,
|