@jackwener/opencli 0.9.5 → 0.9.8
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/.github/ISSUE_TEMPLATE/bug_report.yml +83 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +42 -0
- package/.github/ISSUE_TEMPLATE/new_site_adapter.yml +57 -0
- package/.github/dependabot.yml +27 -0
- package/.github/pull_request_template.md +24 -0
- package/.github/workflows/ci.yml +14 -8
- package/.github/workflows/e2e-headed.yml +6 -2
- package/.github/workflows/pkg-pr-new.yml +2 -2
- package/.github/workflows/release-please.yml +25 -0
- package/.github/workflows/release.yml +2 -2
- package/.github/workflows/security.yml +36 -0
- package/CLI-ELECTRON.md +89 -36
- package/CONTRIBUTING.md +167 -0
- package/README.md +98 -32
- package/README.zh-CN.md +99 -33
- package/dist/browser/discover.js +22 -7
- package/dist/browser.test.js +23 -0
- package/dist/build-manifest.d.ts +26 -0
- package/dist/build-manifest.js +132 -60
- package/dist/build-manifest.test.d.ts +1 -0
- package/dist/build-manifest.test.js +26 -0
- package/dist/cli-manifest.json +1875 -271
- package/dist/clis/antigravity/model.js +2 -2
- package/dist/clis/antigravity/send.js +2 -2
- package/dist/clis/bilibili/download.d.ts +10 -0
- package/dist/clis/bilibili/download.js +135 -0
- package/dist/clis/chatgpt/ask.d.ts +1 -0
- package/dist/clis/chatgpt/ask.js +68 -0
- package/dist/clis/chatgpt/send.js +11 -0
- package/dist/clis/chatwise/ask.d.ts +1 -0
- package/dist/clis/chatwise/ask.js +76 -0
- package/dist/clis/chatwise/export.d.ts +1 -0
- package/dist/clis/chatwise/export.js +46 -0
- package/dist/clis/chatwise/history.d.ts +1 -0
- package/dist/clis/chatwise/history.js +43 -0
- package/dist/clis/chatwise/model.d.ts +1 -0
- package/dist/clis/chatwise/model.js +81 -0
- package/dist/clis/chatwise/new.d.ts +1 -0
- package/dist/clis/chatwise/new.js +18 -0
- package/dist/clis/chatwise/read.d.ts +1 -0
- package/dist/clis/chatwise/read.js +39 -0
- package/dist/clis/chatwise/screenshot.d.ts +1 -0
- package/dist/clis/chatwise/screenshot.js +27 -0
- package/dist/clis/chatwise/send.d.ts +1 -0
- package/dist/clis/chatwise/send.js +45 -0
- package/dist/clis/chatwise/status.d.ts +1 -0
- package/dist/clis/chatwise/status.js +22 -0
- package/dist/clis/codex/ask.d.ts +1 -0
- package/dist/clis/codex/ask.js +67 -0
- package/dist/clis/codex/export.d.ts +1 -0
- package/dist/clis/codex/export.js +37 -0
- package/dist/clis/codex/history.d.ts +1 -0
- package/dist/clis/codex/history.js +43 -0
- package/dist/clis/codex/read.js +3 -5
- package/dist/clis/codex/screenshot.d.ts +1 -0
- package/dist/clis/codex/screenshot.js +27 -0
- package/dist/clis/codex/send.js +3 -6
- package/dist/clis/codex/status.js +2 -1
- package/dist/clis/cursor/ask.d.ts +1 -0
- package/dist/clis/cursor/ask.js +69 -0
- package/dist/clis/cursor/composer.js +9 -28
- package/dist/clis/cursor/export.d.ts +1 -0
- package/dist/clis/cursor/export.js +51 -0
- package/dist/clis/cursor/history.d.ts +1 -0
- package/dist/clis/cursor/history.js +43 -0
- package/dist/clis/cursor/new.js +4 -13
- package/dist/clis/cursor/screenshot.d.ts +1 -0
- package/dist/clis/cursor/screenshot.js +31 -0
- package/dist/clis/discord-app/channels.d.ts +1 -0
- package/dist/clis/discord-app/channels.js +45 -0
- package/dist/clis/discord-app/members.d.ts +1 -0
- package/dist/clis/discord-app/members.js +38 -0
- package/dist/clis/discord-app/read.d.ts +1 -0
- package/dist/clis/discord-app/read.js +45 -0
- package/dist/clis/discord-app/search.d.ts +1 -0
- package/dist/clis/discord-app/search.js +56 -0
- package/dist/clis/discord-app/send.d.ts +1 -0
- package/dist/clis/discord-app/send.js +27 -0
- package/dist/clis/discord-app/servers.d.ts +1 -0
- package/dist/clis/discord-app/servers.js +36 -0
- package/dist/clis/discord-app/status.d.ts +1 -0
- package/dist/clis/discord-app/status.js +16 -0
- package/dist/clis/feishu/new.d.ts +1 -0
- package/dist/clis/feishu/new.js +27 -0
- package/dist/clis/feishu/read.d.ts +1 -0
- package/dist/clis/feishu/read.js +40 -0
- package/dist/clis/feishu/search.d.ts +1 -0
- package/dist/clis/feishu/search.js +30 -0
- package/dist/clis/feishu/send.d.ts +1 -0
- package/dist/clis/feishu/send.js +39 -0
- package/dist/clis/feishu/status.d.ts +1 -0
- package/dist/clis/feishu/status.js +28 -0
- package/dist/clis/grok/ask.d.ts +1 -0
- package/dist/clis/grok/ask.js +82 -0
- package/dist/clis/grok/debug.d.ts +1 -0
- package/dist/clis/grok/debug.js +45 -0
- package/dist/clis/jimeng/generate.yaml +84 -0
- package/dist/clis/jimeng/history.yaml +47 -0
- package/dist/clis/linux-do/categories.yaml +41 -0
- package/dist/clis/linux-do/category.yaml +49 -0
- package/dist/clis/linux-do/hot.yaml +50 -0
- package/dist/clis/linux-do/latest.yaml +40 -0
- package/dist/clis/linux-do/search.yaml +45 -0
- package/dist/clis/linux-do/topic.yaml +38 -0
- package/dist/clis/notion/export.d.ts +1 -0
- package/dist/clis/notion/export.js +31 -0
- package/dist/clis/notion/favorites.d.ts +1 -0
- package/dist/clis/notion/favorites.js +84 -0
- package/dist/clis/notion/new.d.ts +1 -0
- package/dist/clis/notion/new.js +34 -0
- package/dist/clis/notion/read.d.ts +1 -0
- package/dist/clis/notion/read.js +30 -0
- package/dist/clis/notion/search.d.ts +1 -0
- package/dist/clis/notion/search.js +46 -0
- package/dist/clis/notion/sidebar.d.ts +1 -0
- package/dist/clis/notion/sidebar.js +41 -0
- package/dist/clis/notion/status.d.ts +1 -0
- package/dist/clis/notion/status.js +16 -0
- package/dist/clis/notion/write.d.ts +1 -0
- package/dist/clis/notion/write.js +40 -0
- package/dist/clis/twitter/download.d.ts +8 -0
- package/dist/clis/twitter/download.js +204 -0
- package/dist/clis/wechat/chats.d.ts +1 -0
- package/dist/clis/wechat/chats.js +28 -0
- package/dist/clis/wechat/contacts.d.ts +1 -0
- package/dist/clis/wechat/contacts.js +28 -0
- package/dist/clis/wechat/read.d.ts +1 -0
- package/dist/clis/wechat/read.js +58 -0
- package/dist/clis/wechat/search.d.ts +1 -0
- package/dist/clis/wechat/search.js +31 -0
- package/dist/clis/wechat/send.d.ts +1 -0
- package/dist/clis/wechat/send.js +42 -0
- package/dist/clis/wechat/status.d.ts +1 -0
- package/dist/clis/wechat/status.js +29 -0
- package/dist/clis/xiaohongshu/creator-note-detail.d.ts +10 -0
- package/dist/clis/xiaohongshu/creator-note-detail.js +88 -0
- package/dist/clis/xiaohongshu/creator-notes.d.ts +11 -0
- package/dist/clis/xiaohongshu/creator-notes.js +109 -0
- package/dist/clis/xiaohongshu/creator-profile.d.ts +10 -0
- package/dist/clis/xiaohongshu/creator-profile.js +54 -0
- package/dist/clis/xiaohongshu/creator-stats.d.ts +10 -0
- package/dist/clis/xiaohongshu/creator-stats.js +74 -0
- package/dist/clis/xiaohongshu/download.d.ts +7 -0
- package/dist/clis/xiaohongshu/download.js +155 -0
- package/dist/clis/xiaohongshu/search.js +1 -1
- package/dist/clis/xiaohongshu/user-helpers.d.ts +15 -0
- package/dist/clis/xiaohongshu/user-helpers.js +67 -0
- package/dist/clis/xiaohongshu/user-helpers.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/user-helpers.test.js +81 -0
- package/dist/clis/xiaohongshu/user.js +46 -29
- package/dist/clis/zhihu/download.d.ts +11 -0
- package/dist/clis/zhihu/download.js +186 -0
- package/dist/clis/zhihu/download.test.d.ts +1 -0
- package/dist/clis/zhihu/download.test.js +10 -0
- package/dist/download/index.d.ts +79 -0
- package/dist/download/index.js +325 -0
- package/dist/download/progress.d.ts +36 -0
- package/dist/download/progress.js +111 -0
- package/dist/engine.test.js +15 -0
- package/dist/main.js +16 -3
- package/dist/pipeline/registry.js +2 -0
- package/dist/pipeline/steps/download.d.ts +34 -0
- package/dist/pipeline/steps/download.js +251 -0
- package/dist/pipeline/template.js +28 -0
- package/package.json +4 -3
- package/scripts/test-site.mjs +70 -0
- package/src/browser/discover.ts +23 -7
- package/src/browser.test.ts +23 -0
- package/src/build-manifest.test.ts +28 -0
- package/src/build-manifest.ts +147 -57
- package/src/clis/antigravity/README.md +2 -3
- package/src/clis/antigravity/README.zh-CN.md +2 -3
- package/src/clis/antigravity/SKILL.md +1 -1
- package/src/clis/antigravity/model.ts +2 -2
- package/src/clis/antigravity/send.ts +2 -2
- package/src/clis/bilibili/download.ts +161 -0
- package/src/clis/chatgpt/README.md +25 -16
- package/src/clis/chatgpt/README.zh-CN.md +27 -18
- package/src/clis/chatgpt/ask.ts +77 -0
- package/src/clis/chatgpt/send.ts +12 -0
- package/src/clis/chatwise/README.md +38 -0
- package/src/clis/chatwise/README.zh-CN.md +38 -0
- package/src/clis/chatwise/ask.ts +87 -0
- package/src/clis/chatwise/export.ts +51 -0
- package/src/clis/chatwise/history.ts +47 -0
- package/src/clis/chatwise/model.ts +87 -0
- package/src/clis/chatwise/new.ts +21 -0
- package/src/clis/chatwise/read.ts +42 -0
- package/src/clis/chatwise/screenshot.ts +33 -0
- package/src/clis/chatwise/send.ts +50 -0
- package/src/clis/chatwise/status.ts +25 -0
- package/src/clis/codex/ask.ts +77 -0
- package/src/clis/codex/export.ts +42 -0
- package/src/clis/codex/extract-diff.ts +1 -0
- package/src/clis/codex/history.ts +47 -0
- package/src/clis/codex/read.ts +5 -6
- package/src/clis/codex/screenshot.ts +33 -0
- package/src/clis/codex/send.ts +6 -7
- package/src/clis/codex/status.ts +4 -2
- package/src/clis/cursor/ask.ts +81 -0
- package/src/clis/cursor/composer.ts +9 -30
- package/src/clis/cursor/export.ts +57 -0
- package/src/clis/cursor/history.ts +47 -0
- package/src/clis/cursor/new.ts +4 -15
- package/src/clis/cursor/screenshot.ts +38 -0
- package/src/clis/discord-app/README.md +28 -0
- package/src/clis/discord-app/README.zh-CN.md +28 -0
- package/src/clis/discord-app/channels.ts +48 -0
- package/src/clis/discord-app/members.ts +41 -0
- package/src/clis/discord-app/read.ts +49 -0
- package/src/clis/discord-app/search.ts +64 -0
- package/src/clis/discord-app/send.ts +32 -0
- package/src/clis/discord-app/servers.ts +39 -0
- package/src/clis/discord-app/status.ts +18 -0
- package/src/clis/feishu/README.md +20 -0
- package/src/clis/feishu/README.zh-CN.md +20 -0
- package/src/clis/feishu/new.ts +32 -0
- package/src/clis/feishu/read.ts +48 -0
- package/src/clis/feishu/search.ts +35 -0
- package/src/clis/feishu/send.ts +46 -0
- package/src/clis/feishu/status.ts +34 -0
- package/src/clis/grok/ask.ts +90 -0
- package/src/clis/grok/debug.ts +49 -0
- package/src/clis/jimeng/generate.yaml +84 -0
- package/src/clis/jimeng/history.yaml +47 -0
- package/src/clis/linux-do/categories.yaml +41 -0
- package/src/clis/linux-do/category.yaml +49 -0
- package/src/clis/linux-do/hot.yaml +50 -0
- package/src/clis/linux-do/latest.yaml +40 -0
- package/src/clis/linux-do/search.yaml +45 -0
- package/src/clis/linux-do/topic.yaml +38 -0
- package/src/clis/notion/README.md +29 -0
- package/src/clis/notion/README.zh-CN.md +29 -0
- package/src/clis/notion/export.ts +36 -0
- package/src/clis/notion/favorites.ts +87 -0
- package/src/clis/notion/new.ts +39 -0
- package/src/clis/notion/read.ts +33 -0
- package/src/clis/notion/search.ts +54 -0
- package/src/clis/notion/sidebar.ts +44 -0
- package/src/clis/notion/status.ts +18 -0
- package/src/clis/notion/write.ts +45 -0
- package/src/clis/twitter/download.ts +227 -0
- package/src/clis/wechat/README.md +28 -0
- package/src/clis/wechat/README.zh-CN.md +28 -0
- package/src/clis/wechat/chats.ts +33 -0
- package/src/clis/wechat/contacts.ts +33 -0
- package/src/clis/wechat/read.ts +72 -0
- package/src/clis/wechat/search.ts +36 -0
- package/src/clis/wechat/send.ts +49 -0
- package/src/clis/wechat/status.ts +35 -0
- package/src/clis/xiaohongshu/creator-note-detail.ts +95 -0
- package/src/clis/xiaohongshu/creator-notes.ts +116 -0
- package/src/clis/xiaohongshu/creator-profile.ts +60 -0
- package/src/clis/xiaohongshu/creator-stats.ts +81 -0
- package/src/clis/xiaohongshu/download.ts +173 -0
- package/src/clis/xiaohongshu/search.ts +1 -1
- package/src/clis/xiaohongshu/user-helpers.test.ts +106 -0
- package/src/clis/xiaohongshu/user-helpers.ts +85 -0
- package/src/clis/xiaohongshu/user.ts +52 -32
- package/src/clis/zhihu/download.test.ts +12 -0
- package/src/clis/zhihu/download.ts +223 -0
- package/src/download/index.ts +395 -0
- package/src/download/progress.ts +125 -0
- package/src/engine.test.ts +17 -0
- package/src/main.ts +12 -3
- package/src/pipeline/registry.ts +2 -0
- package/src/pipeline/steps/download.ts +310 -0
- package/src/pipeline/template.ts +26 -0
- package/tests/e2e/browser-auth.test.ts +25 -0
package/src/build-manifest.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import * as fs from 'node:fs';
|
|
13
13
|
import * as path from 'node:path';
|
|
14
|
-
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
15
15
|
import yaml from 'js-yaml';
|
|
16
16
|
|
|
17
17
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -43,6 +43,116 @@ interface ManifestEntry {
|
|
|
43
43
|
modulePath?: string;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
function extractBalancedBlock(
|
|
47
|
+
source: string,
|
|
48
|
+
startIndex: number,
|
|
49
|
+
openChar: string,
|
|
50
|
+
closeChar: string,
|
|
51
|
+
): string | null {
|
|
52
|
+
let depth = 0;
|
|
53
|
+
let quote: string | null = null;
|
|
54
|
+
let escaped = false;
|
|
55
|
+
|
|
56
|
+
for (let i = startIndex; i < source.length; i++) {
|
|
57
|
+
const ch = source[i];
|
|
58
|
+
|
|
59
|
+
if (quote) {
|
|
60
|
+
if (escaped) {
|
|
61
|
+
escaped = false;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (ch === '\\') {
|
|
65
|
+
escaped = true;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (ch === quote) quote = null;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (ch === '"' || ch === '\'' || ch === '`') {
|
|
73
|
+
quote = ch;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (ch === openChar) {
|
|
78
|
+
depth++;
|
|
79
|
+
} else if (ch === closeChar) {
|
|
80
|
+
depth--;
|
|
81
|
+
if (depth === 0) {
|
|
82
|
+
return source.slice(startIndex + 1, i);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function extractTsArgsBlock(source: string): string | null {
|
|
91
|
+
const argsMatch = source.match(/args\s*:/);
|
|
92
|
+
if (!argsMatch || argsMatch.index === undefined) return null;
|
|
93
|
+
|
|
94
|
+
const bracketIndex = source.indexOf('[', argsMatch.index);
|
|
95
|
+
if (bracketIndex === -1) return null;
|
|
96
|
+
|
|
97
|
+
return extractBalancedBlock(source, bracketIndex, '[', ']');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function parseInlineChoices(body: string): string[] | undefined {
|
|
101
|
+
const choicesMatch = body.match(/choices\s*:\s*\[([^\]]*)\]/);
|
|
102
|
+
if (!choicesMatch) return undefined;
|
|
103
|
+
|
|
104
|
+
const values = choicesMatch[1]
|
|
105
|
+
.split(',')
|
|
106
|
+
.map(s => s.trim().replace(/^['"`]|['"`]$/g, ''))
|
|
107
|
+
.filter(Boolean);
|
|
108
|
+
|
|
109
|
+
return values.length > 0 ? values : undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function parseTsArgsBlock(argsBlock: string): ManifestEntry['args'] {
|
|
113
|
+
const args: ManifestEntry['args'] = [];
|
|
114
|
+
let cursor = 0;
|
|
115
|
+
|
|
116
|
+
while (cursor < argsBlock.length) {
|
|
117
|
+
const nameMatch = argsBlock.slice(cursor).match(/\{\s*name\s*:\s*['"`](\w+)['"`]/);
|
|
118
|
+
if (!nameMatch || nameMatch.index === undefined) break;
|
|
119
|
+
|
|
120
|
+
const objectStart = cursor + nameMatch.index;
|
|
121
|
+
const body = extractBalancedBlock(argsBlock, objectStart, '{', '}');
|
|
122
|
+
if (body == null) break;
|
|
123
|
+
|
|
124
|
+
const typeMatch = body.match(/type\s*:\s*['"`](\w+)['"`]/);
|
|
125
|
+
const defaultMatch = body.match(/default\s*:\s*([^,}]+)/);
|
|
126
|
+
const requiredMatch = body.match(/required\s*:\s*(true|false)/);
|
|
127
|
+
const helpMatch = body.match(/help\s*:\s*['"`]([^'"`]*)['"`]/);
|
|
128
|
+
const positionalMatch = body.match(/positional\s*:\s*(true|false)/);
|
|
129
|
+
|
|
130
|
+
let defaultVal: any = undefined;
|
|
131
|
+
if (defaultMatch) {
|
|
132
|
+
const raw = defaultMatch[1].trim();
|
|
133
|
+
if (raw === 'true') defaultVal = true;
|
|
134
|
+
else if (raw === 'false') defaultVal = false;
|
|
135
|
+
else if (/^\d+$/.test(raw)) defaultVal = parseInt(raw, 10);
|
|
136
|
+
else if (/^\d+\.\d+$/.test(raw)) defaultVal = parseFloat(raw);
|
|
137
|
+
else defaultVal = raw.replace(/^['"`]|['"`]$/g, '');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
args.push({
|
|
141
|
+
name: nameMatch[1],
|
|
142
|
+
type: typeMatch?.[1] ?? 'str',
|
|
143
|
+
default: defaultVal,
|
|
144
|
+
required: requiredMatch?.[1] === 'true',
|
|
145
|
+
positional: positionalMatch?.[1] === 'true' || undefined,
|
|
146
|
+
help: helpMatch?.[1] ?? '',
|
|
147
|
+
choices: parseInlineChoices(body),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
cursor = objectStart + body.length + 2;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return args;
|
|
154
|
+
}
|
|
155
|
+
|
|
46
156
|
function scanYaml(filePath: string, site: string): ManifestEntry | null {
|
|
47
157
|
try {
|
|
48
158
|
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
@@ -129,39 +239,9 @@ function scanTs(filePath: string, site: string): ManifestEntry {
|
|
|
129
239
|
}
|
|
130
240
|
|
|
131
241
|
// Extract args array items: { name: '...', ... }
|
|
132
|
-
const
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
const argRegex = /\{\s*name\s*:\s*['"`](\w+)['"`]([^}]*)\}/g;
|
|
136
|
-
let m;
|
|
137
|
-
while ((m = argRegex.exec(argsBlock)) !== null) {
|
|
138
|
-
const argName = m[1];
|
|
139
|
-
const body = m[2];
|
|
140
|
-
const typeMatch = body.match(/type\s*:\s*['"`](\w+)['"`]/);
|
|
141
|
-
const defaultMatch = body.match(/default\s*:\s*([^,}]+)/);
|
|
142
|
-
const requiredMatch = body.match(/required\s*:\s*(true|false)/);
|
|
143
|
-
const helpMatch = body.match(/help\s*:\s*['"`]([^'"`]*)['"`]/);
|
|
144
|
-
const positionalMatch = body.match(/positional\s*:\s*(true|false)/);
|
|
145
|
-
|
|
146
|
-
let defaultVal: any = undefined;
|
|
147
|
-
if (defaultMatch) {
|
|
148
|
-
const raw = defaultMatch[1].trim();
|
|
149
|
-
if (raw === 'true') defaultVal = true;
|
|
150
|
-
else if (raw === 'false') defaultVal = false;
|
|
151
|
-
else if (/^\d+$/.test(raw)) defaultVal = parseInt(raw, 10);
|
|
152
|
-
else if (/^\d+\.\d+$/.test(raw)) defaultVal = parseFloat(raw);
|
|
153
|
-
else defaultVal = raw.replace(/^['"`]|['"`]$/g, '');
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
entry.args.push({
|
|
157
|
-
name: argName,
|
|
158
|
-
type: typeMatch?.[1] ?? 'str',
|
|
159
|
-
default: defaultVal,
|
|
160
|
-
required: requiredMatch?.[1] === 'true',
|
|
161
|
-
positional: positionalMatch?.[1] === 'true' || undefined,
|
|
162
|
-
help: helpMatch?.[1] ?? '',
|
|
163
|
-
});
|
|
164
|
-
}
|
|
242
|
+
const argsBlock = extractTsArgsBlock(src);
|
|
243
|
+
if (argsBlock) {
|
|
244
|
+
entry.args = parseTsArgsBlock(argsBlock);
|
|
165
245
|
}
|
|
166
246
|
} catch {
|
|
167
247
|
// If parsing fails, fall back to empty metadata — module will self-register at runtime
|
|
@@ -170,32 +250,42 @@ function scanTs(filePath: string, site: string): ManifestEntry {
|
|
|
170
250
|
return entry;
|
|
171
251
|
}
|
|
172
252
|
|
|
173
|
-
|
|
174
|
-
const manifest: ManifestEntry[] = [];
|
|
175
|
-
|
|
176
|
-
if (fs.existsSync(CLIS_DIR)) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
253
|
+
export function buildManifest(): ManifestEntry[] {
|
|
254
|
+
const manifest: ManifestEntry[] = [];
|
|
255
|
+
|
|
256
|
+
if (fs.existsSync(CLIS_DIR)) {
|
|
257
|
+
for (const site of fs.readdirSync(CLIS_DIR)) {
|
|
258
|
+
const siteDir = path.join(CLIS_DIR, site);
|
|
259
|
+
if (!fs.statSync(siteDir).isDirectory()) continue;
|
|
260
|
+
for (const file of fs.readdirSync(siteDir)) {
|
|
261
|
+
const filePath = path.join(siteDir, file);
|
|
262
|
+
if (file.endsWith('.yaml') || file.endsWith('.yml')) {
|
|
263
|
+
const entry = scanYaml(filePath, site);
|
|
264
|
+
if (entry) manifest.push(entry);
|
|
265
|
+
} else if (
|
|
266
|
+
(file.endsWith('.ts') && !file.endsWith('.d.ts') && file !== 'index.ts') ||
|
|
267
|
+
(file.endsWith('.js') && !file.endsWith('.d.js') && file !== 'index.js')
|
|
268
|
+
) {
|
|
269
|
+
manifest.push(scanTs(filePath, site));
|
|
270
|
+
}
|
|
190
271
|
}
|
|
191
272
|
}
|
|
192
273
|
}
|
|
274
|
+
|
|
275
|
+
return manifest;
|
|
193
276
|
}
|
|
194
277
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
fs.
|
|
278
|
+
function main(): void {
|
|
279
|
+
const manifest = buildManifest();
|
|
280
|
+
fs.mkdirSync(path.dirname(OUTPUT), { recursive: true });
|
|
281
|
+
fs.writeFileSync(OUTPUT, JSON.stringify(manifest, null, 2));
|
|
198
282
|
|
|
199
|
-
const yamlCount = manifest.filter(e => e.type === 'yaml').length;
|
|
200
|
-
const tsCount = manifest.filter(e => e.type === 'ts').length;
|
|
201
|
-
console.log(`✅ Manifest compiled: ${manifest.length} entries (${yamlCount} YAML, ${tsCount} TS) → ${OUTPUT}`);
|
|
283
|
+
const yamlCount = manifest.filter(e => e.type === 'yaml').length;
|
|
284
|
+
const tsCount = manifest.filter(e => e.type === 'ts').length;
|
|
285
|
+
console.log(`✅ Manifest compiled: ${manifest.length} entries (${yamlCount} YAML, ${tsCount} TS) → ${OUTPUT}`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const entrypoint = process.argv[1] ? pathToFileURL(path.resolve(process.argv[1])).href : null;
|
|
289
|
+
if (entrypoint === import.meta.url) {
|
|
290
|
+
main();
|
|
291
|
+
}
|
|
@@ -12,9 +12,8 @@ Start the Antigravity desktop app with the Chrome DevTools `remote-debugging-por
|
|
|
12
12
|
|
|
13
13
|
\`\`\`bash
|
|
14
14
|
# Start Antigravity in the background
|
|
15
|
-
/Applications/Antigravity.app/Contents/MacOS/Electron
|
|
16
|
-
--remote-debugging-port=9224
|
|
17
|
-
--remote-allow-origins="*"
|
|
15
|
+
/Applications/Antigravity.app/Contents/MacOS/Electron \
|
|
16
|
+
--remote-debugging-port=9224
|
|
18
17
|
\`\`\`
|
|
19
18
|
|
|
20
19
|
*(Note: Depending on your installation, the executable might be named differently, e.g., \`Antigravity\` instead of \`Electron\`.)*
|
|
@@ -15,9 +15,8 @@ CLI all electron!现在支持把所有 electron 应用 CLI 化,从而组合
|
|
|
15
15
|
|
|
16
16
|
\`\`\`bash
|
|
17
17
|
# 在后台启动并驻留
|
|
18
|
-
/Applications/Antigravity.app/Contents/MacOS/Electron
|
|
19
|
-
--remote-debugging-port=9224
|
|
20
|
-
--remote-allow-origins="*"
|
|
18
|
+
/Applications/Antigravity.app/Contents/MacOS/Electron \
|
|
19
|
+
--remote-debugging-port=9224
|
|
21
20
|
\`\`\`
|
|
22
21
|
|
|
23
22
|
*(注意:如果你打包的应用重命名过主构建,可能需要把 `Electron` 换成实际的可执行文件名,如 `Antigravity`)*
|
|
@@ -9,7 +9,7 @@ This skill allows AI agents to control the [Antigravity](https://github.com/chen
|
|
|
9
9
|
## Requirements
|
|
10
10
|
The target Electron application MUST be launched with the remote-debugging-port flag:
|
|
11
11
|
\`\`\`bash
|
|
12
|
-
/Applications/Antigravity.app/Contents/MacOS/Electron --remote-debugging-port=9224
|
|
12
|
+
/Applications/Antigravity.app/Contents/MacOS/Electron --remote-debugging-port=9224
|
|
13
13
|
\`\`\`
|
|
14
14
|
|
|
15
15
|
The agent must configure the endpoint environment variable locally before invoking standard commands:
|
|
@@ -10,7 +10,7 @@ export const modelCommand = cli({
|
|
|
10
10
|
args: [
|
|
11
11
|
{ name: 'name', help: 'Target model name (e.g. claude, gemini, o1)', required: true, positional: true }
|
|
12
12
|
],
|
|
13
|
-
columns: ['
|
|
13
|
+
columns: ['Status'],
|
|
14
14
|
func: async (page, kwargs) => {
|
|
15
15
|
const targetName = kwargs.name.toLowerCase();
|
|
16
16
|
|
|
@@ -42,6 +42,6 @@ export const modelCommand = cli({
|
|
|
42
42
|
`);
|
|
43
43
|
|
|
44
44
|
await page.wait(0.5);
|
|
45
|
-
return [{
|
|
45
|
+
return [{ Status: `Model switched to: ${kwargs.name}` }];
|
|
46
46
|
},
|
|
47
47
|
});
|
|
@@ -10,7 +10,7 @@ export const sendCommand = cli({
|
|
|
10
10
|
args: [
|
|
11
11
|
{ name: 'message', help: 'The message text to send', required: true, positional: true }
|
|
12
12
|
],
|
|
13
|
-
columns: ['
|
|
13
|
+
columns: ['Status', 'Message'],
|
|
14
14
|
func: async (page, kwargs) => {
|
|
15
15
|
const text = kwargs.message;
|
|
16
16
|
|
|
@@ -35,6 +35,6 @@ export const sendCommand = cli({
|
|
|
35
35
|
// Press Enter to submit the message
|
|
36
36
|
await page.pressKey('Enter');
|
|
37
37
|
|
|
38
|
-
return [{
|
|
38
|
+
return [{ Status: 'Sent successfully', Message: text }];
|
|
39
39
|
},
|
|
40
40
|
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bilibili download — download videos using yt-dlp.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* opencli bilibili download --bvid BV1xxx --output ./bilibili
|
|
6
|
+
*
|
|
7
|
+
* Requirements:
|
|
8
|
+
* - yt-dlp must be installed: pip install yt-dlp
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
import * as path from 'node:path';
|
|
13
|
+
import { cli, Strategy } from '../../registry.js';
|
|
14
|
+
import {
|
|
15
|
+
ytdlpDownload,
|
|
16
|
+
checkYtdlp,
|
|
17
|
+
sanitizeFilename,
|
|
18
|
+
getTempDir,
|
|
19
|
+
exportCookiesToNetscape,
|
|
20
|
+
} from '../../download/index.js';
|
|
21
|
+
import { DownloadProgressTracker, formatBytes } from '../../download/progress.js';
|
|
22
|
+
|
|
23
|
+
cli({
|
|
24
|
+
site: 'bilibili',
|
|
25
|
+
name: 'download',
|
|
26
|
+
description: '下载B站视频(需要 yt-dlp)',
|
|
27
|
+
domain: 'www.bilibili.com',
|
|
28
|
+
strategy: Strategy.COOKIE,
|
|
29
|
+
args: [
|
|
30
|
+
{ name: 'bvid', required: true, help: 'Video BV ID (e.g., BV1xxx)' },
|
|
31
|
+
{ name: 'output', default: './bilibili-downloads', help: 'Output directory' },
|
|
32
|
+
{ name: 'quality', default: 'best', help: 'Video quality (best, 1080p, 720p, 480p)' },
|
|
33
|
+
],
|
|
34
|
+
columns: ['bvid', 'title', 'status', 'size'],
|
|
35
|
+
func: async (page, kwargs) => {
|
|
36
|
+
const bvid = kwargs.bvid;
|
|
37
|
+
const output = kwargs.output;
|
|
38
|
+
const quality = kwargs.quality;
|
|
39
|
+
|
|
40
|
+
// Check yt-dlp availability
|
|
41
|
+
if (!checkYtdlp()) {
|
|
42
|
+
return [{
|
|
43
|
+
bvid,
|
|
44
|
+
title: '-',
|
|
45
|
+
status: 'failed',
|
|
46
|
+
size: 'yt-dlp not installed. Run: pip install yt-dlp',
|
|
47
|
+
}];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Navigate to video page to get title and cookies
|
|
51
|
+
await page.goto(`https://www.bilibili.com/video/${bvid}`);
|
|
52
|
+
await page.wait(3);
|
|
53
|
+
|
|
54
|
+
// Extract video info
|
|
55
|
+
const data = await page.evaluate(`
|
|
56
|
+
(() => {
|
|
57
|
+
const title = document.querySelector('h1.video-title, .video-title')?.textContent?.trim() || 'video';
|
|
58
|
+
const author = document.querySelector('.up-name, .username')?.textContent?.trim() || 'unknown';
|
|
59
|
+
return { title, author };
|
|
60
|
+
})()
|
|
61
|
+
`);
|
|
62
|
+
|
|
63
|
+
const title = sanitizeFilename(data?.title || 'video');
|
|
64
|
+
|
|
65
|
+
// Extract cookies for authenticated downloads
|
|
66
|
+
const cookieString = await page.evaluate(`(() => document.cookie)()`);
|
|
67
|
+
|
|
68
|
+
// Create output directory
|
|
69
|
+
fs.mkdirSync(output, { recursive: true });
|
|
70
|
+
|
|
71
|
+
// Export cookies to Netscape format for yt-dlp
|
|
72
|
+
let cookiesFile: string | undefined;
|
|
73
|
+
if (typeof cookieString === 'string' && cookieString) {
|
|
74
|
+
const tempDir = getTempDir();
|
|
75
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
76
|
+
cookiesFile = path.join(tempDir, `bilibili_cookies_${Date.now()}.txt`);
|
|
77
|
+
|
|
78
|
+
const cookies = cookieString.split(';').map((c) => {
|
|
79
|
+
const [name, ...rest] = c.trim().split('=');
|
|
80
|
+
return {
|
|
81
|
+
name: name || '',
|
|
82
|
+
value: rest.join('=') || '',
|
|
83
|
+
domain: '.bilibili.com',
|
|
84
|
+
path: '/',
|
|
85
|
+
secure: true,
|
|
86
|
+
httpOnly: false,
|
|
87
|
+
};
|
|
88
|
+
}).filter((c) => c.name);
|
|
89
|
+
|
|
90
|
+
exportCookiesToNetscape(cookies, cookiesFile);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Build yt-dlp format string based on quality
|
|
94
|
+
let format = 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best';
|
|
95
|
+
if (quality === '1080p') {
|
|
96
|
+
format = 'bestvideo[height<=1080][ext=mp4]+bestaudio[ext=m4a]/best[height<=1080]';
|
|
97
|
+
} else if (quality === '720p') {
|
|
98
|
+
format = 'bestvideo[height<=720][ext=mp4]+bestaudio[ext=m4a]/best[height<=720]';
|
|
99
|
+
} else if (quality === '480p') {
|
|
100
|
+
format = 'bestvideo[height<=480][ext=mp4]+bestaudio[ext=m4a]/best[height<=480]';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const destPath = path.join(output, `${bvid}_${title}.mp4`);
|
|
104
|
+
|
|
105
|
+
const tracker = new DownloadProgressTracker(1, true);
|
|
106
|
+
const progressBar = tracker.onFileStart(`${bvid}.mp4`, 0);
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const result = await ytdlpDownload(
|
|
110
|
+
`https://www.bilibili.com/video/${bvid}`,
|
|
111
|
+
destPath,
|
|
112
|
+
{
|
|
113
|
+
cookiesFile,
|
|
114
|
+
format,
|
|
115
|
+
extraArgs: [
|
|
116
|
+
'--merge-output-format', 'mp4',
|
|
117
|
+
'--embed-thumbnail',
|
|
118
|
+
],
|
|
119
|
+
onProgress: (percent) => {
|
|
120
|
+
if (progressBar) progressBar.update(percent, 100);
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
if (progressBar) {
|
|
126
|
+
progressBar.complete(result.success, result.success ? formatBytes(result.size) : undefined);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
tracker.onFileComplete(result.success);
|
|
130
|
+
tracker.finish();
|
|
131
|
+
|
|
132
|
+
// Cleanup cookies file
|
|
133
|
+
if (cookiesFile && fs.existsSync(cookiesFile)) {
|
|
134
|
+
fs.unlinkSync(cookiesFile);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return [{
|
|
138
|
+
bvid,
|
|
139
|
+
title: data?.title || 'video',
|
|
140
|
+
status: result.success ? 'success' : 'failed',
|
|
141
|
+
size: result.success ? formatBytes(result.size) : (result.error || 'unknown error'),
|
|
142
|
+
}];
|
|
143
|
+
} catch (err: any) {
|
|
144
|
+
if (progressBar) progressBar.fail(err.message);
|
|
145
|
+
tracker.onFileComplete(false);
|
|
146
|
+
tracker.finish();
|
|
147
|
+
|
|
148
|
+
// Cleanup cookies file
|
|
149
|
+
if (cookiesFile && fs.existsSync(cookiesFile)) {
|
|
150
|
+
fs.unlinkSync(cookiesFile);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return [{
|
|
154
|
+
bvid,
|
|
155
|
+
title: data?.title || 'video',
|
|
156
|
+
status: 'failed',
|
|
157
|
+
size: err.message,
|
|
158
|
+
}];
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
});
|
|
@@ -1,35 +1,44 @@
|
|
|
1
1
|
# ChatGPT Desktop Adapter for OpenCLI
|
|
2
2
|
|
|
3
|
-
Control the **ChatGPT macOS Desktop App** directly from the terminal
|
|
3
|
+
Control the **ChatGPT macOS Desktop App** directly from the terminal. OpenCLI supports two automation approaches for ChatGPT.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Approach 1: AppleScript (Default, No Setup)
|
|
6
6
|
|
|
7
|
+
The current built-in commands use native AppleScript automation — no extra launch flags needed.
|
|
8
|
+
|
|
9
|
+
### Prerequisites
|
|
7
10
|
1. Install the official [ChatGPT Desktop App](https://openai.com/chatgpt/mac/) from OpenAI.
|
|
8
11
|
2. Grant **Accessibility permissions** to your terminal app (Terminal / iTerm / Warp) in **System Settings → Privacy & Security → Accessibility**. This is required for System Events keystroke simulation.
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
No extra environment variables needed — the adapter uses `osascript` directly.
|
|
13
|
-
|
|
14
|
-
## Commands
|
|
15
|
-
|
|
16
|
-
### Diagnostics
|
|
13
|
+
### Commands
|
|
17
14
|
- `opencli chatgpt status`: Check if the ChatGPT app is currently running.
|
|
18
|
-
|
|
19
|
-
### Chat Manipulation
|
|
20
15
|
- `opencli chatgpt new`: Activate ChatGPT and press `Cmd+N` to start a new conversation.
|
|
21
16
|
- `opencli chatgpt send "message"`: Copy your message to clipboard, activate ChatGPT, paste, and submit.
|
|
22
17
|
- `opencli chatgpt read`: Copy the last AI response via `Cmd+Shift+C` and return it as text.
|
|
23
18
|
|
|
19
|
+
## Approach 2: CDP (Advanced, Electron Debug Mode)
|
|
20
|
+
|
|
21
|
+
ChatGPT Desktop is also an Electron app and can be launched with a remote debugging port for deeper automation via CDP:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
/Applications/ChatGPT.app/Contents/MacOS/ChatGPT \
|
|
25
|
+
--remote-debugging-port=9224
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Then set the endpoint:
|
|
29
|
+
```bash
|
|
30
|
+
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9224"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
> **Note**: The CDP approach enables future advanced commands like DOM inspection, model switching, and code extraction — similar to the Cursor and Codex adapters.
|
|
34
|
+
|
|
24
35
|
## How It Works
|
|
25
36
|
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
- Leverages `pbcopy`/`pbpaste` for clipboard-based text transfer
|
|
29
|
-
- Requires no remote debugging port — works with the stock app
|
|
37
|
+
- **AppleScript mode**: Uses `osascript` and `pbcopy`/`pbpaste` for clipboard-based text transfer. No remote debugging port needed.
|
|
38
|
+
- **CDP mode**: Connects via Playwright to the Electron renderer process for direct DOM manipulation.
|
|
30
39
|
|
|
31
40
|
## Limitations
|
|
32
41
|
|
|
33
42
|
- macOS only (AppleScript dependency)
|
|
34
|
-
-
|
|
43
|
+
- AppleScript mode requires Accessibility permissions
|
|
35
44
|
- `read` command copies the last response — earlier messages need manual scroll
|
|
@@ -1,35 +1,44 @@
|
|
|
1
1
|
# ChatGPT 桌面端适配器
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
在终端中直接控制 **ChatGPT macOS 桌面应用**。OpenCLI 支持两种自动化方式。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 方式一:AppleScript(默认,无需配置)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
2. 在 **系统设置 → 隐私与安全性 → 辅助功能** 中为终端应用(Terminal / iTerm / Warp)授予 **辅助功能权限**。这是 System Events 按键模拟所必需的。
|
|
9
|
-
|
|
10
|
-
## 配置
|
|
11
|
-
|
|
12
|
-
无需额外环境变量 — 适配器直接使用 `osascript`。
|
|
7
|
+
内置命令使用原生 AppleScript 自动化,无需额外启动参数。
|
|
13
8
|
|
|
14
|
-
|
|
9
|
+
### 前置条件
|
|
10
|
+
1. 安装官方 [ChatGPT Desktop App](https://openai.com/chatgpt/mac/)。
|
|
11
|
+
2. 在 **系统设置 → 隐私与安全性 → 辅助功能** 中为终端应用授予权限。
|
|
15
12
|
|
|
16
|
-
###
|
|
13
|
+
### 命令
|
|
17
14
|
- `opencli chatgpt status`:检查 ChatGPT 应用是否在运行。
|
|
18
|
-
|
|
19
|
-
### 对话操作
|
|
20
15
|
- `opencli chatgpt new`:激活 ChatGPT 并按 `Cmd+N` 开始新对话。
|
|
21
16
|
- `opencli chatgpt send "消息"`:将消息复制到剪贴板,激活 ChatGPT,粘贴并提交。
|
|
22
17
|
- `opencli chatgpt read`:通过 `Cmd+Shift+C` 复制最后一条 AI 回复并返回文本。
|
|
23
18
|
|
|
19
|
+
## 方式二:CDP(高级,Electron 调试模式)
|
|
20
|
+
|
|
21
|
+
ChatGPT Desktop 同样是 Electron 应用,可以通过远程调试端口启动以实现更深度的自动化:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
/Applications/ChatGPT.app/Contents/MacOS/ChatGPT \
|
|
25
|
+
--remote-debugging-port=9224
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
然后设置环境变量:
|
|
29
|
+
```bash
|
|
30
|
+
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9224"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
> **注意**:CDP 模式支持未来的高级命令(如 DOM 检查、模型切换、代码提取等),与 Cursor 和 Codex 适配器类似。
|
|
34
|
+
|
|
24
35
|
## 工作原理
|
|
25
36
|
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
- 利用 `pbcopy`/`pbpaste` 进行基于剪贴板的文本传输
|
|
29
|
-
- 无需远程调试端口 — 直接与原生应用交互
|
|
37
|
+
- **AppleScript 模式**:使用 `osascript` 和 `pbcopy`/`pbpaste` 进行剪贴板文本传输,无需远程调试端口。
|
|
38
|
+
- **CDP 模式**:通过 Playwright 连接到 Electron 渲染进程,直接操作 DOM。
|
|
30
39
|
|
|
31
40
|
## 限制
|
|
32
41
|
|
|
33
42
|
- 仅支持 macOS(AppleScript 依赖)
|
|
34
|
-
-
|
|
35
|
-
- `read`
|
|
43
|
+
- AppleScript 模式需要辅助功能权限
|
|
44
|
+
- `read` 命令复制最后一条回复,更早的消息需手动滚动
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { execSync, spawnSync } from 'node:child_process';
|
|
2
|
+
import { cli, Strategy } from '../../registry.js';
|
|
3
|
+
import type { IPage } from '../../types.js';
|
|
4
|
+
|
|
5
|
+
export const askCommand = cli({
|
|
6
|
+
site: 'chatgpt',
|
|
7
|
+
name: 'ask',
|
|
8
|
+
description: 'Send a prompt and wait for the AI response (send + wait + read)',
|
|
9
|
+
domain: 'localhost',
|
|
10
|
+
strategy: Strategy.PUBLIC,
|
|
11
|
+
browser: false,
|
|
12
|
+
args: [
|
|
13
|
+
{ name: 'text', required: true, positional: true, help: 'Prompt to send' },
|
|
14
|
+
{ name: 'timeout', required: false, help: 'Max seconds to wait for response (default: 30)', default: '30' },
|
|
15
|
+
],
|
|
16
|
+
columns: ['Role', 'Text'],
|
|
17
|
+
func: async (page: IPage | null, kwargs: any) => {
|
|
18
|
+
const text = kwargs.text as string;
|
|
19
|
+
const timeout = parseInt(kwargs.timeout as string, 10) || 30;
|
|
20
|
+
|
|
21
|
+
// Backup clipboard
|
|
22
|
+
let clipBackup = '';
|
|
23
|
+
try { clipBackup = execSync('pbpaste', { encoding: 'utf-8' }); } catch {}
|
|
24
|
+
|
|
25
|
+
// Send the message
|
|
26
|
+
spawnSync('pbcopy', { input: text });
|
|
27
|
+
execSync("osascript -e 'tell application \"ChatGPT\" to activate'");
|
|
28
|
+
execSync("osascript -e 'delay 0.5'");
|
|
29
|
+
|
|
30
|
+
const cmd = "osascript " +
|
|
31
|
+
"-e 'tell application \"System Events\"' " +
|
|
32
|
+
"-e 'keystroke \"v\" using command down' " +
|
|
33
|
+
"-e 'delay 0.2' " +
|
|
34
|
+
"-e 'keystroke return' " +
|
|
35
|
+
"-e 'end tell'";
|
|
36
|
+
execSync(cmd);
|
|
37
|
+
|
|
38
|
+
// Clear clipboard marker
|
|
39
|
+
spawnSync('pbcopy', { input: '__OPENCLI_WAITING__' });
|
|
40
|
+
|
|
41
|
+
// Wait for response, then read it
|
|
42
|
+
const pollInterval = 3;
|
|
43
|
+
const maxPolls = Math.ceil(timeout / pollInterval);
|
|
44
|
+
let response = '';
|
|
45
|
+
|
|
46
|
+
for (let i = 0; i < maxPolls; i++) {
|
|
47
|
+
// Wait
|
|
48
|
+
execSync(`sleep ${pollInterval}`);
|
|
49
|
+
|
|
50
|
+
// Try Cmd+Shift+C to copy the latest response
|
|
51
|
+
execSync("osascript -e 'tell application \"ChatGPT\" to activate'");
|
|
52
|
+
execSync("osascript -e 'tell application \"System Events\" to keystroke \"c\" using {command down, shift down}'");
|
|
53
|
+
execSync("osascript -e 'delay 0.3'");
|
|
54
|
+
|
|
55
|
+
const copied = execSync('pbpaste', { encoding: 'utf-8' }).trim();
|
|
56
|
+
if (copied && copied !== '__OPENCLI_WAITING__' && copied !== text) {
|
|
57
|
+
response = copied;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Restore clipboard
|
|
63
|
+
if (clipBackup) spawnSync('pbcopy', { input: clipBackup });
|
|
64
|
+
|
|
65
|
+
if (!response) {
|
|
66
|
+
return [
|
|
67
|
+
{ Role: 'User', Text: text },
|
|
68
|
+
{ Role: 'System', Text: `No response within ${timeout}s. ChatGPT may still be generating.` },
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return [
|
|
73
|
+
{ Role: 'User', Text: text },
|
|
74
|
+
{ Role: 'Assistant', Text: response },
|
|
75
|
+
];
|
|
76
|
+
},
|
|
77
|
+
});
|