@jackwener/opencli 0.7.11 → 0.9.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.
Files changed (76) hide show
  1. package/CDP.md +103 -0
  2. package/CDP.zh-CN.md +103 -0
  3. package/README.md +5 -0
  4. package/README.zh-CN.md +5 -0
  5. package/dist/browser/discover.d.ts +15 -0
  6. package/dist/browser/discover.js +68 -2
  7. package/dist/browser/errors.d.ts +2 -1
  8. package/dist/browser/errors.js +13 -0
  9. package/dist/browser/index.d.ts +1 -0
  10. package/dist/browser/index.js +1 -0
  11. package/dist/browser/mcp.js +8 -3
  12. package/dist/browser/page.js +11 -2
  13. package/dist/cli-manifest.json +246 -0
  14. package/dist/clis/antigravity/dump.d.ts +1 -0
  15. package/dist/clis/antigravity/dump.js +28 -0
  16. package/dist/clis/antigravity/extract-code.d.ts +1 -0
  17. package/dist/clis/antigravity/extract-code.js +32 -0
  18. package/dist/clis/antigravity/model.d.ts +1 -0
  19. package/dist/clis/antigravity/model.js +44 -0
  20. package/dist/clis/antigravity/new.d.ts +1 -0
  21. package/dist/clis/antigravity/new.js +25 -0
  22. package/dist/clis/antigravity/read.d.ts +1 -0
  23. package/dist/clis/antigravity/read.js +34 -0
  24. package/dist/clis/antigravity/send.d.ts +1 -0
  25. package/dist/clis/antigravity/send.js +35 -0
  26. package/dist/clis/antigravity/status.d.ts +1 -0
  27. package/dist/clis/antigravity/status.js +18 -0
  28. package/dist/clis/antigravity/watch.d.ts +1 -0
  29. package/dist/clis/antigravity/watch.js +41 -0
  30. package/dist/clis/barchart/flow.js +56 -58
  31. package/dist/clis/xiaoyuzhou/episode.d.ts +1 -0
  32. package/dist/clis/xiaoyuzhou/episode.js +28 -0
  33. package/dist/clis/xiaoyuzhou/podcast-episodes.d.ts +1 -0
  34. package/dist/clis/xiaoyuzhou/podcast-episodes.js +36 -0
  35. package/dist/clis/xiaoyuzhou/podcast.d.ts +1 -0
  36. package/dist/clis/xiaoyuzhou/podcast.js +27 -0
  37. package/dist/clis/xiaoyuzhou/utils.d.ts +16 -0
  38. package/dist/clis/xiaoyuzhou/utils.js +55 -0
  39. package/dist/clis/xiaoyuzhou/utils.test.d.ts +1 -0
  40. package/dist/clis/xiaoyuzhou/utils.test.js +99 -0
  41. package/dist/doctor.js +8 -0
  42. package/dist/engine.d.ts +1 -1
  43. package/dist/engine.js +59 -1
  44. package/dist/main.js +2 -15
  45. package/dist/pipeline/executor.js +2 -24
  46. package/dist/pipeline/registry.d.ts +19 -0
  47. package/dist/pipeline/registry.js +41 -0
  48. package/package.json +1 -1
  49. package/src/browser/discover.ts +79 -5
  50. package/src/browser/errors.ts +17 -1
  51. package/src/browser/index.ts +1 -0
  52. package/src/browser/mcp.ts +8 -3
  53. package/src/browser/page.ts +21 -2
  54. package/src/clis/antigravity/README.md +49 -0
  55. package/src/clis/antigravity/README.zh-CN.md +52 -0
  56. package/src/clis/antigravity/SKILL.md +42 -0
  57. package/src/clis/antigravity/dump.ts +30 -0
  58. package/src/clis/antigravity/extract-code.ts +34 -0
  59. package/src/clis/antigravity/model.ts +47 -0
  60. package/src/clis/antigravity/new.ts +28 -0
  61. package/src/clis/antigravity/read.ts +36 -0
  62. package/src/clis/antigravity/send.ts +40 -0
  63. package/src/clis/antigravity/status.ts +19 -0
  64. package/src/clis/antigravity/watch.ts +45 -0
  65. package/src/clis/barchart/flow.ts +57 -58
  66. package/src/clis/xiaoyuzhou/episode.ts +28 -0
  67. package/src/clis/xiaoyuzhou/podcast-episodes.ts +36 -0
  68. package/src/clis/xiaoyuzhou/podcast.ts +27 -0
  69. package/src/clis/xiaoyuzhou/utils.test.ts +122 -0
  70. package/src/clis/xiaoyuzhou/utils.ts +65 -0
  71. package/src/doctor.ts +9 -0
  72. package/src/engine.ts +58 -1
  73. package/src/main.ts +6 -11
  74. package/src/pipeline/executor.ts +2 -28
  75. package/src/pipeline/registry.ts +60 -0
  76. package/tests/e2e/public-commands.test.ts +62 -0
@@ -1,4 +1,142 @@
1
1
  [
2
+ {
3
+ "site": "antigravity",
4
+ "name": "dump",
5
+ "description": "Dump the DOM to help AI understand the UI",
6
+ "strategy": "ui",
7
+ "browser": true,
8
+ "args": [],
9
+ "type": "ts",
10
+ "modulePath": "antigravity/dump.js",
11
+ "domain": "localhost",
12
+ "columns": [
13
+ "htmlFile",
14
+ "snapFile"
15
+ ]
16
+ },
17
+ {
18
+ "site": "antigravity",
19
+ "name": "extract-code",
20
+ "description": "Extract multi-line code blocks from the current Antigravity conversation",
21
+ "strategy": "ui",
22
+ "browser": true,
23
+ "args": [],
24
+ "type": "ts",
25
+ "modulePath": "antigravity/extract-code.js",
26
+ "domain": "localhost",
27
+ "columns": [
28
+ "code"
29
+ ]
30
+ },
31
+ {
32
+ "site": "antigravity",
33
+ "name": "model",
34
+ "description": "Switch the active LLM model in Antigravity",
35
+ "strategy": "ui",
36
+ "browser": true,
37
+ "args": [
38
+ {
39
+ "name": "name",
40
+ "type": "str",
41
+ "required": true,
42
+ "positional": true,
43
+ "help": "Target model name (e.g. claude, gemini, o1)"
44
+ }
45
+ ],
46
+ "type": "ts",
47
+ "modulePath": "antigravity/model.js",
48
+ "domain": "localhost",
49
+ "columns": [
50
+ "status"
51
+ ]
52
+ },
53
+ {
54
+ "site": "antigravity",
55
+ "name": "new",
56
+ "description": "Start a new conversation / clear context in Antigravity",
57
+ "strategy": "ui",
58
+ "browser": true,
59
+ "args": [],
60
+ "type": "ts",
61
+ "modulePath": "antigravity/new.js",
62
+ "domain": "localhost",
63
+ "columns": [
64
+ "status"
65
+ ]
66
+ },
67
+ {
68
+ "site": "antigravity",
69
+ "name": "read",
70
+ "description": "Read the latest chat messages from Antigravity AI",
71
+ "strategy": "ui",
72
+ "browser": true,
73
+ "args": [
74
+ {
75
+ "name": "last",
76
+ "type": "str",
77
+ "required": false,
78
+ "help": "Number of recent messages to read (not fully implemented due to generic structure, currently returns full history text or latest chunk)"
79
+ }
80
+ ],
81
+ "type": "ts",
82
+ "modulePath": "antigravity/read.js",
83
+ "domain": "localhost",
84
+ "columns": [
85
+ "role",
86
+ "content"
87
+ ]
88
+ },
89
+ {
90
+ "site": "antigravity",
91
+ "name": "send",
92
+ "description": "Send a message to Antigravity AI via the internal Lexical editor",
93
+ "strategy": "ui",
94
+ "browser": true,
95
+ "args": [
96
+ {
97
+ "name": "message",
98
+ "type": "str",
99
+ "required": true,
100
+ "positional": true,
101
+ "help": "The message text to send"
102
+ }
103
+ ],
104
+ "type": "ts",
105
+ "modulePath": "antigravity/send.js",
106
+ "domain": "localhost",
107
+ "columns": [
108
+ "status",
109
+ "message"
110
+ ]
111
+ },
112
+ {
113
+ "site": "antigravity",
114
+ "name": "status",
115
+ "description": "Check Antigravity CDP connection and get current page state",
116
+ "strategy": "ui",
117
+ "browser": true,
118
+ "args": [],
119
+ "type": "ts",
120
+ "modulePath": "antigravity/status.js",
121
+ "domain": "localhost",
122
+ "columns": [
123
+ "status",
124
+ "url",
125
+ "title"
126
+ ]
127
+ },
128
+ {
129
+ "site": "antigravity",
130
+ "name": "watch",
131
+ "description": "Stream new chat messages from Antigravity in real-time",
132
+ "strategy": "ui",
133
+ "browser": true,
134
+ "args": [],
135
+ "type": "ts",
136
+ "modulePath": "antigravity/watch.js",
137
+ "domain": "localhost",
138
+ "columns": []
139
+ },
2
140
  {
3
141
  "site": "barchart",
4
142
  "name": "flow",
@@ -2522,6 +2660,114 @@
2522
2660
  "url"
2523
2661
  ]
2524
2662
  },
2663
+ {
2664
+ "site": "xiaoyuzhou",
2665
+ "name": "episode",
2666
+ "description": "View details of a Xiaoyuzhou podcast episode",
2667
+ "strategy": "public",
2668
+ "browser": false,
2669
+ "args": [
2670
+ {
2671
+ "name": "id",
2672
+ "type": "str",
2673
+ "required": true,
2674
+ "positional": true,
2675
+ "help": "Episode ID (eid from podcast-episodes output)"
2676
+ }
2677
+ ],
2678
+ "type": "ts",
2679
+ "modulePath": "xiaoyuzhou/episode.js",
2680
+ "domain": "www.xiaoyuzhoufm.com",
2681
+ "columns": [
2682
+ "title",
2683
+ "podcast",
2684
+ "duration",
2685
+ "plays",
2686
+ "comments",
2687
+ "likes",
2688
+ "date"
2689
+ ]
2690
+ },
2691
+ {
2692
+ "site": "xiaoyuzhou",
2693
+ "name": "podcast-episodes",
2694
+ "description": "List recent episodes of a Xiaoyuzhou podcast (up to 15, SSR limit)",
2695
+ "strategy": "public",
2696
+ "browser": false,
2697
+ "args": [
2698
+ {
2699
+ "name": "id",
2700
+ "type": "str",
2701
+ "required": true,
2702
+ "positional": true,
2703
+ "help": "Podcast ID (from xiaoyuzhoufm.com URL)"
2704
+ },
2705
+ {
2706
+ "name": "limit",
2707
+ "type": "int",
2708
+ "default": 15,
2709
+ "required": false,
2710
+ "help": "Max episodes to show (up to 15, SSR limit)"
2711
+ }
2712
+ ],
2713
+ "type": "ts",
2714
+ "modulePath": "xiaoyuzhou/podcast-episodes.js",
2715
+ "domain": "www.xiaoyuzhoufm.com",
2716
+ "columns": [
2717
+ "eid",
2718
+ "title",
2719
+ "duration",
2720
+ "plays",
2721
+ "date"
2722
+ ]
2723
+ },
2724
+ {
2725
+ "site": "xiaoyuzhou",
2726
+ "name": "podcast",
2727
+ "description": "View a Xiaoyuzhou podcast profile",
2728
+ "strategy": "public",
2729
+ "browser": false,
2730
+ "args": [
2731
+ {
2732
+ "name": "id",
2733
+ "type": "str",
2734
+ "required": true,
2735
+ "positional": true,
2736
+ "help": "Podcast ID (from xiaoyuzhoufm.com URL)"
2737
+ }
2738
+ ],
2739
+ "type": "ts",
2740
+ "modulePath": "xiaoyuzhou/podcast.js",
2741
+ "domain": "www.xiaoyuzhoufm.com",
2742
+ "columns": [
2743
+ "title",
2744
+ "author",
2745
+ "description",
2746
+ "subscribers",
2747
+ "episodes",
2748
+ "updated"
2749
+ ]
2750
+ },
2751
+ {
2752
+ "site": "xiaoyuzhou",
2753
+ "name": "utils",
2754
+ "description": "",
2755
+ "strategy": "cookie",
2756
+ "browser": true,
2757
+ "args": [],
2758
+ "type": "ts",
2759
+ "modulePath": "xiaoyuzhou/utils.js"
2760
+ },
2761
+ {
2762
+ "site": "xiaoyuzhou",
2763
+ "name": "utils.test",
2764
+ "description": "",
2765
+ "strategy": "cookie",
2766
+ "browser": true,
2767
+ "args": [],
2768
+ "type": "ts",
2769
+ "modulePath": "xiaoyuzhou/utils.test.js"
2770
+ },
2525
2771
  {
2526
2772
  "site": "xueqiu",
2527
2773
  "name": "feed",
@@ -0,0 +1 @@
1
+ export declare const dumpCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,28 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import * as fs from 'node:fs';
3
+ export const dumpCommand = cli({
4
+ site: 'antigravity',
5
+ name: 'dump',
6
+ description: 'Dump the DOM to help AI understand the UI',
7
+ domain: 'localhost',
8
+ strategy: Strategy.UI,
9
+ browser: true,
10
+ args: [],
11
+ columns: ['htmlFile', 'snapFile'],
12
+ func: async (page) => {
13
+ // Extract HTML
14
+ const html = await page.evaluate('document.body.innerHTML');
15
+ fs.writeFileSync('/tmp/antigravity-dom.html', html);
16
+ // Extract Snapshot
17
+ let snapFile = '';
18
+ try {
19
+ const snap = await page.snapshot({ raw: true });
20
+ snapFile = '/tmp/antigravity-snapshot.json';
21
+ fs.writeFileSync(snapFile, JSON.stringify(snap, null, 2));
22
+ }
23
+ catch (e) {
24
+ snapFile = 'Failed';
25
+ }
26
+ return [{ htmlFile: '/tmp/antigravity-dom.html', snapFile }];
27
+ },
28
+ });
@@ -0,0 +1 @@
1
+ export declare const extractCodeCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,32 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ export const extractCodeCommand = cli({
3
+ site: 'antigravity',
4
+ name: 'extract-code',
5
+ description: 'Extract multi-line code blocks from the current Antigravity conversation',
6
+ domain: 'localhost',
7
+ strategy: Strategy.UI,
8
+ browser: true,
9
+ args: [],
10
+ columns: ['code'],
11
+ func: async (page) => {
12
+ const blocks = await page.evaluate(`
13
+ async () => {
14
+ // Find standard pre/code blocks
15
+ let elements = Array.from(document.querySelectorAll('pre code'));
16
+
17
+ // Fallback to Monaco editor content inside the UI
18
+ if (elements.length === 0) {
19
+ elements = Array.from(document.querySelectorAll('.monaco-editor'));
20
+ }
21
+
22
+ // Generic fallback to any code tag that spans multiple lines
23
+ if (elements.length === 0) {
24
+ elements = Array.from(document.querySelectorAll('code')).filter(c => c.innerText.includes('\\n'));
25
+ }
26
+
27
+ return elements.map(el => el.innerText).filter(text => text.trim().length > 0);
28
+ }
29
+ `);
30
+ return blocks.map((code) => ({ code }));
31
+ },
32
+ });
@@ -0,0 +1 @@
1
+ export declare const modelCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,44 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ export const modelCommand = cli({
3
+ site: 'antigravity',
4
+ name: 'model',
5
+ description: 'Switch the active LLM model in Antigravity',
6
+ domain: 'localhost',
7
+ strategy: Strategy.UI,
8
+ browser: true,
9
+ args: [
10
+ { name: 'name', help: 'Target model name (e.g. claude, gemini, o1)', required: true, positional: true }
11
+ ],
12
+ columns: ['status'],
13
+ func: async (page, kwargs) => {
14
+ const targetName = kwargs.name.toLowerCase();
15
+ await page.evaluate(`
16
+ async () => {
17
+ const targetModelName = ${JSON.stringify(targetName)};
18
+
19
+ // 1. Locate the model selector dropdown trigger
20
+ const trigger = document.querySelector('div[aria-haspopup="dialog"] > div[tabindex="0"]');
21
+ if (!trigger) throw new Error('Could not find the model selector trigger in the UI');
22
+ trigger.click();
23
+
24
+ // 2. Wait a brief moment for React to mount the Portal/Dialog
25
+ await new Promise(r => setTimeout(r, 200));
26
+
27
+ // 3. Find the option spanning target text
28
+ const spans = Array.from(document.querySelectorAll('[role="dialog"] span'));
29
+ const target = spans.find(s => s.innerText.toLowerCase().includes(targetModelName));
30
+ if (!target) {
31
+ // If not found, click the trigger again to close it safely
32
+ trigger.click();
33
+ throw new Error('Model matching "' + targetModelName + '" was not found in the dropdown list.');
34
+ }
35
+
36
+ // 4. Click the closest parent that handles the row action
37
+ const optionNode = target.closest('.cursor-pointer') || target;
38
+ optionNode.click();
39
+ }
40
+ `);
41
+ await page.wait(0.5);
42
+ return [{ status: `Model switched to: ${kwargs.name}` }];
43
+ },
44
+ });
@@ -0,0 +1 @@
1
+ export declare const newCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,25 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ export const newCommand = cli({
3
+ site: 'antigravity',
4
+ name: 'new',
5
+ description: 'Start a new conversation / clear context in Antigravity',
6
+ domain: 'localhost',
7
+ strategy: Strategy.UI,
8
+ browser: true,
9
+ args: [],
10
+ columns: ['status'],
11
+ func: async (page) => {
12
+ await page.evaluate(`
13
+ async () => {
14
+ const btn = document.querySelector('[data-tooltip-id="new-conversation-tooltip"]');
15
+ if (!btn) throw new Error('Could not find New Conversation button');
16
+
17
+ // In case it's disabled, we must check, but we'll try to click it anyway
18
+ btn.click();
19
+ }
20
+ `);
21
+ // Give it a moment to reset the UI
22
+ await page.wait(0.5);
23
+ return [{ status: 'Successfully started a new conversation' }];
24
+ },
25
+ });
@@ -0,0 +1 @@
1
+ export declare const readCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,34 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ export const readCommand = cli({
3
+ site: 'antigravity',
4
+ name: 'read',
5
+ description: 'Read the latest chat messages from Antigravity AI',
6
+ domain: 'localhost',
7
+ strategy: Strategy.UI,
8
+ browser: true,
9
+ args: [
10
+ { name: 'last', help: 'Number of recent messages to read (not fully implemented due to generic structure, currently returns full history text or latest chunk)' }
11
+ ],
12
+ columns: ['role', 'content'],
13
+ func: async (page, kwargs) => {
14
+ // We execute a script inside Antigravity's Chromium environment to extract the text
15
+ // of the entire conversation pane.
16
+ const rawText = await page.evaluate(`
17
+ async () => {
18
+ const container = document.getElementById('conversation');
19
+ if (!container) throw new Error('Could not find conversation container');
20
+
21
+ // Extract the full visible text of the conversation
22
+ // In Electron/Chromium, innerText preserves basic visual line breaks nicely
23
+ return container.innerText;
24
+ }
25
+ `);
26
+ // We can do simple heuristic parsing based on typical visual markers if needed.
27
+ // For now, we return the entire text blob, or just the last 2000 characters if it's too long.
28
+ const cleanText = String(rawText).trim();
29
+ return [{
30
+ role: 'history',
31
+ content: cleanText
32
+ }];
33
+ },
34
+ });
@@ -0,0 +1 @@
1
+ export declare const sendCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,35 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ export const sendCommand = cli({
3
+ site: 'antigravity',
4
+ name: 'send',
5
+ description: 'Send a message to Antigravity AI via the internal Lexical editor',
6
+ domain: 'localhost',
7
+ strategy: Strategy.UI,
8
+ browser: true,
9
+ args: [
10
+ { name: 'message', help: 'The message text to send', required: true, positional: true }
11
+ ],
12
+ columns: ['status', 'message'],
13
+ func: async (page, kwargs) => {
14
+ const text = kwargs.message;
15
+ // We use evaluate to focus and insert text because Lexical editors maintain
16
+ // absolute control over their DOM and don't respond to raw node.textContent.
17
+ // document.execCommand simulates a native paste/typing action perfectly.
18
+ await page.evaluate(`
19
+ async () => {
20
+ const container = document.getElementById('antigravity.agentSidePanelInputBox');
21
+ if (!container) throw new Error('Could not find antigravity.agentSidePanelInputBox');
22
+ const editor = container.querySelector('[data-lexical-editor="true"]');
23
+ if (!editor) throw new Error('Could not find Antigravity input box');
24
+
25
+ editor.focus();
26
+ document.execCommand('insertText', false, ${JSON.stringify(text)});
27
+ }
28
+ `);
29
+ // Wait for the React/Lexical state to flush the new input
30
+ await page.wait(0.5);
31
+ // Press Enter to submit the message
32
+ await page.pressKey('Enter');
33
+ return [{ status: 'Sent successfully', message: text }];
34
+ },
35
+ });
@@ -0,0 +1 @@
1
+ export declare const statusCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,18 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ export const statusCommand = cli({
3
+ site: 'antigravity',
4
+ name: 'status',
5
+ description: 'Check Antigravity CDP connection and get current page state',
6
+ domain: 'localhost',
7
+ strategy: Strategy.UI,
8
+ browser: true,
9
+ args: [],
10
+ columns: ['status', 'url', 'title'],
11
+ func: async (page) => {
12
+ return {
13
+ status: 'Connected',
14
+ url: await page.evaluate('window.location.href'),
15
+ title: await page.evaluate('document.title'),
16
+ };
17
+ },
18
+ });
@@ -0,0 +1 @@
1
+ export declare const watchCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,41 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ export const watchCommand = cli({
3
+ site: 'antigravity',
4
+ name: 'watch',
5
+ description: 'Stream new chat messages from Antigravity in real-time',
6
+ domain: 'localhost',
7
+ strategy: Strategy.UI,
8
+ browser: true,
9
+ args: [],
10
+ timeoutSeconds: 86400, // Run for up to 24 hours
11
+ columns: [], // We use direct stdout streaming
12
+ func: async (page) => {
13
+ console.log('Watching Antigravity chat... (Press Ctrl+C to stop)');
14
+ let lastLength = 0;
15
+ // Loop until process gets killed
16
+ while (true) {
17
+ const text = await page.evaluate(`
18
+ async () => {
19
+ const container = document.getElementById('conversation');
20
+ return container ? container.innerText : '';
21
+ }
22
+ `);
23
+ const currentLength = text.length;
24
+ if (currentLength > lastLength) {
25
+ // Delta mode
26
+ const newSegment = text.substring(lastLength);
27
+ if (newSegment.trim().length > 0) {
28
+ process.stdout.write(newSegment);
29
+ }
30
+ lastLength = currentLength;
31
+ }
32
+ else if (currentLength < lastLength) {
33
+ // The conversation was cleared or updated significantly
34
+ lastLength = currentLength;
35
+ console.log('\\n--- Conversation Cleared/Changed ---\\n');
36
+ process.stdout.write(text);
37
+ }
38
+ await new Promise(resolve => setTimeout(resolve, 500));
39
+ }
40
+ },
41
+ });