@jackwener/opencli 1.7.8 → 1.7.10
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 +49 -14
- package/README.zh-CN.md +30 -10
- package/cli-manifest.json +646 -30
- package/clis/36kr/news.js +1 -1
- package/clis/apple-podcasts/commands.test.js +4 -4
- package/clis/apple-podcasts/episodes.js +1 -1
- package/clis/apple-podcasts/search.js +1 -1
- package/clis/apple-podcasts/top.js +1 -1
- package/clis/arxiv/paper.js +1 -1
- package/clis/arxiv/search.js +1 -1
- package/clis/band/mentions.js +3 -3
- package/clis/bbc/news.js +1 -1
- package/clis/bilibili/subtitle.js +2 -2
- package/clis/bloomberg/businessweek.js +1 -1
- package/clis/bloomberg/economics.js +1 -1
- package/clis/bloomberg/industries.js +1 -1
- package/clis/bloomberg/main.js +1 -1
- package/clis/bloomberg/markets.js +1 -1
- package/clis/bloomberg/opinions.js +1 -1
- package/clis/bloomberg/politics.js +1 -1
- package/clis/bloomberg/tech.js +1 -1
- package/clis/boss/search.js +49 -8
- package/clis/boss/search.test.js +78 -0
- package/clis/boss/send.js +3 -3
- package/clis/chatgpt/image.js +37 -8
- package/clis/chatgpt/image.test.js +92 -0
- package/clis/chatgpt/utils.js +39 -6
- package/clis/chatgpt/utils.test.js +63 -0
- package/clis/chatgpt-app/ask.js +1 -1
- package/clis/chatgpt-app/ax.js +4 -2
- package/clis/chatgpt-app/ax.test.js +12 -0
- package/clis/chatgpt-app/model.js +1 -1
- package/clis/chatgpt-app/new.js +1 -1
- package/clis/chatgpt-app/read.js +1 -1
- package/clis/chatgpt-app/send.js +1 -1
- package/clis/chatgpt-app/status.js +1 -1
- package/clis/chatwise/ask.js +2 -2
- package/clis/chatwise/model.js +2 -2
- package/clis/chatwise/send.js +2 -2
- package/clis/claude/ask.js +128 -0
- package/clis/claude/ask.test.js +338 -0
- package/clis/claude/commands.test.js +118 -0
- package/clis/claude/detail.js +29 -0
- package/clis/claude/history.js +31 -0
- package/clis/claude/new.js +21 -0
- package/clis/claude/read.js +24 -0
- package/clis/claude/send.js +41 -0
- package/clis/claude/status.js +24 -0
- package/clis/claude/utils.js +440 -0
- package/clis/claude/utils.test.js +148 -0
- package/clis/codex/ask.js +2 -2
- package/clis/codex/send.js +2 -2
- package/clis/ctrip/search.js +1 -1
- package/clis/ctrip/search.test.js +4 -4
- package/clis/cursor/ask.js +2 -2
- package/clis/cursor/composer.js +2 -2
- package/clis/cursor/send.js +2 -2
- package/clis/deepseek/ask.js +17 -4
- package/clis/deepseek/ask.test.js +46 -0
- package/clis/deepseek/utils.js +55 -16
- package/clis/deepseek/utils.test.js +124 -5
- package/clis/doubao/utils.js +53 -11
- package/clis/doubao/utils.test.js +22 -2
- package/clis/eastmoney/announcement.js +1 -1
- package/clis/eastmoney/convertible.js +1 -1
- package/clis/eastmoney/etf.js +1 -1
- package/clis/eastmoney/holders.js +1 -1
- package/clis/eastmoney/index-board.js +1 -1
- package/clis/eastmoney/kline.js +1 -1
- package/clis/eastmoney/kuaixun.js +1 -1
- package/clis/eastmoney/longhu.js +1 -1
- package/clis/eastmoney/money-flow.js +1 -1
- package/clis/eastmoney/northbound.js +1 -1
- package/clis/eastmoney/quote.js +1 -1
- package/clis/eastmoney/rank.js +1 -1
- package/clis/eastmoney/sectors.js +1 -1
- package/clis/facebook/marketplace-inbox.js +83 -0
- package/clis/facebook/marketplace-listings.js +83 -0
- package/clis/facebook/marketplace.test.js +91 -0
- package/clis/google/news.js +1 -1
- package/clis/google/suggest.js +1 -1
- package/clis/google/trends.js +1 -1
- package/clis/google-scholar/cite.js +74 -0
- package/clis/google-scholar/cite.test.js +47 -0
- package/clis/google-scholar/profile.js +92 -0
- package/clis/google-scholar/profile.test.js +49 -0
- package/clis/google-scholar/search.js +1 -1
- package/clis/google-scholar/search.test.js +15 -0
- package/clis/hf/top.js +1 -1
- package/clis/instagram/collection-create.js +57 -0
- package/clis/instagram/saved.js +21 -7
- package/clis/jd/item.js +679 -47
- package/clis/jd/item.test.js +318 -7
- package/clis/jd/item.test.ts +517 -0
- package/clis/lesswrong/comments.js +1 -1
- package/clis/lesswrong/curated.js +1 -1
- package/clis/lesswrong/frontpage.js +1 -1
- package/clis/lesswrong/new.js +1 -1
- package/clis/lesswrong/read.js +1 -1
- package/clis/lesswrong/sequences.js +1 -1
- package/clis/lesswrong/shortform.js +1 -1
- package/clis/lesswrong/tag.js +1 -1
- package/clis/lesswrong/tags.js +1 -1
- package/clis/lesswrong/top-month.js +1 -1
- package/clis/lesswrong/top-week.js +1 -1
- package/clis/lesswrong/top-year.js +1 -1
- package/clis/lesswrong/top.js +1 -1
- package/clis/lesswrong/user-posts.js +1 -1
- package/clis/lesswrong/user.js +1 -1
- package/clis/paperreview/commands.test.js +6 -6
- package/clis/paperreview/feedback.js +1 -1
- package/clis/paperreview/review.js +1 -1
- package/clis/paperreview/submit.js +1 -1
- package/clis/producthunt/posts.js +1 -1
- package/clis/producthunt/today.js +1 -1
- package/clis/sinablog/search.js +1 -1
- package/clis/sinafinance/news.js +1 -1
- package/clis/sinafinance/stock.js +1 -1
- package/clis/sinafinance/stock.test.js +2 -2
- package/clis/spotify/spotify.js +6 -6
- package/clis/substack/search.js +1 -1
- package/clis/toutiao/articles.js +5 -6
- package/clis/toutiao/articles.test.js +22 -15
- package/clis/twitter/followers.js +2 -2
- package/clis/twitter/following.js +224 -73
- package/clis/twitter/following.test.js +277 -0
- package/clis/twitter/post.js +184 -47
- package/clis/twitter/post.test.js +114 -34
- package/clis/uiverse/_shared.js +63 -4
- package/clis/uiverse/_shared.test.js +7 -0
- package/clis/uiverse/code.js +1 -0
- package/clis/uiverse/navigation.test.js +12 -0
- package/clis/uiverse/preview.js +1 -0
- package/clis/web/read.js +319 -81
- package/clis/web/read.test.js +221 -5
- package/clis/weibo/favorites.js +169 -0
- package/clis/weibo/favorites.test.js +114 -0
- package/clis/weibo/publish.js +282 -0
- package/clis/weibo/publish.test.js +183 -0
- package/clis/weread/ranking.js +1 -1
- package/clis/weread/search-regression.test.js +8 -8
- package/clis/weread/search.js +1 -1
- package/clis/wikipedia/random.js +1 -1
- package/clis/wikipedia/search.js +1 -1
- package/clis/wikipedia/summary.js +1 -1
- package/clis/wikipedia/trending.js +1 -1
- package/clis/xianyu/chat.js +3 -3
- package/clis/xianyu/item.js +2 -2
- package/clis/xianyu/item.test.js +3 -3
- package/clis/xiaohongshu/search.js +17 -2
- package/clis/xiaohongshu/search.test.js +37 -1
- package/clis/xiaoyuzhou/download.js +1 -1
- package/clis/xiaoyuzhou/download.test.js +3 -3
- package/clis/xiaoyuzhou/episode.js +1 -1
- package/clis/xiaoyuzhou/podcast-episodes.js +1 -1
- package/clis/xiaoyuzhou/podcast-episodes.test.js +2 -2
- package/clis/xiaoyuzhou/podcast.js +1 -1
- package/clis/xiaoyuzhou/transcript.js +1 -1
- package/clis/xiaoyuzhou/transcript.test.js +5 -5
- package/clis/yollomi/models.js +1 -1
- package/clis/youtube/channel.js +24 -1
- package/clis/youtube/channel.test.js +59 -0
- package/clis/zhihu/answer.js +21 -162
- package/clis/zhihu/answer.test.js +26 -53
- package/clis/zhihu/collection.js +197 -0
- package/clis/zhihu/collection.test.js +290 -0
- package/clis/zhihu/collections.js +127 -0
- package/clis/zhihu/collections.test.js +182 -0
- package/clis/zhihu/comment.js +24 -305
- package/clis/zhihu/comment.test.js +31 -35
- package/clis/zhihu/favorite.js +44 -182
- package/clis/zhihu/favorite.test.js +30 -167
- package/clis/zhihu/follow.js +25 -56
- package/clis/zhihu/follow.test.js +20 -23
- package/clis/zhihu/like.js +22 -67
- package/clis/zhihu/like.test.js +19 -42
- package/clis/zhihu/search.js +3 -2
- package/clis/zhihu/write-shared.js +8 -1
- package/clis/zhihu/write-shared.test.js +1 -0
- package/clis/zlibrary/commands.test.js +75 -0
- package/clis/zlibrary/info.js +47 -0
- package/clis/zlibrary/search.js +46 -0
- package/clis/zlibrary/utils.js +136 -0
- package/dist/src/adapter-source.d.ts +11 -0
- package/dist/src/adapter-source.js +24 -0
- package/dist/src/adapter-source.test.js +29 -0
- package/dist/src/browser/base-page.d.ts +3 -1
- package/dist/src/browser/base-page.js +76 -1
- package/dist/src/browser/base-page.test.d.ts +1 -0
- package/dist/src/browser/base-page.test.js +74 -0
- package/dist/src/browser/bridge.d.ts +1 -2
- package/dist/src/browser/bridge.js +40 -41
- package/dist/src/browser/cdp.d.ts +1 -0
- package/dist/src/browser/cdp.js +3 -3
- package/dist/src/browser/daemon-client.d.ts +38 -4
- package/dist/src/browser/daemon-client.js +24 -7
- package/dist/src/browser/daemon-client.test.js +49 -0
- package/dist/src/browser/daemon-lifecycle.d.ts +23 -0
- package/dist/src/browser/daemon-lifecycle.js +67 -0
- package/dist/src/browser/daemon-version.d.ts +4 -0
- package/dist/src/browser/daemon-version.js +12 -0
- package/dist/src/browser/errors.js +3 -0
- package/dist/src/browser/errors.test.js +3 -0
- package/dist/src/browser/network-cache.d.ts +1 -0
- package/dist/src/browser/page.d.ts +3 -1
- package/dist/src/browser/page.js +10 -2
- package/dist/src/browser/profile.d.ts +14 -0
- package/dist/src/browser/profile.js +85 -0
- package/dist/src/build-manifest.d.ts +2 -0
- package/dist/src/build-manifest.js +13 -3
- package/dist/src/build-manifest.test.js +20 -2
- package/dist/src/cli.d.ts +6 -0
- package/dist/src/cli.js +477 -35
- package/dist/src/cli.test.js +303 -2
- package/dist/src/commanderAdapter.js +17 -9
- package/dist/src/commanderAdapter.test.js +67 -2
- package/dist/src/commands/daemon.d.ts +2 -0
- package/dist/src/commands/daemon.js +42 -1
- package/dist/src/commands/daemon.test.js +103 -2
- package/dist/src/completion-shared.js +1 -2
- package/dist/src/completion.test.js +3 -2
- package/dist/src/daemon.js +125 -41
- package/dist/src/doctor.d.ts +5 -6
- package/dist/src/doctor.js +77 -19
- package/dist/src/doctor.test.js +117 -0
- package/dist/src/engine.test.js +6 -5
- package/dist/src/errors.d.ts +14 -8
- package/dist/src/errors.js +36 -30
- package/dist/src/errors.test.js +5 -5
- package/dist/src/execution.d.ts +4 -0
- package/dist/src/execution.js +173 -25
- package/dist/src/execution.test.js +171 -1
- package/dist/src/main.js +10 -0
- package/dist/src/observation/artifact.d.ts +16 -0
- package/dist/src/observation/artifact.js +260 -0
- package/dist/src/observation/artifact.test.d.ts +1 -0
- package/dist/src/observation/artifact.test.js +121 -0
- package/dist/src/observation/events.d.ts +89 -0
- package/dist/src/observation/events.js +1 -0
- package/dist/src/observation/index.d.ts +7 -0
- package/dist/src/observation/index.js +7 -0
- package/dist/src/observation/manager.d.ts +9 -0
- package/dist/src/observation/manager.js +27 -0
- package/dist/src/observation/manager.test.d.ts +1 -0
- package/dist/src/observation/manager.test.js +13 -0
- package/dist/src/observation/redaction.d.ts +11 -0
- package/dist/src/observation/redaction.js +81 -0
- package/dist/src/observation/redaction.test.d.ts +1 -0
- package/dist/src/observation/redaction.test.js +32 -0
- package/dist/src/observation/retention.d.ts +32 -0
- package/dist/src/observation/retention.js +160 -0
- package/dist/src/observation/retention.test.d.ts +1 -0
- package/dist/src/observation/retention.test.js +118 -0
- package/dist/src/observation/ring-buffer.d.ts +22 -0
- package/dist/src/observation/ring-buffer.js +45 -0
- package/dist/src/observation/ring-buffer.test.d.ts +1 -0
- package/dist/src/observation/ring-buffer.test.js +22 -0
- package/dist/src/observation/session.d.ts +25 -0
- package/dist/src/observation/session.js +50 -0
- package/dist/src/pipeline/executor.test.js +1 -0
- package/dist/src/pipeline/steps/download.test.js +1 -0
- package/dist/src/pipeline/steps/fetch.js +1 -21
- package/dist/src/pipeline/steps/fetch.test.js +6 -12
- package/dist/src/plugin-scaffold.js +1 -1
- package/dist/src/plugin-scaffold.test.js +1 -1
- package/dist/src/registry.d.ts +40 -9
- package/dist/src/registry.js +3 -1
- package/dist/src/runtime-detect.d.ts +10 -0
- package/dist/src/runtime-detect.js +19 -0
- package/dist/src/runtime-detect.test.js +12 -1
- package/dist/src/runtime.d.ts +2 -0
- package/dist/src/runtime.js +1 -0
- package/dist/src/types.d.ts +22 -0
- package/dist/src/update-check.d.ts +31 -1
- package/dist/src/update-check.js +62 -16
- package/dist/src/update-check.test.js +86 -1
- package/package.json +1 -1
- package/dist/src/diagnostic.d.ts +0 -63
- package/dist/src/diagnostic.js +0 -292
- package/dist/src/diagnostic.test.js +0 -302
- /package/dist/src/{diagnostic.test.d.ts → adapter-source.test.d.ts} +0 -0
package/clis/chatgpt/utils.js
CHANGED
|
@@ -7,11 +7,21 @@ export const CHATGPT_DOMAIN = 'chatgpt.com';
|
|
|
7
7
|
export const CHATGPT_URL = 'https://chatgpt.com';
|
|
8
8
|
|
|
9
9
|
// Selectors
|
|
10
|
-
const
|
|
10
|
+
const COMPOSER_SELECTORS = [
|
|
11
|
+
'[aria-label="Chat with ChatGPT"]',
|
|
12
|
+
'[placeholder="Ask anything"]',
|
|
13
|
+
'#prompt-textarea',
|
|
14
|
+
];
|
|
11
15
|
const SEND_BTN_SELECTOR = 'button[aria-label="Send prompt"]';
|
|
12
16
|
|
|
17
|
+
function isSameChatGPTConversation(currentUrl, expectedUrl) {
|
|
18
|
+
if (!currentUrl || !expectedUrl) return false;
|
|
19
|
+
return currentUrl === expectedUrl
|
|
20
|
+
|| currentUrl.startsWith(`${expectedUrl}?`)
|
|
21
|
+
|| currentUrl.startsWith(`${expectedUrl}#`);
|
|
22
|
+
}
|
|
23
|
+
|
|
13
24
|
function buildComposerLocatorScript() {
|
|
14
|
-
const selectorsJson = JSON.stringify([COMPOSER_SELECTOR]);
|
|
15
25
|
const markerAttr = 'data-opencli-chatgpt-composer';
|
|
16
26
|
return `
|
|
17
27
|
const isVisible = (el) => {
|
|
@@ -33,7 +43,7 @@ function buildComposerLocatorScript() {
|
|
|
33
43
|
const marked = document.querySelector('[' + markerAttr + '="1"]');
|
|
34
44
|
if (marked instanceof HTMLElement && isVisible(marked)) return marked;
|
|
35
45
|
|
|
36
|
-
for (const selector of ${JSON.stringify(
|
|
46
|
+
for (const selector of ${JSON.stringify(COMPOSER_SELECTORS)}) {
|
|
37
47
|
const node = Array.from(document.querySelectorAll(selector)).find(c => c instanceof HTMLElement && isVisible(c));
|
|
38
48
|
if (node instanceof HTMLElement) {
|
|
39
49
|
node.setAttribute(markerAttr, '1');
|
|
@@ -89,7 +99,9 @@ export async function sendChatGPTMessage(page, text) {
|
|
|
89
99
|
// Fallback: use execCommand
|
|
90
100
|
await page.evaluate(`
|
|
91
101
|
(() => {
|
|
92
|
-
|
|
102
|
+
var composer = null;
|
|
103
|
+
var sels = ${JSON.stringify(COMPOSER_SELECTORS)};
|
|
104
|
+
for (var si = 0; si < sels.length; si++) { composer = document.querySelector(sels[si]); if (composer) break; }
|
|
93
105
|
if (!composer) return;
|
|
94
106
|
composer.focus();
|
|
95
107
|
document.execCommand('insertText', false, ${JSON.stringify(text)});
|
|
@@ -181,7 +193,7 @@ export async function getChatGPTVisibleImageUrls(page) {
|
|
|
181
193
|
/**
|
|
182
194
|
* Wait for new images to appear after sending a prompt.
|
|
183
195
|
*/
|
|
184
|
-
export async function waitForChatGPTImages(page, beforeUrls, timeoutSeconds) {
|
|
196
|
+
export async function waitForChatGPTImages(page, beforeUrls, timeoutSeconds, convUrl) {
|
|
185
197
|
const beforeSet = new Set(beforeUrls);
|
|
186
198
|
const pollIntervalSeconds = 3;
|
|
187
199
|
const maxPolls = Math.max(1, Math.ceil(timeoutSeconds / pollIntervalSeconds));
|
|
@@ -191,10 +203,26 @@ export async function waitForChatGPTImages(page, beforeUrls, timeoutSeconds) {
|
|
|
191
203
|
for (let i = 0; i < maxPolls; i++) {
|
|
192
204
|
await page.wait(i === 0 ? 3 : pollIntervalSeconds);
|
|
193
205
|
|
|
194
|
-
|
|
206
|
+
let currentUrl = '';
|
|
207
|
+
if (convUrl && convUrl.includes('/c/')) {
|
|
208
|
+
currentUrl = await page.evaluate('window.location.href').catch(() => '');
|
|
209
|
+
if (currentUrl && !isSameChatGPTConversation(currentUrl, convUrl)) {
|
|
210
|
+
await page.goto(convUrl);
|
|
211
|
+
await page.wait(3);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
195
215
|
const generating = await isGenerating(page);
|
|
196
216
|
if (generating) continue;
|
|
197
217
|
|
|
218
|
+
if (convUrl && convUrl.includes('/c/') && i > 0 && i % 5 === 0) {
|
|
219
|
+
const onConversation = !currentUrl || isSameChatGPTConversation(currentUrl, convUrl);
|
|
220
|
+
if (onConversation) {
|
|
221
|
+
await page.goto(convUrl);
|
|
222
|
+
await page.wait(3);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
198
226
|
const urls = (await getChatGPTVisibleImageUrls(page)).filter(url => !beforeSet.has(url));
|
|
199
227
|
if (urls.length === 0) continue;
|
|
200
228
|
|
|
@@ -214,6 +242,11 @@ export async function waitForChatGPTImages(page, beforeUrls, timeoutSeconds) {
|
|
|
214
242
|
return lastUrls;
|
|
215
243
|
}
|
|
216
244
|
|
|
245
|
+
export const __test__ = {
|
|
246
|
+
COMPOSER_SELECTORS,
|
|
247
|
+
isSameChatGPTConversation,
|
|
248
|
+
};
|
|
249
|
+
|
|
217
250
|
/**
|
|
218
251
|
* Export images by URL: fetch from ChatGPT backend API and convert to base64 data URLs.
|
|
219
252
|
*/
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { __test__, waitForChatGPTImages } from './utils.js';
|
|
3
|
+
|
|
4
|
+
function createPageMock({ location = '', generating = [], imageUrls = [] } = {}) {
|
|
5
|
+
let generatingIndex = 0;
|
|
6
|
+
let imageIndex = 0;
|
|
7
|
+
return {
|
|
8
|
+
wait: vi.fn().mockResolvedValue(undefined),
|
|
9
|
+
goto: vi.fn().mockResolvedValue(undefined),
|
|
10
|
+
evaluate: vi.fn((script) => {
|
|
11
|
+
if (script === 'window.location.href') return Promise.resolve(location);
|
|
12
|
+
if (script.includes('Stop generating') || script.includes('Thinking')) {
|
|
13
|
+
const value = generating[Math.min(generatingIndex, generating.length - 1)] ?? false;
|
|
14
|
+
generatingIndex += 1;
|
|
15
|
+
return Promise.resolve(value);
|
|
16
|
+
}
|
|
17
|
+
if (script.includes("document.querySelectorAll('img')")) {
|
|
18
|
+
const value = imageUrls[Math.min(imageIndex, imageUrls.length - 1)] ?? [];
|
|
19
|
+
imageIndex += 1;
|
|
20
|
+
return Promise.resolve(value);
|
|
21
|
+
}
|
|
22
|
+
return Promise.resolve(undefined);
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('chatgpt image wait contract', () => {
|
|
28
|
+
it('does not periodically reload the conversation while generation is still active', async () => {
|
|
29
|
+
const convUrl = 'https://chatgpt.com/c/demo';
|
|
30
|
+
const page = createPageMock({
|
|
31
|
+
location: convUrl,
|
|
32
|
+
generating: [true, true, true, true, true, true],
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await expect(waitForChatGPTImages(page, [], 18, convUrl)).resolves.toEqual([]);
|
|
36
|
+
expect(page.goto).not.toHaveBeenCalled();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('jumps back to the captured conversation when the page drifts away', async () => {
|
|
40
|
+
const convUrl = 'https://chatgpt.com/c/demo';
|
|
41
|
+
const page = createPageMock({
|
|
42
|
+
location: 'https://chatgpt.com/',
|
|
43
|
+
generating: [false],
|
|
44
|
+
imageUrls: [['https://cdn.openai.com/generated/demo.png']],
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await expect(waitForChatGPTImages(page, [], 3, convUrl)).resolves.toEqual([
|
|
48
|
+
'https://cdn.openai.com/generated/demo.png',
|
|
49
|
+
]);
|
|
50
|
+
expect(page.goto).toHaveBeenCalledWith(convUrl);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('treats query and hash variants as the same conversation', () => {
|
|
54
|
+
expect(__test__.isSameChatGPTConversation(
|
|
55
|
+
'https://chatgpt.com/c/demo?model=gpt-image-1',
|
|
56
|
+
'https://chatgpt.com/c/demo',
|
|
57
|
+
)).toBe(true);
|
|
58
|
+
expect(__test__.isSameChatGPTConversation(
|
|
59
|
+
'https://chatgpt.com/c/other',
|
|
60
|
+
'https://chatgpt.com/c/demo',
|
|
61
|
+
)).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
});
|
package/clis/chatgpt-app/ask.js
CHANGED
|
@@ -15,7 +15,7 @@ export const askCommand = cli({
|
|
|
15
15
|
{ name: 'timeout', required: false, help: 'Max seconds to wait for response (default: 30)', default: '30' },
|
|
16
16
|
],
|
|
17
17
|
columns: ['Role', 'Text'],
|
|
18
|
-
func: async (
|
|
18
|
+
func: async (kwargs) => {
|
|
19
19
|
if (process.platform !== 'darwin') {
|
|
20
20
|
throw new ConfigError('ChatGPT Desktop integration requires macOS (osascript is not available on this platform)');
|
|
21
21
|
}
|
package/clis/chatgpt-app/ax.js
CHANGED
|
@@ -156,7 +156,7 @@ guard s(input, kAXValueAttribute as String) == text else {
|
|
|
156
156
|
exit(1)
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
guard let sendButton = findByDescriptions(win, ["发送", "Send"]) else {
|
|
159
|
+
guard let sendButton = findByDescriptions(win, ["发送", "傳送", "Send"]) else {
|
|
160
160
|
fputs("Could not find send button\\n", stderr)
|
|
161
161
|
exit(1)
|
|
162
162
|
}
|
|
@@ -240,10 +240,11 @@ let args = CommandLine.arguments
|
|
|
240
240
|
let target = args.count > 1 ? args[1] : ""
|
|
241
241
|
let needsLegacy = args.count > 2 && args[2] == "legacy"
|
|
242
242
|
|
|
243
|
-
// Step 1: Click the "Options" button to open the popover (support
|
|
243
|
+
// Step 1: Click the "Options" button to open the popover (support English, Simplified and Traditional Chinese UI)
|
|
244
244
|
var optionsBtn: AXUIElement? = nil
|
|
245
245
|
if let btn = findByDesc(win, "Options") { optionsBtn = btn }
|
|
246
246
|
else if let btn = findByDesc(win, "选项") { optionsBtn = btn }
|
|
247
|
+
else if let btn = findByDesc(win, "選項") { optionsBtn = btn }
|
|
247
248
|
guard let options = optionsBtn else {
|
|
248
249
|
fputs("Could not find Options button\\n", stderr); exit(1)
|
|
249
250
|
}
|
|
@@ -379,5 +380,6 @@ export function getVisibleChatMessages() {
|
|
|
379
380
|
}
|
|
380
381
|
export const __test__ = {
|
|
381
382
|
AX_SEND_SCRIPT,
|
|
383
|
+
AX_MODEL_SCRIPT,
|
|
382
384
|
AX_GENERATING_SCRIPT,
|
|
383
385
|
};
|
|
@@ -13,6 +13,18 @@ describe('chatgpt-app AX send script', () => {
|
|
|
13
13
|
it('does not report success until the prompt leaves the composer after send', () => {
|
|
14
14
|
expect(__test__.AX_SEND_SCRIPT).toContain('Prompt did not leave input after pressing send');
|
|
15
15
|
});
|
|
16
|
+
|
|
17
|
+
it('supports english, zh-CN, and zh-TW send button labels', () => {
|
|
18
|
+
expect(__test__.AX_SEND_SCRIPT).toContain('["发送", "傳送", "Send"]');
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('chatgpt-app AX model script', () => {
|
|
23
|
+
it('supports english, zh-CN, and zh-TW options button labels', () => {
|
|
24
|
+
expect(__test__.AX_MODEL_SCRIPT).toContain('findByDesc(win, "Options")');
|
|
25
|
+
expect(__test__.AX_MODEL_SCRIPT).toContain('findByDesc(win, "选项")');
|
|
26
|
+
expect(__test__.AX_MODEL_SCRIPT).toContain('findByDesc(win, "選項")');
|
|
27
|
+
});
|
|
16
28
|
});
|
|
17
29
|
|
|
18
30
|
describe('chatgpt-app generating detection', () => {
|
|
@@ -12,7 +12,7 @@ export const modelCommand = cli({
|
|
|
12
12
|
{ name: 'model', required: true, positional: true, help: 'Model to switch to', choices: MODEL_CHOICES },
|
|
13
13
|
],
|
|
14
14
|
columns: ['Status', 'Model'],
|
|
15
|
-
func: async (
|
|
15
|
+
func: async (kwargs) => {
|
|
16
16
|
if (process.platform !== 'darwin') {
|
|
17
17
|
throw new ConfigError('ChatGPT Desktop integration requires macOS');
|
|
18
18
|
}
|
package/clis/chatgpt-app/new.js
CHANGED
|
@@ -10,7 +10,7 @@ export const newCommand = cli({
|
|
|
10
10
|
browser: false,
|
|
11
11
|
args: [],
|
|
12
12
|
columns: ['Status'],
|
|
13
|
-
func: async (
|
|
13
|
+
func: async () => {
|
|
14
14
|
if (process.platform !== 'darwin') {
|
|
15
15
|
throw new ConfigError('ChatGPT Desktop integration requires macOS (osascript is not available on this platform)');
|
|
16
16
|
}
|
package/clis/chatgpt-app/read.js
CHANGED
|
@@ -11,7 +11,7 @@ export const readCommand = cli({
|
|
|
11
11
|
browser: false,
|
|
12
12
|
args: [],
|
|
13
13
|
columns: ['Role', 'Text'],
|
|
14
|
-
func: async (
|
|
14
|
+
func: async () => {
|
|
15
15
|
if (process.platform !== 'darwin') {
|
|
16
16
|
throw new ConfigError('ChatGPT Desktop integration requires macOS (osascript is not available on this platform)');
|
|
17
17
|
}
|
package/clis/chatgpt-app/send.js
CHANGED
|
@@ -13,7 +13,7 @@ export const sendCommand = cli({
|
|
|
13
13
|
{ name: 'model', required: false, help: 'Model/mode to use: auto, instant, thinking, 5.2-instant, 5.2-thinking', choices: MODEL_CHOICES },
|
|
14
14
|
],
|
|
15
15
|
columns: ['Status'],
|
|
16
|
-
func: async (
|
|
16
|
+
func: async (kwargs) => {
|
|
17
17
|
const text = kwargs.text;
|
|
18
18
|
const model = kwargs.model;
|
|
19
19
|
try {
|
|
@@ -10,7 +10,7 @@ export const statusCommand = cli({
|
|
|
10
10
|
browser: false,
|
|
11
11
|
args: [],
|
|
12
12
|
columns: ['Status'],
|
|
13
|
-
func: async (
|
|
13
|
+
func: async () => {
|
|
14
14
|
if (process.platform !== 'darwin') {
|
|
15
15
|
throw new ConfigError('ChatGPT Desktop integration requires macOS (osascript is not available on this platform)');
|
|
16
16
|
}
|
package/clis/chatwise/ask.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
-
import {
|
|
2
|
+
import { selectorError } from '@jackwener/opencli/errors';
|
|
3
3
|
export const askCommand = cli({
|
|
4
4
|
site: 'chatwise',
|
|
5
5
|
name: 'ask',
|
|
@@ -43,7 +43,7 @@ export const askCommand = cli({
|
|
|
43
43
|
})(${JSON.stringify(text)})
|
|
44
44
|
`);
|
|
45
45
|
if (!injected)
|
|
46
|
-
throw
|
|
46
|
+
throw selectorError('ChatWise input element');
|
|
47
47
|
await page.wait(0.5);
|
|
48
48
|
await page.pressKey('Enter');
|
|
49
49
|
// Poll for response
|
package/clis/chatwise/model.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
-
import {
|
|
2
|
+
import { selectorError } from '@jackwener/opencli/errors';
|
|
3
3
|
export const modelCommand = cli({
|
|
4
4
|
site: 'chatwise',
|
|
5
5
|
name: 'model',
|
|
@@ -58,7 +58,7 @@ export const modelCommand = cli({
|
|
|
58
58
|
})(${JSON.stringify(desiredModel)})
|
|
59
59
|
`);
|
|
60
60
|
if (!opened)
|
|
61
|
-
throw
|
|
61
|
+
throw selectorError('ChatWise model selector');
|
|
62
62
|
await page.wait(0.5);
|
|
63
63
|
// Find and click the target model in the dropdown
|
|
64
64
|
const found = await page.evaluate(`
|
package/clis/chatwise/send.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
-
import {
|
|
2
|
+
import { selectorError } from '@jackwener/opencli/errors';
|
|
3
3
|
export const sendCommand = cli({
|
|
4
4
|
site: 'chatwise',
|
|
5
5
|
name: 'send',
|
|
@@ -36,7 +36,7 @@ export const sendCommand = cli({
|
|
|
36
36
|
})(${JSON.stringify(text)})
|
|
37
37
|
`);
|
|
38
38
|
if (!injected)
|
|
39
|
-
throw
|
|
39
|
+
throw selectorError('ChatWise input element');
|
|
40
40
|
await page.wait(0.5);
|
|
41
41
|
await page.pressKey('Enter');
|
|
42
42
|
return [
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
+
import { ArgumentError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
|
|
3
|
+
import {
|
|
4
|
+
CLAUDE_DOMAIN, CLAUDE_URL, ensureOnClaude, selectModel, setAdaptiveThinking,
|
|
5
|
+
sendMessage, sendWithFile, getBubbleCount, waitForResponse, parseBoolFlag, withRetry,
|
|
6
|
+
ensureClaudeComposer, requireNonEmptyPrompt, requirePositiveInt,
|
|
7
|
+
} from './utils.js';
|
|
8
|
+
|
|
9
|
+
export const askCommand = cli({
|
|
10
|
+
site: 'claude',
|
|
11
|
+
name: 'ask',
|
|
12
|
+
description: 'Send a prompt to Claude and get the response',
|
|
13
|
+
domain: CLAUDE_DOMAIN,
|
|
14
|
+
strategy: Strategy.COOKIE,
|
|
15
|
+
browser: true,
|
|
16
|
+
navigateBefore: false,
|
|
17
|
+
timeoutSeconds: 180,
|
|
18
|
+
args: [
|
|
19
|
+
{ name: 'prompt', positional: true, required: true, help: 'Prompt to send' },
|
|
20
|
+
{ name: 'timeout', type: 'int', default: 120, help: 'Max seconds to wait for response' },
|
|
21
|
+
{ name: 'new', type: 'boolean', default: false, help: 'Start a new chat before sending' },
|
|
22
|
+
{ name: 'model', default: 'sonnet', choices: ['sonnet', 'opus', 'haiku'], help: 'Model to use: sonnet, opus, or haiku' },
|
|
23
|
+
{ name: 'think', type: 'boolean', default: false, help: 'Enable Adaptive thinking' },
|
|
24
|
+
{ name: 'file', help: 'Attach a file (image, PDF, text) with the prompt' },
|
|
25
|
+
],
|
|
26
|
+
columns: ['response'],
|
|
27
|
+
|
|
28
|
+
func: async (page, kwargs) => {
|
|
29
|
+
const prompt = requireNonEmptyPrompt(kwargs.prompt, 'claude ask');
|
|
30
|
+
const timeoutSeconds = requirePositiveInt(
|
|
31
|
+
Number(kwargs.timeout ?? 120),
|
|
32
|
+
'claude ask --timeout',
|
|
33
|
+
'Example: opencli claude ask "hello" --timeout 120',
|
|
34
|
+
);
|
|
35
|
+
const timeoutMs = timeoutSeconds * 1000;
|
|
36
|
+
const wantThink = parseBoolFlag(kwargs.think);
|
|
37
|
+
|
|
38
|
+
if (parseBoolFlag(kwargs.new)) {
|
|
39
|
+
await page.goto(CLAUDE_URL);
|
|
40
|
+
await page.wait(3);
|
|
41
|
+
} else {
|
|
42
|
+
const navigated = await ensureOnClaude(page);
|
|
43
|
+
if (navigated) {
|
|
44
|
+
// Workspace was recycled; try to resume the most recent
|
|
45
|
+
// conversation instead of starting a new one.
|
|
46
|
+
await page.evaluate(`(() => {
|
|
47
|
+
var link = document.querySelector('a[href*="/chat/"]');
|
|
48
|
+
if (link) link.click();
|
|
49
|
+
})()`);
|
|
50
|
+
await page.wait(2);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
await page.wait(2);
|
|
55
|
+
await withRetry(() => ensureClaudeComposer(page, 'Claude ask requires a visible composer on the current page.'));
|
|
56
|
+
|
|
57
|
+
// Model selector is only available on the new-chat page, not inside
|
|
58
|
+
// an existing conversation. Skip it when we resumed a prior thread.
|
|
59
|
+
const currentUrl = await page.evaluate('window.location.href') || '';
|
|
60
|
+
const inConversation = currentUrl.includes('/chat/');
|
|
61
|
+
const modelExplicit = kwargs.__opencliOptionSources?.model === 'cli';
|
|
62
|
+
|
|
63
|
+
const wantModel = kwargs.model || 'sonnet';
|
|
64
|
+
if (inConversation && modelExplicit) {
|
|
65
|
+
throw new ArgumentError(
|
|
66
|
+
`Cannot switch to ${wantModel} model inside an existing conversation.`,
|
|
67
|
+
'Re-run with --new to start a fresh chat before selecting a model.',
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!inConversation) {
|
|
72
|
+
const modelResult = await withRetry(() => selectModel(page, wantModel));
|
|
73
|
+
if (!modelResult?.ok) {
|
|
74
|
+
if (modelResult?.upgrade) {
|
|
75
|
+
throw new ArgumentError(
|
|
76
|
+
`${wantModel} model requires a paid Claude plan.`,
|
|
77
|
+
'Pick --model sonnet or --model haiku, or upgrade your account.',
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
throw new CommandExecutionError(`Could not switch to ${wantModel} model`);
|
|
81
|
+
}
|
|
82
|
+
if (modelResult?.toggled) await page.wait(0.5);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const thinkResult = await withRetry(() => setAdaptiveThinking(page, wantThink));
|
|
86
|
+
if (!thinkResult?.ok && wantThink) {
|
|
87
|
+
throw new CommandExecutionError('Could not enable Adaptive thinking');
|
|
88
|
+
}
|
|
89
|
+
if (thinkResult?.toggled) await page.wait(0.5);
|
|
90
|
+
|
|
91
|
+
if (kwargs.file) {
|
|
92
|
+
const baseline = await withRetry(() => getBubbleCount(page));
|
|
93
|
+
try {
|
|
94
|
+
const fileResult = await sendWithFile(page, kwargs.file, prompt);
|
|
95
|
+
if (fileResult && !fileResult.ok) {
|
|
96
|
+
throw new CommandExecutionError(fileResult.reason || 'Failed to attach file');
|
|
97
|
+
}
|
|
98
|
+
} catch (err) {
|
|
99
|
+
// SPA navigates after send; "Promise was collected" means send succeeded
|
|
100
|
+
if (!String(err?.message || err).includes('Promise was collected')) throw err;
|
|
101
|
+
}
|
|
102
|
+
await page.wait(3);
|
|
103
|
+
const result = await waitForResponse(page, baseline, prompt, timeoutMs);
|
|
104
|
+
if (!result) {
|
|
105
|
+
throw new EmptyResultError(
|
|
106
|
+
'claude ask',
|
|
107
|
+
`No Claude response appeared within ${timeoutSeconds}s. Re-run with a higher --timeout if the model is still generating.`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return [{ response: result }];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const baseline = await withRetry(() => getBubbleCount(page));
|
|
114
|
+
const sendResult = await withRetry(() => sendMessage(page, prompt));
|
|
115
|
+
if (!sendResult?.ok) {
|
|
116
|
+
throw new CommandExecutionError(sendResult?.reason || 'Failed to send message');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const result = await waitForResponse(page, baseline, prompt, timeoutMs);
|
|
120
|
+
if (!result) {
|
|
121
|
+
throw new EmptyResultError(
|
|
122
|
+
'claude ask',
|
|
123
|
+
`No Claude response appeared within ${timeoutSeconds}s. Re-run with a higher --timeout if the model is still generating.`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return [{ response: result }];
|
|
127
|
+
},
|
|
128
|
+
});
|