glidercli 0.1.4 β 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/README.md +12 -7
- package/bin/glider.js +243 -3
- package/lib/bcdp.js +482 -0
- package/lib/beval.js +78 -0
- package/lib/bserve.js +311 -0
- package/package.json +8 -2
- package/.git-personal-enforced +0 -4
- package/.github/hooks/post-checkout +0 -24
- 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/assets/icons/.gitkeep +0 -0
- package/repo.config.json +0 -31
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<img src="
|
|
4
|
-
<img src="
|
|
3
|
+
<img src="assets/icons/glider-blue-squircle.webp" alt="glider" width="80" height="80" />
|
|
4
|
+
<img src="assets/icons/claude.webp" alt="claude" width="80" height="80" />
|
|
5
|
+
<img src="assets/icons/ralph-wiggum.webp" alt="ralph" width="80" height="80" />
|
|
5
6
|
|
|
6
7
|
<h1 align="center">glidercli</h1>
|
|
7
8
|
<p align="center"><i><b>Browser automation CLI with autonomous loop execution.</b></i></p>
|
|
@@ -9,8 +10,6 @@
|
|
|
9
10
|
[![Github][github]][github-url]
|
|
10
11
|
[![npm][npm]][npm-url]
|
|
11
12
|
|
|
12
|
-
<img src="https://res.cloudinary.com/ddyc1es5v/image/upload/v1768050244/gh-repos/glidercli/social-preview.png" />
|
|
13
|
-
|
|
14
13
|
</div>
|
|
15
14
|
|
|
16
15
|
<br/>
|
|
@@ -47,9 +46,12 @@ npm i -g glidercli
|
|
|
47
46
|
|
|
48
47
|
### Requirements
|
|
49
48
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
-
|
|
49
|
+
1. **Node 18+**
|
|
50
|
+
|
|
51
|
+
2. **Glider Chrome Extension** - [Source](https://github.com/vdutts7/glider) *(Chrome Web Store pending approval)*
|
|
52
|
+
- For now: clone repo, load unpacked in `chrome://extensions`
|
|
53
|
+
|
|
54
|
+
3. **bserve relay server** - included with extension, auto-starts
|
|
53
55
|
|
|
54
56
|
## πUsage
|
|
55
57
|
|
|
@@ -101,6 +103,7 @@ steps:
|
|
|
101
103
|
|
|
102
104
|
## π§Tools Used
|
|
103
105
|
|
|
106
|
+
[![Claude][claude-badge]][claude-url]
|
|
104
107
|
[![Node.js][nodejs-badge]][nodejs-url]
|
|
105
108
|
[![Chrome DevTools Protocol][cdp-badge]][cdp-url]
|
|
106
109
|
|
|
@@ -114,6 +117,8 @@ steps:
|
|
|
114
117
|
[github-url]: https://github.com/vdutts7/glidercli
|
|
115
118
|
[npm]: https://img.shields.io/badge/npm-glidercli-CB3837?style=for-the-badge&logo=npm
|
|
116
119
|
[npm-url]: https://www.npmjs.com/package/glidercli
|
|
120
|
+
[claude-badge]: https://img.shields.io/badge/Claude-D97757?style=for-the-badge&logo=anthropic&logoColor=white
|
|
121
|
+
[claude-url]: https://claude.ai
|
|
117
122
|
[nodejs-badge]: https://img.shields.io/badge/Node.js-339933?style=for-the-badge&logo=nodedotjs&logoColor=white
|
|
118
123
|
[nodejs-url]: https://nodejs.org
|
|
119
124
|
[cdp-badge]: https://img.shields.io/badge/Chrome_DevTools_Protocol-4285F4?style=for-the-badge&logo=googlechrome&logoColor=white
|
package/bin/glider.js
CHANGED
|
@@ -32,10 +32,25 @@ const YAML = require('yaml');
|
|
|
32
32
|
// Config
|
|
33
33
|
const PORT = process.env.GLIDER_PORT || 19988;
|
|
34
34
|
const SERVER_URL = `http://127.0.0.1:${PORT}`;
|
|
35
|
-
const
|
|
35
|
+
const LIB_DIR = path.join(__dirname, '..', 'lib');
|
|
36
36
|
const STATE_FILE = '/tmp/glider-state.json';
|
|
37
37
|
const LOG_FILE = '/tmp/glider.log';
|
|
38
38
|
|
|
39
|
+
// Domain extensions - load from ~/.cursor/glider/domains.json or ~/.glider/domains.json
|
|
40
|
+
const DOMAIN_CONFIG_PATHS = [
|
|
41
|
+
path.join(os.homedir(), '.cursor', 'glider', 'domains.json'),
|
|
42
|
+
path.join(os.homedir(), '.glider', 'domains.json'),
|
|
43
|
+
];
|
|
44
|
+
let DOMAINS = {};
|
|
45
|
+
for (const cfgPath of DOMAIN_CONFIG_PATHS) {
|
|
46
|
+
if (fs.existsSync(cfgPath)) {
|
|
47
|
+
try {
|
|
48
|
+
DOMAINS = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
49
|
+
break;
|
|
50
|
+
} catch (e) { /* ignore parse errors */ }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
39
54
|
// Colors
|
|
40
55
|
const RED = '\x1b[31m';
|
|
41
56
|
const GREEN = '\x1b[32m';
|
|
@@ -198,7 +213,7 @@ async function cmdStart() {
|
|
|
198
213
|
}
|
|
199
214
|
|
|
200
215
|
log.info('Starting glider server...');
|
|
201
|
-
const bserve = path.join(
|
|
216
|
+
const bserve = path.join(LIB_DIR, 'bserve.js');
|
|
202
217
|
|
|
203
218
|
if (!fs.existsSync(bserve)) {
|
|
204
219
|
log.fail(`bserve not found at ${bserve}`);
|
|
@@ -386,6 +401,151 @@ async function cmdText() {
|
|
|
386
401
|
}
|
|
387
402
|
}
|
|
388
403
|
|
|
404
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
405
|
+
// NEW COMMANDS: restart, test, tabs, domains, open, html, title, url
|
|
406
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
407
|
+
|
|
408
|
+
async function cmdRestart() {
|
|
409
|
+
await cmdStop();
|
|
410
|
+
await new Promise(r => setTimeout(r, 500));
|
|
411
|
+
await cmdStart();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function cmdTest() {
|
|
415
|
+
showBanner();
|
|
416
|
+
console.log('βββββββββββββββββββββββββββββββββββββββ');
|
|
417
|
+
console.log(' GLIDER TEST');
|
|
418
|
+
console.log('βββββββββββββββββββββββββββββββββββββββ');
|
|
419
|
+
|
|
420
|
+
// Test 1: Server
|
|
421
|
+
const serverOk = await checkServer();
|
|
422
|
+
console.log(serverOk ? `${GREEN}[1/4]${NC} Server: OK` : `${RED}[1/4]${NC} Server: FAIL`);
|
|
423
|
+
if (!serverOk) {
|
|
424
|
+
log.info('Starting server...');
|
|
425
|
+
await cmdStart();
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Test 2: Extension
|
|
429
|
+
const extOk = await checkExtension();
|
|
430
|
+
console.log(extOk ? `${GREEN}[2/4]${NC} Extension: OK` : `${RED}[2/4]${NC} Extension: NOT CONNECTED`);
|
|
431
|
+
|
|
432
|
+
// Test 3: Tab
|
|
433
|
+
const tabOk = await checkTab();
|
|
434
|
+
console.log(tabOk ? `${GREEN}[3/4]${NC} Tab: OK` : `${RED}[3/4]${NC} Tab: NO TABS`);
|
|
435
|
+
|
|
436
|
+
// Test 4: CDP command
|
|
437
|
+
if (tabOk) {
|
|
438
|
+
try {
|
|
439
|
+
const result = await httpPost('/cdp', {
|
|
440
|
+
method: 'Runtime.evaluate',
|
|
441
|
+
params: { expression: '1+1', returnByValue: true }
|
|
442
|
+
});
|
|
443
|
+
const cdpOk = result.result?.value === 2;
|
|
444
|
+
console.log(cdpOk ? `${GREEN}[4/4]${NC} CDP: OK` : `${RED}[4/4]${NC} CDP: FAIL`);
|
|
445
|
+
} catch {
|
|
446
|
+
console.log(`${RED}[4/4]${NC} CDP: FAIL`);
|
|
447
|
+
}
|
|
448
|
+
} else {
|
|
449
|
+
console.log(`${YELLOW}[4/4]${NC} CDP: SKIPPED (no tab)`);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
console.log('βββββββββββββββββββββββββββββββββββββββ');
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async function cmdTabs() {
|
|
456
|
+
const targets = await getTargets();
|
|
457
|
+
if (targets.length === 0) {
|
|
458
|
+
log.warn('No tabs connected');
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
console.log(`${GREEN}${targets.length}${NC} tab(s) connected:\n`);
|
|
462
|
+
targets.forEach((t, i) => {
|
|
463
|
+
const url = t.targetInfo?.url || 'unknown';
|
|
464
|
+
const title = t.targetInfo?.title || '';
|
|
465
|
+
console.log(` ${CYAN}[${i + 1}]${NC} ${title}`);
|
|
466
|
+
console.log(` ${DIM}${url}${NC}`);
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
async function cmdDomains() {
|
|
471
|
+
const domainKeys = Object.keys(DOMAINS);
|
|
472
|
+
if (domainKeys.length === 0) {
|
|
473
|
+
log.warn('No domains configured');
|
|
474
|
+
log.info('Add domains to ~/.cursor/glider/domains.json or ~/.glider/domains.json');
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
console.log(`${GREEN}${domainKeys.length}${NC} domain(s) configured:\n`);
|
|
478
|
+
for (const key of domainKeys) {
|
|
479
|
+
const d = DOMAINS[key];
|
|
480
|
+
const type = d.script ? 'script' : 'url';
|
|
481
|
+
const target = d.script || d.url || '';
|
|
482
|
+
console.log(` ${CYAN}${key}${NC} ${DIM}(${type})${NC}`);
|
|
483
|
+
if (d.description) console.log(` ${d.description}`);
|
|
484
|
+
console.log(` ${DIM}${target}${NC}`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async function cmdOpen(url) {
|
|
489
|
+
if (!url) {
|
|
490
|
+
log.fail('Usage: glider open <url>');
|
|
491
|
+
process.exit(1);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Open URL in default browser (not in connected tab)
|
|
495
|
+
const { exec } = require('child_process');
|
|
496
|
+
const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
497
|
+
exec(`${cmd} "${url}"`, (err) => {
|
|
498
|
+
if (err) {
|
|
499
|
+
log.fail(`Failed to open: ${err.message}`);
|
|
500
|
+
process.exit(1);
|
|
501
|
+
}
|
|
502
|
+
log.ok(`Opened: ${url}`);
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
async function cmdHtml(selector) {
|
|
507
|
+
try {
|
|
508
|
+
const expression = selector
|
|
509
|
+
? `document.querySelector('${selector.replace(/'/g, "\\'")}')?.outerHTML || 'Element not found'`
|
|
510
|
+
: 'document.documentElement.outerHTML';
|
|
511
|
+
|
|
512
|
+
const result = await httpPost('/cdp', {
|
|
513
|
+
method: 'Runtime.evaluate',
|
|
514
|
+
params: { expression, returnByValue: true }
|
|
515
|
+
});
|
|
516
|
+
console.log(result.result?.value || '');
|
|
517
|
+
} catch (e) {
|
|
518
|
+
log.fail(`HTML extraction failed: ${e.message}`);
|
|
519
|
+
process.exit(1);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async function cmdTitle() {
|
|
524
|
+
try {
|
|
525
|
+
const result = await httpPost('/cdp', {
|
|
526
|
+
method: 'Runtime.evaluate',
|
|
527
|
+
params: { expression: 'document.title', returnByValue: true }
|
|
528
|
+
});
|
|
529
|
+
console.log(result.result?.value || '');
|
|
530
|
+
} catch (e) {
|
|
531
|
+
log.fail(`Title extraction failed: ${e.message}`);
|
|
532
|
+
process.exit(1);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
async function cmdUrl() {
|
|
537
|
+
try {
|
|
538
|
+
const result = await httpPost('/cdp', {
|
|
539
|
+
method: 'Runtime.evaluate',
|
|
540
|
+
params: { expression: 'window.location.href', returnByValue: true }
|
|
541
|
+
});
|
|
542
|
+
console.log(result.result?.value || '');
|
|
543
|
+
} catch (e) {
|
|
544
|
+
log.fail(`URL extraction failed: ${e.message}`);
|
|
545
|
+
process.exit(1);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
389
549
|
// YAML Task Runner
|
|
390
550
|
async function cmdRun(taskFile) {
|
|
391
551
|
if (!taskFile || !fs.existsSync(taskFile)) {
|
|
@@ -650,19 +810,31 @@ ${YELLOW}SERVER:${NC}
|
|
|
650
810
|
status Check server, extension, tabs
|
|
651
811
|
start Start relay server
|
|
652
812
|
stop Stop relay server
|
|
813
|
+
restart Stop then start relay server
|
|
814
|
+
test Run connectivity test
|
|
653
815
|
|
|
654
816
|
${YELLOW}NAVIGATION:${NC}
|
|
655
817
|
goto <url> Navigate current tab to URL
|
|
818
|
+
open <url> Open URL in default browser
|
|
656
819
|
eval <js> Execute JavaScript, return result
|
|
657
820
|
click <selector> Click element
|
|
658
821
|
type <sel> <text> Type into input
|
|
659
822
|
screenshot [path] Take screenshot
|
|
823
|
+
|
|
824
|
+
${YELLOW}PAGE INFO:${NC}
|
|
660
825
|
text Get page text content
|
|
826
|
+
html [selector] Get page HTML (or element HTML)
|
|
827
|
+
title Get page title
|
|
828
|
+
url Get current URL
|
|
829
|
+
tabs List connected tabs
|
|
661
830
|
|
|
662
831
|
${YELLOW}AUTOMATION:${NC}
|
|
663
832
|
run <task.yaml> Execute YAML task file
|
|
664
833
|
loop <task> [opts] Run in Ralph Wiggum loop until complete
|
|
665
834
|
|
|
835
|
+
${YELLOW}CONFIG:${NC}
|
|
836
|
+
domains List configured domain shortcuts
|
|
837
|
+
|
|
666
838
|
${YELLOW}LOOP OPTIONS:${NC}
|
|
667
839
|
-n, --max-iterations N Max iterations (default: 10)
|
|
668
840
|
-t, --timeout N Max runtime in seconds (default: 3600)
|
|
@@ -685,6 +857,7 @@ ${YELLOW}EXAMPLES:${NC}
|
|
|
685
857
|
glider start
|
|
686
858
|
glider goto "https://google.com"
|
|
687
859
|
glider eval "document.title"
|
|
860
|
+
glider html "div.main"
|
|
688
861
|
glider run mytask.yaml
|
|
689
862
|
glider loop mytask.yaml -n 20 -t 600
|
|
690
863
|
|
|
@@ -697,9 +870,29 @@ ${YELLOW}RALPH WIGGUM PATTERN:${NC}
|
|
|
697
870
|
|
|
698
871
|
${YELLOW}REQUIREMENTS:${NC}
|
|
699
872
|
- Node.js 18+
|
|
700
|
-
- bserve relay server (~/scripts/bserve)
|
|
701
873
|
- Glider Chrome extension connected
|
|
874
|
+
|
|
875
|
+
${YELLOW}DOMAIN EXTENSIONS:${NC}
|
|
876
|
+
Add custom domain commands via ~/.cursor/glider/domains.json:
|
|
877
|
+
{
|
|
878
|
+
"mysite": { "url": "https://mysite.com/dashboard" },
|
|
879
|
+
"mytool": { "script": "~/.cursor/tools/scripts/mytool.sh" }
|
|
880
|
+
}
|
|
881
|
+
Then: glider mysite -> navigates to that URL
|
|
882
|
+
glider mytool -> runs that script
|
|
702
883
|
`);
|
|
884
|
+
|
|
885
|
+
// Show loaded domains if any
|
|
886
|
+
const domainKeys = Object.keys(DOMAINS);
|
|
887
|
+
if (domainKeys.length > 0) {
|
|
888
|
+
console.log(`${YELLOW}LOADED DOMAINS:${NC} (from config)`);
|
|
889
|
+
for (const key of domainKeys) {
|
|
890
|
+
const d = DOMAINS[key];
|
|
891
|
+
const desc = d.description || d.url || d.script || '';
|
|
892
|
+
console.log(` ${GREEN}${key}${NC} ${DIM}${desc}${NC}`);
|
|
893
|
+
}
|
|
894
|
+
console.log('');
|
|
895
|
+
}
|
|
703
896
|
}
|
|
704
897
|
|
|
705
898
|
// Main
|
|
@@ -730,10 +923,25 @@ async function main() {
|
|
|
730
923
|
case 'stop':
|
|
731
924
|
await cmdStop();
|
|
732
925
|
break;
|
|
926
|
+
case 'restart':
|
|
927
|
+
await cmdRestart();
|
|
928
|
+
break;
|
|
929
|
+
case 'test':
|
|
930
|
+
await cmdTest();
|
|
931
|
+
break;
|
|
932
|
+
case 'tabs':
|
|
933
|
+
await cmdTabs();
|
|
934
|
+
break;
|
|
935
|
+
case 'domains':
|
|
936
|
+
await cmdDomains();
|
|
937
|
+
break;
|
|
733
938
|
case 'goto':
|
|
734
939
|
case 'navigate':
|
|
735
940
|
await cmdGoto(args[1]);
|
|
736
941
|
break;
|
|
942
|
+
case 'open':
|
|
943
|
+
await cmdOpen(args[1]);
|
|
944
|
+
break;
|
|
737
945
|
case 'eval':
|
|
738
946
|
case 'js':
|
|
739
947
|
await cmdEval(args.slice(1).join(' '));
|
|
@@ -750,6 +958,15 @@ async function main() {
|
|
|
750
958
|
case 'text':
|
|
751
959
|
await cmdText();
|
|
752
960
|
break;
|
|
961
|
+
case 'html':
|
|
962
|
+
await cmdHtml(args[1]);
|
|
963
|
+
break;
|
|
964
|
+
case 'title':
|
|
965
|
+
await cmdTitle();
|
|
966
|
+
break;
|
|
967
|
+
case 'url':
|
|
968
|
+
await cmdUrl();
|
|
969
|
+
break;
|
|
753
970
|
case 'run':
|
|
754
971
|
await cmdRun(args[1]);
|
|
755
972
|
break;
|
|
@@ -773,6 +990,29 @@ async function main() {
|
|
|
773
990
|
await cmdLoop(taskArg, loopOpts);
|
|
774
991
|
break;
|
|
775
992
|
default:
|
|
993
|
+
// Check if it's a domain command from config
|
|
994
|
+
if (DOMAINS[cmd]) {
|
|
995
|
+
const domain = DOMAINS[cmd];
|
|
996
|
+
if (domain.script) {
|
|
997
|
+
// Execute external script
|
|
998
|
+
const scriptPath = domain.script.replace(/^~/, os.homedir());
|
|
999
|
+
if (fs.existsSync(scriptPath)) {
|
|
1000
|
+
const { execSync } = require('child_process');
|
|
1001
|
+
try {
|
|
1002
|
+
execSync(`"${scriptPath}" ${args.slice(1).map(a => `"${a}"`).join(' ')}`, { stdio: 'inherit' });
|
|
1003
|
+
} catch (e) {
|
|
1004
|
+
process.exit(e.status || 1);
|
|
1005
|
+
}
|
|
1006
|
+
} else {
|
|
1007
|
+
log.fail(`Domain script not found: ${scriptPath}`);
|
|
1008
|
+
process.exit(1);
|
|
1009
|
+
}
|
|
1010
|
+
} else if (domain.url) {
|
|
1011
|
+
// Navigate to domain URL
|
|
1012
|
+
await cmdGoto(domain.url);
|
|
1013
|
+
}
|
|
1014
|
+
break;
|
|
1015
|
+
}
|
|
776
1016
|
log.fail(`Unknown command: ${cmd}`);
|
|
777
1017
|
showHelp();
|
|
778
1018
|
process.exit(1);
|