@jackwener/opencli 1.7.15 → 1.7.17
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 +15 -13
- package/README.zh-CN.md +15 -12
- package/cli-manifest.json +165 -209
- package/clis/chatgpt/ask.js +3 -2
- package/clis/chatgpt/commands.test.js +2 -2
- package/clis/chatgpt/detail.js +7 -2
- 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 +3 -2
- package/clis/chatgpt/send.js +3 -2
- package/clis/chatgpt/status.js +1 -1
- package/clis/chatgpt/utils.js +259 -25
- package/clis/chatgpt/utils.test.js +166 -2
- package/clis/claude/ask.js +23 -8
- package/clis/claude/detail.js +10 -3
- package/clis/claude/history.js +1 -1
- package/clis/claude/new.js +9 -3
- package/clis/claude/read.js +3 -2
- package/clis/claude/send.js +9 -4
- package/clis/claude/status.js +1 -1
- package/clis/claude/utils.js +27 -4
- package/clis/deepseek/ask.js +22 -9
- package/clis/deepseek/detail.js +10 -2
- package/clis/deepseek/history.js +1 -1
- package/clis/deepseek/new.js +14 -3
- package/clis/deepseek/read.js +3 -2
- package/clis/deepseek/send.js +1 -1
- package/clis/deepseek/status.js +1 -1
- package/clis/deepseek/utils.js +8 -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/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/linkedin/search.js +8 -11
- package/clis/maimai/search-talents.js +10 -6
- 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/openreview/author.js +58 -0
- package/clis/openreview/openreview.test.js +83 -1
- package/clis/openreview/utils.js +14 -0
- 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 -0
- package/clis/reddit/frontpage.js +1 -0
- package/clis/reddit/popular.js +1 -0
- package/clis/reddit/read.js +2 -0
- package/clis/reddit/read.test.js +4 -0
- package/clis/reddit/save.js +1 -0
- package/clis/reddit/saved.js +1 -0
- package/clis/reddit/search.js +1 -0
- package/clis/reddit/subreddit.js +1 -0
- package/clis/reddit/subscribe.js +1 -0
- package/clis/reddit/upvote.js +1 -0
- package/clis/reddit/upvoted.js +1 -0
- package/clis/reddit/user-comments.js +1 -0
- package/clis/reddit/user-posts.js +1 -0
- package/clis/reddit/user.js +1 -0
- package/clis/twitter/article.js +7 -4
- package/clis/twitter/bookmark-folder.js +3 -5
- package/clis/twitter/bookmark-folder.test.js +5 -2
- package/clis/twitter/bookmark-folders.js +3 -5
- package/clis/twitter/bookmark-folders.test.js +3 -1
- package/clis/twitter/bookmarks.js +3 -5
- package/clis/twitter/download.js +1 -0
- package/clis/twitter/followers.js +1 -0
- package/clis/twitter/following.js +3 -6
- package/clis/twitter/following.test.js +2 -1
- package/clis/twitter/likes.js +3 -5
- package/clis/twitter/list-add.js +4 -3
- package/clis/twitter/list-add.test.js +23 -1
- package/clis/twitter/list-remove.js +4 -3
- package/clis/twitter/list-remove.test.js +23 -1
- package/clis/twitter/list-tweets.js +3 -5
- package/clis/twitter/lists.js +3 -5
- package/clis/twitter/notifications.js +1 -0
- package/clis/twitter/profile.js +7 -4
- package/clis/twitter/search.js +1 -0
- package/clis/twitter/thread.js +5 -7
- package/clis/twitter/timeline.js +5 -7
- package/clis/twitter/trending.js +4 -4
- package/clis/twitter/tweets.js +3 -6
- package/clis/youtube/like.js +6 -2
- package/clis/youtube/subscribe.js +6 -2
- package/clis/youtube/unlike.js +6 -2
- package/clis/youtube/unsubscribe.js +6 -2
- package/clis/youtube/utils.js +19 -13
- package/clis/youtube/utils.test.js +17 -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 +4 -1
- package/dist/src/browser/bridge.js +3 -1
- package/dist/src/browser/cdp.d.ts +4 -1
- package/dist/src/browser/daemon-client.d.ts +9 -16
- package/dist/src/browser/daemon-client.js +8 -9
- package/dist/src/browser/daemon-client.test.js +10 -0
- 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 +9 -7
- package/dist/src/browser/page.js +27 -16
- package/dist/src/browser/page.test.js +60 -30
- package/dist/src/build-manifest.js +1 -1
- package/dist/src/cli.js +91 -125
- package/dist/src/cli.test.js +293 -180
- package/dist/src/commanderAdapter.js +9 -0
- package/dist/src/discovery.js +1 -1
- package/dist/src/doctor.d.ts +0 -4
- package/dist/src/doctor.js +8 -72
- package/dist/src/doctor.test.js +26 -97
- package/dist/src/execution.d.ts +3 -0
- package/dist/src/execution.js +47 -23
- package/dist/src/execution.test.js +68 -45
- package/dist/src/external-clis.yaml +24 -0
- package/dist/src/help.d.ts +1 -0
- package/dist/src/help.js +36 -1
- package/dist/src/main.js +0 -29
- 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 +10 -2
- package/dist/src/runtime.js +4 -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
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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
-
import { CLAUDE_DOMAIN, CLAUDE_URL, ensureClaudeComposer } from './utils.js';
|
|
2
|
+
import { CLAUDE_DOMAIN, CLAUDE_URL, COMPOSER_SELECTOR, ensureClaudeComposer } from './utils.js';
|
|
3
3
|
|
|
4
4
|
export const newCommand = cli({
|
|
5
5
|
site: 'claude',
|
|
@@ -9,14 +9,20 @@ export const newCommand = cli({
|
|
|
9
9
|
domain: CLAUDE_DOMAIN,
|
|
10
10
|
strategy: Strategy.COOKIE,
|
|
11
11
|
browser: true,
|
|
12
|
-
|
|
12
|
+
siteSession: 'persistent',
|
|
13
13
|
navigateBefore: false,
|
|
14
14
|
args: [],
|
|
15
15
|
columns: ['Status'],
|
|
16
16
|
|
|
17
17
|
func: async (page) => {
|
|
18
18
|
await page.goto(CLAUDE_URL);
|
|
19
|
-
|
|
19
|
+
// Wait for the composer to mount instead of a fixed 2 s sleep. If it
|
|
20
|
+
// never mounts, swallow and let ensureClaudeComposer surface a typed error.
|
|
21
|
+
try {
|
|
22
|
+
await page.wait({ selector: COMPOSER_SELECTOR, timeout: 8 });
|
|
23
|
+
} catch {
|
|
24
|
+
// Login or error page — ensureClaudeComposer below throws AuthRequiredError / CommandExecutionError.
|
|
25
|
+
}
|
|
20
26
|
await ensureClaudeComposer(page, 'Claude new requires a logged-in Claude session with a visible composer.');
|
|
21
27
|
return [{ Status: 'New chat started' }];
|
|
22
28
|
},
|
package/clis/claude/read.js
CHANGED
|
@@ -10,14 +10,15 @@ export const readCommand = 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
|
columns: ['Index', 'Role', 'Text'],
|
|
17
17
|
|
|
18
18
|
func: async (page) => {
|
|
19
|
+
// ensureOnClaude now waits for the composer selector; the previous post-nav
|
|
20
|
+
// 3 s settle is covered by that event-based wait.
|
|
19
21
|
await ensureOnClaude(page);
|
|
20
|
-
await page.wait(3);
|
|
21
22
|
await ensureClaudeLogin(page, 'Claude read requires a logged-in Claude session.');
|
|
22
23
|
const messages = await getVisibleMessages(page);
|
|
23
24
|
if (messages.length > 0) return messages;
|
package/clis/claude/send.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
2
|
import { CommandExecutionError } from '@jackwener/opencli/errors';
|
|
3
|
-
import { CLAUDE_DOMAIN, CLAUDE_URL, ensureOnClaude, sendMessage, parseBoolFlag, withRetry, ensureClaudeComposer, requireNonEmptyPrompt } from './utils.js';
|
|
3
|
+
import { CLAUDE_DOMAIN, CLAUDE_URL, COMPOSER_SELECTOR, ensureOnClaude, sendMessage, parseBoolFlag, withRetry, ensureClaudeComposer, requireNonEmptyPrompt } from './utils.js';
|
|
4
4
|
|
|
5
5
|
export const sendCommand = cli({
|
|
6
6
|
site: 'claude',
|
|
@@ -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' },
|
|
@@ -23,10 +23,15 @@ export const sendCommand = cli({
|
|
|
23
23
|
|
|
24
24
|
if (parseBoolFlag(kwargs.new)) {
|
|
25
25
|
await page.goto(CLAUDE_URL);
|
|
26
|
-
|
|
26
|
+
try {
|
|
27
|
+
await page.wait({ selector: COMPOSER_SELECTOR, timeout: 8 });
|
|
28
|
+
} catch {
|
|
29
|
+
// Composer didn't mount; ensureClaudeComposer below surfaces a typed error.
|
|
30
|
+
}
|
|
27
31
|
} else {
|
|
32
|
+
// ensureOnClaude now waits for the composer selector; the previous
|
|
33
|
+
// post-nav 2 s settle is covered by that event-based wait.
|
|
28
34
|
await ensureOnClaude(page);
|
|
29
|
-
await page.wait(2);
|
|
30
35
|
}
|
|
31
36
|
await withRetry(() => ensureClaudeComposer(page, 'Claude send requires a visible composer on the current page.'));
|
|
32
37
|
|
package/clis/claude/status.js
CHANGED
package/clis/claude/utils.js
CHANGED
|
@@ -26,7 +26,14 @@ export async function isOnClaude(page) {
|
|
|
26
26
|
export async function ensureOnClaude(page) {
|
|
27
27
|
if (await isOnClaude(page)) return false;
|
|
28
28
|
await page.goto(CLAUDE_URL);
|
|
29
|
-
|
|
29
|
+
// Wait for the composer textarea instead of a fixed 3 s sleep. On the login
|
|
30
|
+
// page it never mounts; swallow the timeout so callers (read / detail /
|
|
31
|
+
// send) can still inspect page state and produce typed errors.
|
|
32
|
+
try {
|
|
33
|
+
await page.wait({ selector: COMPOSER_SELECTOR, timeout: 8 });
|
|
34
|
+
} catch {
|
|
35
|
+
// Login or error page — downstream ensureClaudeLogin / ensureClaudeComposer surfaces a typed error.
|
|
36
|
+
}
|
|
30
37
|
return true;
|
|
31
38
|
}
|
|
32
39
|
|
|
@@ -111,7 +118,13 @@ export async function getVisibleMessages(page) {
|
|
|
111
118
|
export async function getConversationList(page) {
|
|
112
119
|
if (!(await isOnClaude(page)) || !(await page.evaluate('window.location.href') || '').includes('/recents')) {
|
|
113
120
|
await page.goto('https://claude.ai/recents');
|
|
114
|
-
|
|
121
|
+
// Recents list mounts <a href="/chat/...">; an empty history is also
|
|
122
|
+
// valid (returns []), so swallow the timeout instead of raising.
|
|
123
|
+
try {
|
|
124
|
+
await page.wait({ selector: 'a[href*="/chat/"]', timeout: 8 });
|
|
125
|
+
} catch {
|
|
126
|
+
// Empty history or login page — downstream evaluate returns [].
|
|
127
|
+
}
|
|
115
128
|
}
|
|
116
129
|
const items = await page.evaluate(`(() => {
|
|
117
130
|
var links = Array.from(document.querySelectorAll('a[href*="/chat/"]'));
|
|
@@ -147,7 +160,12 @@ export async function selectModel(page, modelName) {
|
|
|
147
160
|
if (!opened?.ok) return opened;
|
|
148
161
|
if (!opened.opened) return opened;
|
|
149
162
|
|
|
150
|
-
|
|
163
|
+
// Wait for the dropdown menu items to mount instead of a fixed 0.6 s sleep.
|
|
164
|
+
try {
|
|
165
|
+
await page.wait({ selector: 'div[role="menuitemradio"]', timeout: 3 });
|
|
166
|
+
} catch {
|
|
167
|
+
// Dropdown didn't open — next evaluate finds no target and returns { ok: false }.
|
|
168
|
+
}
|
|
151
169
|
|
|
152
170
|
return page.evaluate(`(() => {
|
|
153
171
|
var items = Array.from(document.querySelectorAll('div[role="menuitemradio"]'));
|
|
@@ -175,7 +193,12 @@ export async function setAdaptiveThinking(page, enabled) {
|
|
|
175
193
|
})()`);
|
|
176
194
|
if (!opened?.ok) return { ok: false };
|
|
177
195
|
|
|
178
|
-
|
|
196
|
+
// Wait for the dropdown menu items to mount instead of a fixed 0.6 s sleep.
|
|
197
|
+
try {
|
|
198
|
+
await page.wait({ selector: 'div[role="menuitem"]', timeout: 3 });
|
|
199
|
+
} catch {
|
|
200
|
+
// Dropdown didn't open — next evaluate finds no target and returns { ok: false }.
|
|
201
|
+
}
|
|
179
202
|
|
|
180
203
|
return page.evaluate(`(() => {
|
|
181
204
|
var items = Array.from(document.querySelectorAll('div[role="menuitem"]'));
|
package/clis/deepseek/ask.js
CHANGED
|
@@ -3,7 +3,7 @@ import { CliError, CommandExecutionError, EXIT_CODES } from '@jackwener/opencli/
|
|
|
3
3
|
import {
|
|
4
4
|
DEEPSEEK_DOMAIN, DEEPSEEK_URL, ensureOnDeepSeek, selectModel, setFeature,
|
|
5
5
|
sendMessage, sendWithFile, getBubbleCount, waitForResponse, parseBoolFlag, withRetry,
|
|
6
|
-
pickResumeUrl,
|
|
6
|
+
pickResumeUrl, TEXTAREA_SELECTOR,
|
|
7
7
|
} from './utils.js';
|
|
8
8
|
|
|
9
9
|
export const askCommand = cli({
|
|
@@ -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' },
|
|
@@ -35,7 +35,13 @@ export const askCommand = cli({
|
|
|
35
35
|
|
|
36
36
|
if (parseBoolFlag(kwargs.new)) {
|
|
37
37
|
await page.goto(DEEPSEEK_URL);
|
|
38
|
-
|
|
38
|
+
// Wait for the composer to mount instead of a fixed 3 s sleep.
|
|
39
|
+
try {
|
|
40
|
+
await page.wait({ selector: TEXTAREA_SELECTOR, timeout: 8 });
|
|
41
|
+
} catch {
|
|
42
|
+
// Selector still missing → downstream selectModel/sendMessage
|
|
43
|
+
// will surface the failure with a typed error.
|
|
44
|
+
}
|
|
39
45
|
} else {
|
|
40
46
|
const navigated = await ensureOnDeepSeek(page);
|
|
41
47
|
if (navigated) {
|
|
@@ -49,12 +55,15 @@ export const askCommand = cli({
|
|
|
49
55
|
);
|
|
50
56
|
}
|
|
51
57
|
await page.goto(resumeUrl);
|
|
52
|
-
|
|
58
|
+
try {
|
|
59
|
+
await page.wait({ selector: TEXTAREA_SELECTOR, timeout: 5 });
|
|
60
|
+
} catch {
|
|
61
|
+
// Conversation page may still be loading; subsequent steps
|
|
62
|
+
// will retry or report.
|
|
63
|
+
}
|
|
53
64
|
}
|
|
54
65
|
}
|
|
55
66
|
|
|
56
|
-
await page.wait(2);
|
|
57
|
-
|
|
58
67
|
// Model selector is only available on the new-chat page, not inside
|
|
59
68
|
// an existing conversation. Skip it when we resumed a prior thread.
|
|
60
69
|
const currentUrl = await page.evaluate('window.location.href') || '';
|
|
@@ -76,7 +85,9 @@ export const askCommand = cli({
|
|
|
76
85
|
if (!modelResult?.ok) {
|
|
77
86
|
throw new CommandExecutionError(`Could not switch to ${wantModel} model`);
|
|
78
87
|
}
|
|
79
|
-
|
|
88
|
+
// The 0.5 s settle previously here was redundant: each subsequent
|
|
89
|
+
// step (setFeature, sendMessage) issues a fresh CDP eval, giving
|
|
90
|
+
// React more than enough time to flush the toggle's state update.
|
|
80
91
|
}
|
|
81
92
|
|
|
82
93
|
const thinkResult = await withRetry(() => setFeature(page, 'DeepThink', wantThink));
|
|
@@ -102,7 +113,8 @@ export const askCommand = cli({
|
|
|
102
113
|
}
|
|
103
114
|
}
|
|
104
115
|
|
|
105
|
-
|
|
116
|
+
// No settle wait after toggles: the next CDP eval below already gives
|
|
117
|
+
// React time to flush the aria-checked state.
|
|
106
118
|
|
|
107
119
|
if (kwargs.file) {
|
|
108
120
|
const baseline = await withRetry(() => getBubbleCount(page));
|
|
@@ -115,7 +127,8 @@ export const askCommand = cli({
|
|
|
115
127
|
// SPA navigates after send; "Promise was collected" means send succeeded
|
|
116
128
|
if (!String(err?.message || err).includes('Promise was collected')) throw err;
|
|
117
129
|
}
|
|
118
|
-
|
|
130
|
+
// waitForResponse polls every 3 s for new bubbles, so the previous
|
|
131
|
+
// 3 s settle here was a redundant sleep on top of the first poll.
|
|
119
132
|
const result = await waitForResponse(page, baseline, prompt, timeoutMs, wantThink);
|
|
120
133
|
if (!result) {
|
|
121
134
|
return [{ response: `[NO RESPONSE] No reply within ${kwargs.timeout}s.` }];
|
package/clis/deepseek/detail.js
CHANGED
|
@@ -2,6 +2,7 @@ import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
|
2
2
|
import { EmptyResultError } from '@jackwener/opencli/errors';
|
|
3
3
|
import {
|
|
4
4
|
DEEPSEEK_DOMAIN,
|
|
5
|
+
MESSAGE_SELECTOR,
|
|
5
6
|
ensureOnDeepSeek,
|
|
6
7
|
getVisibleMessages,
|
|
7
8
|
parseDeepSeekConversationId,
|
|
@@ -15,7 +16,7 @@ export const detailCommand = cli({
|
|
|
15
16
|
domain: DEEPSEEK_DOMAIN,
|
|
16
17
|
strategy: Strategy.COOKIE,
|
|
17
18
|
browser: true,
|
|
18
|
-
|
|
19
|
+
siteSession: 'persistent',
|
|
19
20
|
navigateBefore: false,
|
|
20
21
|
args: [
|
|
21
22
|
{ name: 'id', required: true, positional: true, help: 'Conversation ID (UUID) or full /a/chat/s/<id> URL' },
|
|
@@ -25,7 +26,14 @@ export const detailCommand = cli({
|
|
|
25
26
|
const id = parseDeepSeekConversationId(kwargs.id);
|
|
26
27
|
await ensureOnDeepSeek(page);
|
|
27
28
|
await page.goto(`https://chat.deepseek.com/a/chat/s/${id}`);
|
|
28
|
-
|
|
29
|
+
// Wait for at least one rendered bubble instead of a fixed 5 s sleep.
|
|
30
|
+
// Empty / invalid conversations fall through to the EmptyResultError
|
|
31
|
+
// below.
|
|
32
|
+
try {
|
|
33
|
+
await page.wait({ selector: MESSAGE_SELECTOR, timeout: 10 });
|
|
34
|
+
} catch {
|
|
35
|
+
// No bubble mounted within 10 s; treated as empty by the check below.
|
|
36
|
+
}
|
|
29
37
|
const messages = await getVisibleMessages(page);
|
|
30
38
|
if (messages.length === 0) {
|
|
31
39
|
throw new EmptyResultError(
|
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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
-
import {
|
|
2
|
+
import { CommandExecutionError } from '@jackwener/opencli/errors';
|
|
3
|
+
import { DEEPSEEK_DOMAIN, DEEPSEEK_URL, TEXTAREA_SELECTOR } from './utils.js';
|
|
3
4
|
|
|
4
5
|
export const newCommand = cli({
|
|
5
6
|
site: 'deepseek',
|
|
@@ -9,14 +10,24 @@ export const newCommand = cli({
|
|
|
9
10
|
domain: DEEPSEEK_DOMAIN,
|
|
10
11
|
strategy: Strategy.COOKIE,
|
|
11
12
|
browser: true,
|
|
12
|
-
|
|
13
|
+
siteSession: 'persistent',
|
|
13
14
|
navigateBefore: false,
|
|
14
15
|
args: [],
|
|
15
16
|
columns: ['Status'],
|
|
16
17
|
|
|
17
18
|
func: async (page) => {
|
|
18
19
|
await page.goto(DEEPSEEK_URL);
|
|
19
|
-
|
|
20
|
+
// Confirm the composer mounted before reporting success. The previous
|
|
21
|
+
// 2 s blind sleep would return "New chat started" even when the page
|
|
22
|
+
// was still loading or the user was logged out.
|
|
23
|
+
try {
|
|
24
|
+
await page.wait({ selector: TEXTAREA_SELECTOR, timeout: 8 });
|
|
25
|
+
} catch {
|
|
26
|
+
throw new CommandExecutionError(
|
|
27
|
+
'DeepSeek composer did not mount within 8 s',
|
|
28
|
+
'Verify you are logged into chat.deepseek.com.',
|
|
29
|
+
);
|
|
30
|
+
}
|
|
20
31
|
return [{ Status: 'New chat started' }];
|
|
21
32
|
},
|
|
22
33
|
});
|
package/clis/deepseek/read.js
CHANGED
|
@@ -9,14 +9,15 @@ export const readCommand = 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
|
columns: ['Role', 'Text'],
|
|
16
16
|
|
|
17
17
|
func: async (page) => {
|
|
18
|
+
// ensureOnDeepSeek already waits for the composer to mount; the
|
|
19
|
+
// follow-up 5 s sleep was redundant.
|
|
18
20
|
await ensureOnDeepSeek(page);
|
|
19
|
-
await page.wait(5);
|
|
20
21
|
const messages = await getVisibleMessages(page);
|
|
21
22
|
if (messages.length > 0) return messages;
|
|
22
23
|
return [{ Role: 'system', Text: 'No visible messages found.' }];
|
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/deepseek/utils.js
CHANGED
|
@@ -43,7 +43,14 @@ export async function isOnDeepSeek(page) {
|
|
|
43
43
|
export async function ensureOnDeepSeek(page) {
|
|
44
44
|
if (await isOnDeepSeek(page)) return false;
|
|
45
45
|
await page.goto(DEEPSEEK_URL);
|
|
46
|
-
|
|
46
|
+
// Wait for the composer textarea instead of a fixed 3 s sleep. On the login
|
|
47
|
+
// page it never mounts; swallow the timeout so callers (status / read /
|
|
48
|
+
// history) can still inspect page state.
|
|
49
|
+
try {
|
|
50
|
+
await page.wait({ selector: TEXTAREA_SELECTOR, timeout: 8 });
|
|
51
|
+
} catch {
|
|
52
|
+
// Login or error page — downstream will see hasTextarea=false / empty results.
|
|
53
|
+
}
|
|
47
54
|
return true;
|
|
48
55
|
}
|
|
49
56
|
|
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/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
package/clis/linkedin/search.js
CHANGED
|
@@ -245,23 +245,20 @@ async function fetchJobCards(page, input) {
|
|
|
245
245
|
const MAX_BATCH = 25;
|
|
246
246
|
const allJobs = [];
|
|
247
247
|
let offset = input.start;
|
|
248
|
+
// Read JSESSIONID directly from the cookie store via CDP — zero page.evaluate round-trip
|
|
249
|
+
const cookies = await page.getCookies({ url: 'https://www.linkedin.com' });
|
|
250
|
+
const jsession = cookies.find((c) => c.name === 'JSESSIONID')?.value;
|
|
251
|
+
if (!jsession) {
|
|
252
|
+
throw new AuthRequiredError(LINKEDIN_DOMAIN, 'LinkedIn JSESSIONID cookie not found. Please sign in to LinkedIn in the browser.');
|
|
253
|
+
}
|
|
254
|
+
const csrf = jsession.replace(/^"|"$/g, '');
|
|
248
255
|
while (allJobs.length < input.limit) {
|
|
249
256
|
const count = Math.min(MAX_BATCH, input.limit - allJobs.length);
|
|
250
257
|
const apiPath = buildVoyagerUrl(input, offset, count);
|
|
251
258
|
const batch = await page.evaluate(`(async () => {
|
|
252
|
-
const jsession = document.cookie.split(';').map(p => p.trim())
|
|
253
|
-
.find(p => p.startsWith('JSESSIONID='))?.slice('JSESSIONID='.length);
|
|
254
|
-
if (!jsession) {
|
|
255
|
-
return {
|
|
256
|
-
authRequired: true,
|
|
257
|
-
error: 'LinkedIn JSESSIONID cookie not found. Please sign in to LinkedIn in the browser.'
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const csrf = jsession.replace(/^"|"$/g, '');
|
|
262
259
|
const res = await fetch(${JSON.stringify(apiPath)}, {
|
|
263
260
|
credentials: 'include',
|
|
264
|
-
headers: { 'csrf-token': csrf, 'x-restli-protocol-version': '2.0.0' },
|
|
261
|
+
headers: { 'csrf-token': ${JSON.stringify(csrf)}, 'x-restli-protocol-version': '2.0.0' },
|
|
265
262
|
});
|
|
266
263
|
if (res.status === 401 || res.status === 403) {
|
|
267
264
|
const text = await res.text();
|