@jackwener/opencli 1.7.16 → 1.7.18
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 +11 -9
- package/README.zh-CN.md +10 -8
- package/cli-manifest.json +377 -271
- package/clis/chatgpt/ask.js +1 -1
- package/clis/chatgpt/commands.test.js +2 -2
- package/clis/chatgpt/detail.js +1 -1
- package/clis/chatgpt/history.js +1 -1
- package/clis/chatgpt/image.js +38 -4
- package/clis/chatgpt/image.test.js +68 -1
- package/clis/chatgpt/new.js +1 -1
- package/clis/chatgpt/read.js +1 -1
- package/clis/chatgpt/send.js +1 -1
- package/clis/chatgpt/status.js +1 -1
- package/clis/chatgpt/utils.js +208 -16
- package/clis/chatgpt/utils.test.js +131 -2
- package/clis/claude/ask.js +1 -1
- package/clis/claude/detail.js +1 -1
- package/clis/claude/history.js +1 -1
- package/clis/claude/new.js +1 -1
- package/clis/claude/read.js +1 -1
- package/clis/claude/send.js +1 -1
- package/clis/claude/status.js +1 -1
- package/clis/deepseek/ask.js +1 -1
- package/clis/deepseek/detail.js +1 -1
- package/clis/deepseek/history.js +1 -1
- package/clis/deepseek/new.js +1 -1
- package/clis/deepseek/read.js +1 -1
- package/clis/deepseek/send.js +1 -1
- package/clis/deepseek/status.js +1 -1
- package/clis/doubao/ask.js +1 -1
- package/clis/doubao/detail.js +1 -1
- package/clis/doubao/history.js +1 -1
- package/clis/doubao/meeting-summary.js +1 -1
- package/clis/doubao/meeting-transcript.js +1 -1
- package/clis/doubao/new.js +1 -1
- package/clis/doubao/read.js +1 -1
- package/clis/doubao/send.js +1 -1
- package/clis/doubao/status.js +1 -1
- package/clis/doubao/utils.js +17 -0
- package/clis/doubao/utils.test.js +61 -0
- package/clis/gemini/ask.js +1 -1
- package/clis/gemini/deep-research-result.js +1 -1
- package/clis/gemini/deep-research.js +1 -1
- package/clis/gemini/image.js +1 -1
- package/clis/gemini/new.js +1 -1
- package/clis/grok/ask.js +1 -1
- package/clis/grok/detail.js +1 -1
- package/clis/grok/history.js +1 -1
- package/clis/grok/image.js +1 -1
- package/clis/grok/new.js +1 -1
- package/clis/grok/read.js +1 -1
- package/clis/grok/send.js +1 -1
- package/clis/grok/status.js +1 -1
- package/clis/notebooklm/current.js +1 -1
- package/clis/notebooklm/get.js +1 -1
- package/clis/notebooklm/history.js +1 -1
- package/clis/notebooklm/note-list.js +1 -1
- package/clis/notebooklm/notes-get.js +1 -1
- package/clis/notebooklm/open.js +2 -2
- package/clis/notebooklm/open.test.js +1 -1
- package/clis/notebooklm/source-fulltext.js +1 -1
- package/clis/notebooklm/source-get.js +1 -1
- package/clis/notebooklm/source-guide.js +1 -1
- package/clis/notebooklm/source-list.js +1 -1
- package/clis/notebooklm/summary.js +1 -1
- package/clis/qwen/ask.js +1 -1
- package/clis/qwen/detail.js +1 -1
- package/clis/qwen/history.js +1 -1
- package/clis/qwen/image.js +1 -1
- package/clis/qwen/new.js +1 -1
- package/clis/qwen/read.js +1 -1
- package/clis/qwen/send.js +1 -1
- package/clis/qwen/status.js +1 -1
- package/clis/reddit/comment.js +1 -1
- package/clis/reddit/frontpage.js +1 -1
- package/clis/reddit/popular.js +1 -1
- package/clis/reddit/read.js +1 -1
- package/clis/reddit/read.test.js +2 -2
- package/clis/reddit/reply.js +182 -0
- package/clis/reddit/reply.test.js +89 -0
- package/clis/reddit/save.js +1 -1
- package/clis/reddit/saved.js +1 -1
- package/clis/reddit/search.js +1 -1
- package/clis/reddit/subreddit.js +1 -1
- package/clis/reddit/subscribe.js +1 -1
- package/clis/reddit/upvote.js +1 -1
- package/clis/reddit/upvoted.js +1 -1
- package/clis/reddit/user-comments.js +1 -1
- package/clis/reddit/user-posts.js +1 -1
- package/clis/reddit/user.js +1 -1
- package/clis/rednote/comments.js +76 -0
- package/clis/rednote/download.js +59 -0
- package/clis/rednote/feed.js +95 -0
- package/clis/rednote/navigation.test.js +26 -0
- package/clis/rednote/note.js +68 -0
- package/clis/rednote/notifications.js +139 -0
- package/clis/rednote/rednote.test.js +157 -0
- package/clis/rednote/search.js +97 -0
- package/clis/rednote/user.js +55 -0
- package/clis/twitter/article.js +1 -1
- package/clis/twitter/bookmark-folder.js +1 -1
- package/clis/twitter/bookmark-folders.js +1 -1
- package/clis/twitter/bookmarks.js +1 -1
- package/clis/twitter/download.js +1 -1
- package/clis/twitter/followers.js +1 -1
- package/clis/twitter/following.js +1 -1
- package/clis/twitter/likes.js +1 -1
- package/clis/twitter/list-tweets.js +1 -1
- package/clis/twitter/lists.js +1 -1
- package/clis/twitter/notifications.js +1 -1
- package/clis/twitter/profile.js +1 -1
- package/clis/twitter/search.js +1 -1
- package/clis/twitter/thread.js +1 -1
- package/clis/twitter/timeline.js +1 -1
- package/clis/twitter/trending.js +1 -1
- package/clis/twitter/tweets.js +1 -1
- package/clis/xiaohongshu/comments.js +34 -24
- package/clis/xiaohongshu/download.js +32 -23
- package/clis/xiaohongshu/feed.js +23 -15
- package/clis/xiaohongshu/note-helpers.js +16 -6
- package/clis/xiaohongshu/note.js +26 -20
- package/clis/xiaohongshu/notifications.js +26 -19
- package/clis/xiaohongshu/search.js +37 -28
- package/clis/xiaohongshu/user-helpers.js +13 -4
- package/clis/xiaohongshu/user-helpers.test.js +20 -0
- package/clis/xiaohongshu/user.js +9 -4
- package/clis/youtube/transcript.js +28 -3
- package/clis/youtube/transcript.test.js +90 -1
- package/clis/yuanbao/ask.js +1 -1
- package/clis/yuanbao/detail.js +1 -1
- package/clis/yuanbao/history.js +1 -1
- package/clis/yuanbao/new.js +1 -1
- package/clis/yuanbao/read.js +1 -1
- package/clis/yuanbao/send.js +1 -1
- package/clis/yuanbao/status.js +1 -1
- package/dist/src/browser/bridge.d.ts +3 -1
- package/dist/src/browser/bridge.js +3 -1
- package/dist/src/browser/cdp.d.ts +3 -1
- package/dist/src/browser/daemon-client.d.ts +7 -14
- package/dist/src/browser/daemon-client.js +2 -6
- package/dist/src/browser/network-cache.d.ts +5 -5
- package/dist/src/browser/network-cache.js +8 -8
- package/dist/src/browser/network-cache.test.js +4 -4
- package/dist/src/browser/page.d.ts +8 -7
- package/dist/src/browser/page.js +23 -16
- package/dist/src/browser/page.test.js +60 -30
- package/dist/src/build-manifest.js +1 -1
- package/dist/src/cli.js +60 -162
- package/dist/src/cli.test.js +184 -198
- package/dist/src/commanderAdapter.js +2 -0
- package/dist/src/discovery.js +1 -1
- package/dist/src/doctor.d.ts +0 -4
- package/dist/src/doctor.js +14 -73
- package/dist/src/doctor.test.js +28 -97
- package/dist/src/execution.d.ts +1 -0
- package/dist/src/execution.js +20 -21
- package/dist/src/execution.test.js +27 -31
- package/dist/src/help.js +7 -1
- package/dist/src/main.js +0 -19
- package/dist/src/manifest-types.d.ts +2 -4
- package/dist/src/observation/artifact.js +1 -1
- package/dist/src/observation/artifact.test.js +3 -3
- package/dist/src/observation/events.d.ts +1 -1
- package/dist/src/observation/manager.js +1 -1
- package/dist/src/observation/manager.test.js +3 -3
- package/dist/src/registry-api.d.ts +1 -1
- package/dist/src/registry.d.ts +3 -12
- package/dist/src/registry.js +6 -10
- package/dist/src/runtime.d.ts +7 -2
- package/dist/src/runtime.js +3 -1
- package/dist/src/serialization.d.ts +1 -1
- package/dist/src/serialization.js +1 -1
- package/dist/src/types.d.ts +0 -15
- package/package.json +1 -1
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { __test__, prepareChatGPTImagePaths, sendChatGPTMessage, uploadChatGPTImages, waitForChatGPTImages } from './utils.js';
|
|
6
|
+
|
|
7
|
+
const tempDirs = [];
|
|
8
|
+
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
vi.restoreAllMocks();
|
|
11
|
+
while (tempDirs.length) {
|
|
12
|
+
fs.rmSync(tempDirs.pop(), { recursive: true, force: true });
|
|
13
|
+
}
|
|
14
|
+
});
|
|
3
15
|
|
|
4
16
|
function createPageMock({ location = '', generating = [], imageUrls = [] } = {}) {
|
|
5
17
|
let generatingIndex = 0;
|
|
@@ -96,6 +108,26 @@ describe('chatgpt send selectors', () => {
|
|
|
96
108
|
await expect(sendChatGPTMessage(page, 'hello')).resolves.toBe(true);
|
|
97
109
|
});
|
|
98
110
|
|
|
111
|
+
it('uses the composer submit fallback consistently for readiness and click', async () => {
|
|
112
|
+
const page = {
|
|
113
|
+
wait: vi.fn().mockResolvedValue(undefined),
|
|
114
|
+
nativeType: vi.fn().mockResolvedValue(undefined),
|
|
115
|
+
evaluate: vi.fn((script) => {
|
|
116
|
+
if (script.includes('findComposer')) return Promise.resolve(true);
|
|
117
|
+
if (script.includes('sendBtnFound')) {
|
|
118
|
+
expect(script).toContain('#composer-submit-button:not([disabled])');
|
|
119
|
+
return Promise.resolve({ sendBtnFound: true });
|
|
120
|
+
}
|
|
121
|
+
if (script.includes('if (sendBtn) sendBtn.click')) {
|
|
122
|
+
expect(script).toContain('#composer-submit-button:not([disabled])');
|
|
123
|
+
}
|
|
124
|
+
return Promise.resolve(undefined);
|
|
125
|
+
}),
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
await expect(sendChatGPTMessage(page, 'hello')).resolves.toBe(true);
|
|
129
|
+
});
|
|
130
|
+
|
|
99
131
|
it('keeps zh-CN aria and placeholder fallbacks without replacing English selectors', () => {
|
|
100
132
|
expect(__test__.COMPOSER_SELECTORS).toEqual(expect.arrayContaining([
|
|
101
133
|
'[aria-label="Chat with ChatGPT"]',
|
|
@@ -105,7 +137,104 @@ describe('chatgpt send selectors', () => {
|
|
|
105
137
|
'[data-testid="prompt-textarea"]',
|
|
106
138
|
]));
|
|
107
139
|
expect(__test__.SEND_BUTTON_SELECTOR).toBe('button[data-testid="send-button"]:not([disabled])');
|
|
140
|
+
expect(__test__.SEND_BUTTON_FALLBACK_SELECTORS).toContain('#composer-submit-button:not([disabled])');
|
|
108
141
|
expect(__test__.SEND_BUTTON_LABELS).toEqual(expect.arrayContaining(['Send prompt', 'Send message', 'Send', '发送提示']));
|
|
109
142
|
expect(__test__.CLOSE_SIDEBAR_LABELS).toEqual(expect.arrayContaining(['Close sidebar', '关闭边栏']));
|
|
110
143
|
});
|
|
111
144
|
});
|
|
145
|
+
|
|
146
|
+
describe('chatgpt image upload helper', () => {
|
|
147
|
+
it('validates local images without a browser page', async () => {
|
|
148
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-chatgpt-'));
|
|
149
|
+
tempDirs.push(dir);
|
|
150
|
+
const filePath = path.join(dir, 'cat.png');
|
|
151
|
+
fs.writeFileSync(filePath, 'fake-png');
|
|
152
|
+
|
|
153
|
+
await expect(prepareChatGPTImagePaths([filePath])).resolves.toEqual({ ok: true, paths: [filePath] });
|
|
154
|
+
await expect(prepareChatGPTImagePaths([path.join(dir, 'missing.png')])).resolves.toMatchObject({
|
|
155
|
+
ok: false,
|
|
156
|
+
reason: expect.stringContaining('Image not found'),
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('prefers Browser Bridge file input upload and waits for a preview', async () => {
|
|
161
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-chatgpt-'));
|
|
162
|
+
tempDirs.push(dir);
|
|
163
|
+
const filePath = path.join(dir, 'cat.png');
|
|
164
|
+
fs.writeFileSync(filePath, 'fake-png');
|
|
165
|
+
|
|
166
|
+
const page = {
|
|
167
|
+
setFileInput: vi.fn().mockResolvedValue(undefined),
|
|
168
|
+
wait: vi.fn().mockResolvedValue(undefined),
|
|
169
|
+
evaluate: vi.fn().mockResolvedValue(true),
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const result = await uploadChatGPTImages(page, [filePath]);
|
|
173
|
+
|
|
174
|
+
expect(result).toEqual({ ok: true, files: [filePath] });
|
|
175
|
+
expect(page.setFileInput).toHaveBeenCalledWith([filePath], 'input[type="file"]');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('rejects missing files before touching the page', async () => {
|
|
179
|
+
const page = {
|
|
180
|
+
setFileInput: vi.fn(),
|
|
181
|
+
wait: vi.fn(),
|
|
182
|
+
evaluate: vi.fn(),
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const result = await uploadChatGPTImages(page, ['/no/such/cat.png']);
|
|
186
|
+
|
|
187
|
+
expect(result.ok).toBe(false);
|
|
188
|
+
expect(result.reason).toContain('Image not found');
|
|
189
|
+
expect(page.setFileInput).not.toHaveBeenCalled();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('rejects non-image extensions', async () => {
|
|
193
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-chatgpt-'));
|
|
194
|
+
tempDirs.push(dir);
|
|
195
|
+
const filePath = path.join(dir, 'report.pdf');
|
|
196
|
+
fs.writeFileSync(filePath, 'fake');
|
|
197
|
+
|
|
198
|
+
const page = {
|
|
199
|
+
setFileInput: vi.fn(),
|
|
200
|
+
wait: vi.fn(),
|
|
201
|
+
evaluate: vi.fn(),
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const result = await uploadChatGPTImages(page, [filePath]);
|
|
205
|
+
|
|
206
|
+
expect(result.ok).toBe(false);
|
|
207
|
+
expect(result.reason).toContain('Unsupported image type');
|
|
208
|
+
expect(page.setFileInput).not.toHaveBeenCalled();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('passes a React-compatible change event in fallback upload', async () => {
|
|
212
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-chatgpt-'));
|
|
213
|
+
tempDirs.push(dir);
|
|
214
|
+
const filePath = path.join(dir, 'cat.png');
|
|
215
|
+
fs.writeFileSync(filePath, 'fake-png');
|
|
216
|
+
|
|
217
|
+
const page = {
|
|
218
|
+
setFileInput: vi.fn().mockRejectedValue(new Error('No element found')),
|
|
219
|
+
wait: vi.fn().mockResolvedValue(undefined),
|
|
220
|
+
evaluate: vi.fn((script) => {
|
|
221
|
+
return Promise.resolve({ ok: true });
|
|
222
|
+
}),
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const result = await uploadChatGPTImages(page, [filePath]);
|
|
226
|
+
|
|
227
|
+
expect(result).toEqual({ ok: true, files: [filePath] });
|
|
228
|
+
const fallbackScript = page.evaluate.mock.calls
|
|
229
|
+
.map(([script]) => String(script))
|
|
230
|
+
.find(script => script.includes('new DataTransfer()'));
|
|
231
|
+
expect(fallbackScript).toContain('preventDefault()');
|
|
232
|
+
expect(fallbackScript).toContain('stopPropagation()');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('exposes image MIME inference for fallback upload', () => {
|
|
236
|
+
expect(__test__.imageMimeFromPath('/tmp/a.png')).toBe('image/png');
|
|
237
|
+
expect(__test__.imageMimeFromPath('/tmp/a.webp')).toBe('image/webp');
|
|
238
|
+
expect(__test__.imageMimeFromPath('/tmp/a.jpg')).toBe('image/jpeg');
|
|
239
|
+
});
|
|
240
|
+
});
|
package/clis/claude/ask.js
CHANGED
|
@@ -15,7 +15,7 @@ export const askCommand = cli({
|
|
|
15
15
|
domain: CLAUDE_DOMAIN,
|
|
16
16
|
strategy: Strategy.COOKIE,
|
|
17
17
|
browser: true,
|
|
18
|
-
|
|
18
|
+
siteSession: 'persistent',
|
|
19
19
|
navigateBefore: false,
|
|
20
20
|
args: [
|
|
21
21
|
{ name: 'prompt', positional: true, required: true, help: 'Prompt to send' },
|
package/clis/claude/detail.js
CHANGED
|
@@ -10,7 +10,7 @@ export const detailCommand = cli({
|
|
|
10
10
|
domain: CLAUDE_DOMAIN,
|
|
11
11
|
strategy: Strategy.COOKIE,
|
|
12
12
|
browser: true,
|
|
13
|
-
|
|
13
|
+
siteSession: 'persistent',
|
|
14
14
|
navigateBefore: false,
|
|
15
15
|
args: [
|
|
16
16
|
{ name: 'id', positional: true, required: true, help: 'Conversation ID (UUID from /chat/<id>)' },
|
package/clis/claude/history.js
CHANGED
|
@@ -10,7 +10,7 @@ export const historyCommand = cli({
|
|
|
10
10
|
domain: CLAUDE_DOMAIN,
|
|
11
11
|
strategy: Strategy.COOKIE,
|
|
12
12
|
browser: true,
|
|
13
|
-
|
|
13
|
+
siteSession: 'persistent',
|
|
14
14
|
navigateBefore: false,
|
|
15
15
|
args: [
|
|
16
16
|
{ name: 'limit', type: 'int', default: 20, help: 'Max conversations to show' },
|
package/clis/claude/new.js
CHANGED
package/clis/claude/read.js
CHANGED
package/clis/claude/send.js
CHANGED
|
@@ -10,7 +10,7 @@ export const sendCommand = cli({
|
|
|
10
10
|
domain: CLAUDE_DOMAIN,
|
|
11
11
|
strategy: Strategy.COOKIE,
|
|
12
12
|
browser: true,
|
|
13
|
-
|
|
13
|
+
siteSession: 'persistent',
|
|
14
14
|
navigateBefore: false,
|
|
15
15
|
args: [
|
|
16
16
|
{ name: 'prompt', positional: true, required: true, help: 'Prompt to send' },
|
package/clis/claude/status.js
CHANGED
package/clis/deepseek/ask.js
CHANGED
|
@@ -14,7 +14,7 @@ export const askCommand = cli({
|
|
|
14
14
|
domain: DEEPSEEK_DOMAIN,
|
|
15
15
|
strategy: Strategy.COOKIE,
|
|
16
16
|
browser: true,
|
|
17
|
-
|
|
17
|
+
siteSession: 'persistent',
|
|
18
18
|
navigateBefore: false,
|
|
19
19
|
args: [
|
|
20
20
|
{ name: 'prompt', positional: true, required: true, help: 'Prompt to send' },
|
package/clis/deepseek/detail.js
CHANGED
|
@@ -16,7 +16,7 @@ export const detailCommand = cli({
|
|
|
16
16
|
domain: DEEPSEEK_DOMAIN,
|
|
17
17
|
strategy: Strategy.COOKIE,
|
|
18
18
|
browser: true,
|
|
19
|
-
|
|
19
|
+
siteSession: 'persistent',
|
|
20
20
|
navigateBefore: false,
|
|
21
21
|
args: [
|
|
22
22
|
{ name: 'id', required: true, positional: true, help: 'Conversation ID (UUID) or full /a/chat/s/<id> URL' },
|
package/clis/deepseek/history.js
CHANGED
|
@@ -9,7 +9,7 @@ export const historyCommand = cli({
|
|
|
9
9
|
domain: DEEPSEEK_DOMAIN,
|
|
10
10
|
strategy: Strategy.COOKIE,
|
|
11
11
|
browser: true,
|
|
12
|
-
|
|
12
|
+
siteSession: 'persistent',
|
|
13
13
|
navigateBefore: false,
|
|
14
14
|
args: [
|
|
15
15
|
{ name: 'limit', type: 'int', default: 20, help: 'Max conversations to show' },
|
package/clis/deepseek/new.js
CHANGED
package/clis/deepseek/read.js
CHANGED
package/clis/deepseek/send.js
CHANGED
|
@@ -15,7 +15,7 @@ export const sendCommand = cli({
|
|
|
15
15
|
domain: DEEPSEEK_DOMAIN,
|
|
16
16
|
strategy: Strategy.COOKIE,
|
|
17
17
|
browser: true,
|
|
18
|
-
|
|
18
|
+
siteSession: 'persistent',
|
|
19
19
|
navigateBefore: false,
|
|
20
20
|
args: [
|
|
21
21
|
{ name: 'id', required: true, positional: true, help: 'Conversation ID (UUID) or full /a/chat/s/<id> URL' },
|
package/clis/deepseek/status.js
CHANGED
package/clis/doubao/ask.js
CHANGED
|
@@ -9,7 +9,7 @@ export const askCommand = cli({
|
|
|
9
9
|
domain: DOUBAO_DOMAIN,
|
|
10
10
|
strategy: Strategy.COOKIE,
|
|
11
11
|
browser: true,
|
|
12
|
-
|
|
12
|
+
siteSession: 'persistent',
|
|
13
13
|
navigateBefore: false,
|
|
14
14
|
args: [
|
|
15
15
|
{ name: 'text', required: true, positional: true, help: 'Prompt to send' },
|
package/clis/doubao/detail.js
CHANGED
|
@@ -8,7 +8,7 @@ export const detailCommand = cli({
|
|
|
8
8
|
domain: DOUBAO_DOMAIN,
|
|
9
9
|
strategy: Strategy.COOKIE,
|
|
10
10
|
browser: true,
|
|
11
|
-
|
|
11
|
+
siteSession: 'persistent',
|
|
12
12
|
navigateBefore: false,
|
|
13
13
|
args: [
|
|
14
14
|
{ name: 'id', required: true, positional: true, help: 'Conversation ID (numeric or full URL)' },
|
package/clis/doubao/history.js
CHANGED
|
@@ -8,7 +8,7 @@ export const historyCommand = cli({
|
|
|
8
8
|
domain: DOUBAO_DOMAIN,
|
|
9
9
|
strategy: Strategy.COOKIE,
|
|
10
10
|
browser: true,
|
|
11
|
-
|
|
11
|
+
siteSession: 'persistent',
|
|
12
12
|
navigateBefore: false,
|
|
13
13
|
args: [
|
|
14
14
|
{ name: 'limit', required: false, help: 'Max number of conversations to show', default: '50' },
|
|
@@ -8,7 +8,7 @@ export const meetingSummaryCommand = cli({
|
|
|
8
8
|
domain: DOUBAO_DOMAIN,
|
|
9
9
|
strategy: Strategy.COOKIE,
|
|
10
10
|
browser: true,
|
|
11
|
-
|
|
11
|
+
siteSession: 'persistent',
|
|
12
12
|
navigateBefore: false,
|
|
13
13
|
args: [
|
|
14
14
|
{ name: 'id', required: true, positional: true, help: 'Conversation ID (numeric or full URL)' },
|
|
@@ -8,7 +8,7 @@ export const meetingTranscriptCommand = cli({
|
|
|
8
8
|
domain: DOUBAO_DOMAIN,
|
|
9
9
|
strategy: Strategy.COOKIE,
|
|
10
10
|
browser: true,
|
|
11
|
-
|
|
11
|
+
siteSession: 'persistent',
|
|
12
12
|
navigateBefore: false,
|
|
13
13
|
args: [
|
|
14
14
|
{ name: 'id', required: true, positional: true, help: 'Conversation ID (numeric or full URL)' },
|
package/clis/doubao/new.js
CHANGED
package/clis/doubao/read.js
CHANGED
package/clis/doubao/send.js
CHANGED
|
@@ -8,7 +8,7 @@ export const sendCommand = cli({
|
|
|
8
8
|
domain: DOUBAO_DOMAIN,
|
|
9
9
|
strategy: Strategy.COOKIE,
|
|
10
10
|
browser: true,
|
|
11
|
-
|
|
11
|
+
siteSession: 'persistent',
|
|
12
12
|
navigateBefore: false,
|
|
13
13
|
args: [{ name: 'text', required: true, positional: true, help: 'Message to send' }],
|
|
14
14
|
columns: ['Status', 'SubmittedBy', 'InjectedText'],
|
package/clis/doubao/status.js
CHANGED
package/clis/doubao/utils.js
CHANGED
|
@@ -163,6 +163,19 @@ function getTurnsScript() {
|
|
|
163
163
|
) {
|
|
164
164
|
return 'Assistant';
|
|
165
165
|
}
|
|
166
|
+
// 2026-05 Doubao DOM refactor: no more receive-message / bg-g-receive-msg-bubble
|
|
167
|
+
// markers on assistant turns. Wrappers are now [class*="inner-item-"] /
|
|
168
|
+
// [class*="top-item-"] and the only reliable assistant signal is the
|
|
169
|
+
// .flow-markdown-body content container WITHOUT any send-bubble marker.
|
|
170
|
+
if (
|
|
171
|
+
(root.matches('[class*="inner-item-"], [class*="top-item-"]')
|
|
172
|
+
|| root.closest('[class*="inner-item-"], [class*="top-item-"]'))
|
|
173
|
+
&& (root.matches('.flow-markdown-body') || root.querySelector('.flow-markdown-body'))
|
|
174
|
+
&& !root.matches('[class*="bg-g-send-msg-bubble"]')
|
|
175
|
+
&& !root.querySelector('[class*="bg-g-send-msg-bubble"]')
|
|
176
|
+
) {
|
|
177
|
+
return 'Assistant';
|
|
178
|
+
}
|
|
166
179
|
return '';
|
|
167
180
|
};
|
|
168
181
|
|
|
@@ -223,6 +236,10 @@ function getTurnsScript() {
|
|
|
223
236
|
if (!messageList) return [];
|
|
224
237
|
|
|
225
238
|
const itemSelectors = [
|
|
239
|
+
// 2026-05 Doubao DOM refactor wrappers (prepended; outer ones win via
|
|
240
|
+
// ancestor-keep dedup below).
|
|
241
|
+
'[class*="inner-item-"]',
|
|
242
|
+
'[class*="top-item-"]',
|
|
226
243
|
'[class*="item-kDun2N"]',
|
|
227
244
|
'[data-testid="union_message"]',
|
|
228
245
|
'[data-testid="message-block-container"]',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { JSDOM } from 'jsdom';
|
|
1
2
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
3
|
import { CommandExecutionError } from '@jackwener/opencli/errors';
|
|
3
4
|
import {
|
|
@@ -145,6 +146,28 @@ describe('doubao send strategy', () => {
|
|
|
145
146
|
});
|
|
146
147
|
});
|
|
147
148
|
describe('doubao receive strategy', () => {
|
|
149
|
+
function runTurnsScript(html) {
|
|
150
|
+
const dom = new JSDOM(html, { url: 'https://www.doubao.com/chat', runScripts: 'outside-only' });
|
|
151
|
+
Object.defineProperty(dom.window.HTMLElement.prototype, 'innerText', {
|
|
152
|
+
configurable: true,
|
|
153
|
+
get() {
|
|
154
|
+
return this.textContent || '';
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
dom.window.HTMLElement.prototype.getBoundingClientRect = () => ({
|
|
158
|
+
width: 100,
|
|
159
|
+
height: 24,
|
|
160
|
+
top: 0,
|
|
161
|
+
left: 0,
|
|
162
|
+
right: 100,
|
|
163
|
+
bottom: 24,
|
|
164
|
+
x: 0,
|
|
165
|
+
y: 0,
|
|
166
|
+
toJSON: () => ({}),
|
|
167
|
+
});
|
|
168
|
+
return dom.window.eval(__test__.getTurnsScript());
|
|
169
|
+
}
|
|
170
|
+
|
|
148
171
|
it('keeps both the new skin selectors and the older structural fallbacks in the turns script', () => {
|
|
149
172
|
const turnsScript = __test__.getTurnsScript();
|
|
150
173
|
expect(turnsScript).toContain('[class*="message-list-S2Fv2S"]');
|
|
@@ -157,6 +180,44 @@ describe('doubao receive strategy', () => {
|
|
|
157
180
|
expect(turnsScript).toContain('[data-testid="message-block-container"]');
|
|
158
181
|
});
|
|
159
182
|
|
|
183
|
+
it('includes the 2026-05 doubao DOM-refactor inner-item / top-item wrappers and the flow-markdown-body assistant fallback', () => {
|
|
184
|
+
const turnsScript = __test__.getTurnsScript();
|
|
185
|
+
// New wrappers added to itemSelectors so message roots resolve under the
|
|
186
|
+
// refactored DOM where the legacy item-kDun2N / union_message / message-block-container
|
|
187
|
+
// / data-message-id selectors no longer match.
|
|
188
|
+
expect(turnsScript).toContain('[class*="inner-item-"]');
|
|
189
|
+
expect(turnsScript).toContain('[class*="top-item-"]');
|
|
190
|
+
// Assistant fallback: post-refactor doubao no longer emits receive-message /
|
|
191
|
+
// bg-g-receive-msg-bubble markup. Only signal is .flow-markdown-body content
|
|
192
|
+
// container without send-bubble.
|
|
193
|
+
expect(turnsScript).toContain('.flow-markdown-body');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('extracts clean assistant turns from the 2026-05 wrapper DOM without using whole-page chrome', () => {
|
|
197
|
+
const turns = runTurnsScript(`
|
|
198
|
+
<main>
|
|
199
|
+
<aside>历史对话</aside>
|
|
200
|
+
<section class="message-list-S2Fv2S">
|
|
201
|
+
<div class="top-item-user">
|
|
202
|
+
<div class="inner-item-user">
|
|
203
|
+
<div class="bg-g-send-msg-bubble">测试一下,只回复OK</div>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
<div class="top-item-assistant">
|
|
207
|
+
<div class="inner-item-assistant">
|
|
208
|
+
<div class="flow-markdown-body"><p>OK</p></div>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
</section>
|
|
212
|
+
</main>
|
|
213
|
+
`);
|
|
214
|
+
|
|
215
|
+
expect(turns).toEqual([
|
|
216
|
+
{ Role: 'User', Text: '测试一下,只回复OK' },
|
|
217
|
+
{ Role: 'Assistant', Text: 'OK' },
|
|
218
|
+
]);
|
|
219
|
+
});
|
|
220
|
+
|
|
160
221
|
it('extends transcript-noise cleanup for the current zh-CN chrome copy', () => {
|
|
161
222
|
const transcriptScript = __test__.getTranscriptLinesScript();
|
|
162
223
|
expect(transcriptScript).toContain('请仔细甄别');
|
package/clis/gemini/ask.js
CHANGED
package/clis/gemini/image.js
CHANGED
package/clis/gemini/new.js
CHANGED
package/clis/grok/ask.js
CHANGED
|
@@ -29,7 +29,7 @@ export const askCommand = cli({
|
|
|
29
29
|
domain: 'grok.com',
|
|
30
30
|
strategy: Strategy.COOKIE,
|
|
31
31
|
browser: true,
|
|
32
|
-
|
|
32
|
+
siteSession: 'persistent',
|
|
33
33
|
args: [
|
|
34
34
|
{ name: 'prompt', positional: true, type: 'string', required: true, help: 'Prompt to send to Grok' },
|
|
35
35
|
{ name: 'timeout', type: 'int', default: 120, help: 'Max seconds to wait for response (default: 120)' },
|
package/clis/grok/detail.js
CHANGED
|
@@ -17,7 +17,7 @@ cli({
|
|
|
17
17
|
strategy: Strategy.COOKIE,
|
|
18
18
|
browser: true,
|
|
19
19
|
navigateBefore: false,
|
|
20
|
-
|
|
20
|
+
siteSession: 'persistent',
|
|
21
21
|
args: [
|
|
22
22
|
{ name: 'id', positional: true, required: true, help: 'Session ID (UUID) or full https://grok.com/c/<id> URL' },
|
|
23
23
|
{ name: 'markdown', type: 'boolean', default: false, help: 'Emit assistant replies as markdown' },
|
package/clis/grok/history.js
CHANGED
|
@@ -16,7 +16,7 @@ cli({
|
|
|
16
16
|
domain: GROK_DOMAIN,
|
|
17
17
|
strategy: Strategy.COOKIE,
|
|
18
18
|
browser: true,
|
|
19
|
-
|
|
19
|
+
siteSession: 'persistent',
|
|
20
20
|
navigateBefore: false,
|
|
21
21
|
args: [
|
|
22
22
|
{ name: 'limit', type: 'int', default: 20, help: 'Max conversations to show (default 20, max 100)' },
|
package/clis/grok/image.js
CHANGED
|
@@ -253,7 +253,7 @@ export const imageCommand = cli({
|
|
|
253
253
|
domain: 'grok.com',
|
|
254
254
|
strategy: Strategy.COOKIE,
|
|
255
255
|
browser: true,
|
|
256
|
-
|
|
256
|
+
siteSession: 'persistent',
|
|
257
257
|
args: [
|
|
258
258
|
{ name: 'prompt', positional: true, type: 'string', required: true, help: 'Image generation prompt' },
|
|
259
259
|
{ name: 'timeout', type: 'int', default: 240, help: 'Max seconds to wait for the image (default: 240)' },
|
package/clis/grok/new.js
CHANGED
package/clis/grok/read.js
CHANGED
|
@@ -15,7 +15,7 @@ cli({
|
|
|
15
15
|
domain: GROK_DOMAIN,
|
|
16
16
|
strategy: Strategy.COOKIE,
|
|
17
17
|
browser: true,
|
|
18
|
-
|
|
18
|
+
siteSession: 'persistent',
|
|
19
19
|
navigateBefore: false,
|
|
20
20
|
args: [
|
|
21
21
|
{ name: 'markdown', type: 'boolean', default: false, help: 'Emit assistant replies as markdown' },
|
package/clis/grok/send.js
CHANGED
|
@@ -18,7 +18,7 @@ cli({
|
|
|
18
18
|
domain: GROK_DOMAIN,
|
|
19
19
|
strategy: Strategy.COOKIE,
|
|
20
20
|
browser: true,
|
|
21
|
-
|
|
21
|
+
siteSession: 'persistent',
|
|
22
22
|
navigateBefore: false,
|
|
23
23
|
args: [
|
|
24
24
|
{ name: 'prompt', required: true, positional: true, help: 'Prompt to send to Grok' },
|
package/clis/grok/status.js
CHANGED
|
@@ -17,7 +17,7 @@ cli({
|
|
|
17
17
|
await requireNotebooklmSession(page);
|
|
18
18
|
const state = await getNotebooklmPageState(page);
|
|
19
19
|
if (state.kind !== 'notebook') {
|
|
20
|
-
throw new EmptyResultError('opencli notebooklm current', 'No NotebookLM notebook is open in the
|
|
20
|
+
throw new EmptyResultError('opencli notebooklm current', 'No NotebookLM notebook is open in the adapter session. Run `opencli notebooklm open <notebook>` first.');
|
|
21
21
|
}
|
|
22
22
|
const current = await readCurrentNotebooklm(page);
|
|
23
23
|
if (!current) {
|