@jackwener/opencli 0.9.2 → 0.9.5

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 (49) hide show
  1. package/CLI-ELECTRON.md +72 -0
  2. package/README.md +5 -1
  3. package/README.zh-CN.md +5 -1
  4. package/dist/cli-manifest.json +231 -0
  5. package/dist/clis/chatgpt/new.d.ts +1 -0
  6. package/dist/clis/chatgpt/new.js +23 -0
  7. package/dist/clis/chatgpt/read.d.ts +1 -0
  8. package/dist/clis/chatgpt/read.js +28 -0
  9. package/dist/clis/chatgpt/send.d.ts +1 -0
  10. package/dist/clis/chatgpt/send.js +31 -0
  11. package/dist/clis/chatgpt/status.d.ts +1 -0
  12. package/dist/clis/chatgpt/status.js +21 -0
  13. package/dist/clis/codex/model.d.ts +1 -0
  14. package/dist/clis/codex/model.js +55 -0
  15. package/dist/clis/cursor/composer.d.ts +1 -0
  16. package/dist/clis/cursor/composer.js +60 -0
  17. package/dist/clis/cursor/dump.d.ts +1 -0
  18. package/dist/clis/cursor/dump.js +25 -0
  19. package/dist/clis/cursor/extract-code.d.ts +1 -0
  20. package/dist/clis/cursor/extract-code.js +35 -0
  21. package/dist/clis/cursor/model.d.ts +1 -0
  22. package/dist/clis/cursor/model.js +53 -0
  23. package/dist/clis/cursor/new.d.ts +1 -0
  24. package/dist/clis/cursor/new.js +27 -0
  25. package/dist/clis/cursor/read.d.ts +1 -0
  26. package/dist/clis/cursor/read.js +43 -0
  27. package/dist/clis/cursor/send.d.ts +1 -0
  28. package/dist/clis/cursor/send.js +39 -0
  29. package/dist/clis/cursor/status.d.ts +1 -0
  30. package/dist/clis/cursor/status.js +21 -0
  31. package/package.json +1 -1
  32. package/src/clis/chatgpt/README.md +35 -0
  33. package/src/clis/chatgpt/README.zh-CN.md +35 -0
  34. package/src/clis/chatgpt/new.ts +24 -0
  35. package/src/clis/chatgpt/read.ts +32 -0
  36. package/src/clis/chatgpt/send.ts +36 -0
  37. package/src/clis/chatgpt/status.ts +22 -0
  38. package/src/clis/codex/README.md +1 -0
  39. package/src/clis/codex/model.ts +59 -0
  40. package/src/clis/cursor/README.md +33 -0
  41. package/src/clis/cursor/README.zh-CN.md +33 -0
  42. package/src/clis/cursor/composer.ts +71 -0
  43. package/src/clis/cursor/dump.ts +28 -0
  44. package/src/clis/cursor/extract-code.ts +39 -0
  45. package/src/clis/cursor/model.ts +57 -0
  46. package/src/clis/cursor/new.ts +32 -0
  47. package/src/clis/cursor/read.ts +47 -0
  48. package/src/clis/cursor/send.ts +47 -0
  49. package/src/clis/cursor/status.ts +23 -0
@@ -0,0 +1,28 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import * as fs from 'fs';
3
+
4
+ export const dumpCommand = cli({
5
+ site: 'cursor',
6
+ name: 'dump',
7
+ description: 'Dump the DOM and Accessibility tree of Cursor for reverse-engineering',
8
+ domain: 'localhost',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ columns: ['action', 'files'],
12
+ func: async (page) => {
13
+ // Extract full HTML
14
+ const dom = await page.evaluate('document.body.innerHTML');
15
+ fs.writeFileSync('/tmp/cursor-dom.html', dom);
16
+
17
+ // Get accessibility snapshot
18
+ const snap = await page.snapshot({ interactive: false });
19
+ fs.writeFileSync('/tmp/cursor-snapshot.json', JSON.stringify(snap, null, 2));
20
+
21
+ return [
22
+ {
23
+ action: 'Dom extraction finished',
24
+ files: '/tmp/cursor-dom.html, /tmp/cursor-snapshot.json',
25
+ },
26
+ ];
27
+ },
28
+ });
@@ -0,0 +1,39 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ export const extractCodeCommand = cli({
5
+ site: 'cursor',
6
+ name: 'extract-code',
7
+ description: 'Extract multi-line code blocks from the current Cursor conversation',
8
+ domain: 'localhost',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ args: [],
12
+ columns: ['Code'],
13
+ func: async (page: IPage) => {
14
+ const blocks = await page.evaluate(`
15
+ (function() {
16
+ // Find standard pre/code blocks
17
+ let elements = Array.from(document.querySelectorAll('pre code, .markdown-root pre'));
18
+
19
+ // Fallback to Monaco editor content inside the UI
20
+ if (elements.length === 0) {
21
+ elements = Array.from(document.querySelectorAll('.monaco-editor'));
22
+ }
23
+
24
+ // Generic fallback to any code tag that spans multiple lines
25
+ if (elements.length === 0) {
26
+ elements = Array.from(document.querySelectorAll('code')).filter(c => c.innerText.includes('\\n'));
27
+ }
28
+
29
+ return elements.map(el => el.innerText || el.textContent || '').filter(text => text.trim().length > 0);
30
+ })()
31
+ `);
32
+
33
+ if (!blocks || blocks.length === 0) {
34
+ return [{ Code: 'No code blocks found in Cursor.' }];
35
+ }
36
+
37
+ return blocks.map((code: string) => ({ Code: code }));
38
+ },
39
+ });
@@ -0,0 +1,57 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ export const modelCommand = cli({
5
+ site: 'cursor',
6
+ name: 'model',
7
+ description: 'Get or switch the currently active AI model in Cursor',
8
+ domain: 'localhost',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ args: [
12
+ { name: 'model_name', required: false, positional: true, help: 'The ID of the model to switch to (e.g. claude-3.5-sonnet)' }
13
+ ],
14
+ columns: ['Status', 'Model'],
15
+ func: async (page: IPage, kwargs: any) => {
16
+ const desiredModel = kwargs.model_name as string | undefined;
17
+
18
+ if (!desiredModel) {
19
+ // Just read the current model
20
+ const currentModel = await page.evaluate(`
21
+ (function() {
22
+ const m = document.querySelector('.composer-unified-dropdown-model span, [class*="unifiedmodeldropdown"] span');
23
+ return m ? m.textContent.trim() : 'Unknown or Not Found';
24
+ })()
25
+ `);
26
+
27
+ return [
28
+ {
29
+ Status: 'Active',
30
+ Model: currentModel,
31
+ },
32
+ ];
33
+ } else {
34
+ // Try to switch model (click dropdown, type/select model)
35
+ const success = await page.evaluate(`
36
+ (function(targetModel) {
37
+ const dropdown = document.querySelector('.composer-unified-dropdown-model, [class*="unifiedmodeldropdown"]');
38
+ if (!dropdown) return 'Dropdown not found';
39
+
40
+ dropdown.click();
41
+ // After clicking, the DOM usually spawns a popup list.
42
+ // Because it's hard to predict exactly how the list renders,
43
+ // a simple scriptable approach is just to click it, and hope we can select it via UI.
44
+ // In many React apps, clicking it opens a menu, and clicking the item works.
45
+ return 'Dropdown opened. Automated switching is not fully generic. Please implement precise list navigation depending on DOM.';
46
+ })(${JSON.stringify(desiredModel)})
47
+ `);
48
+
49
+ return [
50
+ {
51
+ Status: success,
52
+ Model: desiredModel,
53
+ },
54
+ ];
55
+ }
56
+ },
57
+ });
@@ -0,0 +1,32 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ export const newCommand = cli({
5
+ site: 'cursor',
6
+ name: 'new',
7
+ description: 'Start a new Cursor chat or Composer session',
8
+ domain: 'localhost',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ columns: ['Status'],
12
+ func: async (page: IPage) => {
13
+ const success = await page.evaluate(`
14
+ (function() {
15
+ const newChatButton = document.querySelector('[aria-label="New Chat"], [aria-label="New Chat (⌘N)"], .agent-sidebar-new-agent-button');
16
+ if (newChatButton) {
17
+ newChatButton.click();
18
+ return true;
19
+ }
20
+ return false;
21
+ })()
22
+ `);
23
+
24
+ if (!success) {
25
+ throw new Error('Could not find New Chat button in Cursor DOM.');
26
+ }
27
+
28
+ await page.wait(1);
29
+
30
+ return [{ Status: 'Success' }];
31
+ },
32
+ });
@@ -0,0 +1,47 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ export const readCommand = cli({
5
+ site: 'cursor',
6
+ name: 'read',
7
+ description: 'Read the current Cursor chat/composer conversation history',
8
+ domain: 'localhost',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ columns: ['Role', 'Text'],
12
+ func: async (page: IPage) => {
13
+ const history = await page.evaluate(`
14
+ (function() {
15
+ const messages = Array.from(document.querySelectorAll('[data-message-role]'));
16
+
17
+ if (messages.length === 0) {
18
+ return [];
19
+ }
20
+
21
+ return messages.map(msg => {
22
+ const role = msg.getAttribute('data-message-role');
23
+ let text = '';
24
+
25
+ // Try to get structured markdown root for AI, or lexical text for human
26
+ const markdownRoot = msg.querySelector('.markdown-root');
27
+ if (markdownRoot) {
28
+ text = markdownRoot.innerText || markdownRoot.textContent;
29
+ } else {
30
+ text = msg.innerText || msg.textContent;
31
+ }
32
+
33
+ return {
34
+ Role: role === 'human' ? 'User' : 'Assistant',
35
+ Text: text.trim()
36
+ };
37
+ });
38
+ })()
39
+ `);
40
+
41
+ if (!history || history.length === 0) {
42
+ throw new Error('No conversation history found in Cursor.');
43
+ }
44
+
45
+ return history;
46
+ },
47
+ });
@@ -0,0 +1,47 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ export const sendCommand = cli({
5
+ site: 'cursor',
6
+ name: 'send',
7
+ description: 'Send a prompt directly into Cursor Composer/Chat',
8
+ domain: 'localhost',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ args: [{ name: 'text', required: true, positional: true, help: 'Text to send into Cursor' }],
12
+ columns: ['Status', 'InjectedText'],
13
+ func: async (page: IPage, kwargs: any) => {
14
+ const textToInsert = kwargs.text as string;
15
+
16
+ const injected = await page.evaluate(
17
+ `(function(text) {
18
+ // Find the Lexical editor input for Composer or Chat
19
+ let composer = document.querySelector('.aislash-editor-input, [data-lexical-editor="true"], [contenteditable="true"]');
20
+
21
+ if (!composer) {
22
+ return false;
23
+ }
24
+
25
+ composer.focus();
26
+ document.execCommand('insertText', false, text);
27
+ return true;
28
+ })(${JSON.stringify(textToInsert)})`
29
+ );
30
+
31
+ if (!injected) {
32
+ throw new Error('Could not find Cursor Composer input element.');
33
+ }
34
+
35
+ // Submit the command. In Cursor, Enter usually submits the chat.
36
+ await page.wait(0.5);
37
+ await page.pressKey('Enter');
38
+ await page.wait(1);
39
+
40
+ return [
41
+ {
42
+ Status: 'Success',
43
+ InjectedText: textToInsert,
44
+ },
45
+ ];
46
+ },
47
+ });
@@ -0,0 +1,23 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+
3
+ export const statusCommand = cli({
4
+ site: 'cursor',
5
+ name: 'status',
6
+ description: 'Check active CDP connection to Cursor AI Editor',
7
+ domain: 'localhost',
8
+ strategy: Strategy.UI, // Interactive UI manipulation
9
+ browser: true,
10
+ columns: ['Status', 'Url', 'Title'],
11
+ func: async (page) => {
12
+ const url = await page.evaluate('window.location.href');
13
+ const title = await page.evaluate('document.title');
14
+
15
+ return [
16
+ {
17
+ Status: 'Connected',
18
+ Url: url,
19
+ Title: title,
20
+ },
21
+ ];
22
+ },
23
+ });