Skip to content

Commit 4a0b805

Browse files
authored
fix: batch quality improvements — dedupe completion, unify logging, fix docs (#945)
* fix: batch quality improvements — dedupe completion, unify logging, fix docs 1. Extract shared completion code (BUILTIN_COMMANDS + shell scripts) into completion-shared.ts, eliminating duplication between completion.ts and completion-fast.ts. 2. Replace console.error/warn/log with log.* from logger.ts in: - daemon.ts (7 occurrences) - runtime.ts (1 occurrence) - cli.ts browserAction error handler (3 occurrences) - base-page.ts snapshot fallback (1 occurrence) - download/index.ts cookie warning (1 occurrence) - commands/daemon.ts (2 occurrences) 3. Fix Node version in build-extension.yml: 20 → 22 (matches package.json >=21) 4. Fix error handling consistency: tap.ts now throws CliError instead of bare Error 5. Remove 31 duplicate rows in docs/adapters/index.md (grok, gemini, yuanbao, notebooklm, doubao, weread + 25 more entries duplicated without .md suffix) 6. Update skill version: opencli-usage SKILL.md 1.6.9 → 1.7.0, adapter count 79 → 87 * fix: update daemon.test.ts to match logger migration Tests now spy on process.stderr.write (used by log.*) instead of console.log/console.error (no longer used by daemonStop). * fix: address review feedback on PR #945 1. base-page.ts: restore DEBUG_SNAPSHOT env guard — log.debug uses a different env var (DEBUG=opencli), so keep the original gate to avoid breaking existing users. 2. daemon.ts: remove dead `prefix` variable left over from console.error migration.
1 parent 575986c commit 4a0b805

14 files changed

Lines changed: 118 additions & 178 deletions

File tree

.github/workflows/build-extension.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
- name: Setup Node.js
2727
uses: actions/setup-node@v6
2828
with:
29-
node-version: 20
29+
node-version: 22
3030
cache: 'npm'
3131
cache-dependency-path: extension/package-lock.json
3232

docs/adapters/index.md

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -61,37 +61,6 @@ Run `opencli list` for the live registry.
6161
| **[douyin](./browser/douyin.md)** | `profile` `videos` `user-videos` `activities` `collections` `hashtag` `location` `stats` `publish` `draft` `drafts` `delete` `update` | 🔐 Browser |
6262
| **[xianyu](./browser/xianyu.md)** | `search` `item` `chat` | 🔐 Browser |
6363
| **[quark](./browser/quark.md)** | `ls` `mkdir` `mv` `rename` `rm` `save` `share-tree` | 🔐 Browser |
64-
| **[grok](./browser/grok)** | `ask` | 🔐 Browser |
65-
| **[gemini](./browser/gemini)** | `new` `ask` `image` `deep-research` `deep-research-result` | 🔐 Browser |
66-
| **[yuanbao](./browser/yuanbao)** | `new` `ask` | 🔐 Browser |
67-
| **[notebooklm](./browser/notebooklm)** | `status` `list` `open` `current` `get` `source-list` `source-get` `source-fulltext` `source-guide` `history` `note-list` `notes-get` `summary` | 🔐 Browser |
68-
| **[doubao](./browser/doubao)** | `status` `new` `send` `read` `ask` `history` `detail` `meeting-summary` `meeting-transcript` | 🔐 Browser |
69-
| **[weread](./browser/weread)** | `shelf` `search` `book` `ranking` `notebooks` `highlights` `notes` | 🔐 Browser |
70-
| **[douban](./browser/douban.md)** | `search` `top250` `subject` `photos` `download` `marks` `reviews` `movie-hot` `book-hot` | 🔐 Browser |
71-
| **[facebook](./browser/facebook.md)** | `feed` `profile` `search` `friends` `groups` `events` `notifications` `memories` `add-friend` `join-group` | 🔐 Browser |
72-
| **[imdb](./browser/imdb.md)** | `search` `title` `top` `trending` `person` `reviews` | 🌐 / 🔐 |
73-
| **[instagram](./browser/instagram.md)** | `explore` `profile` `search` `user` `followers` `following` `follow` `unfollow` `like` `unlike` `comment` `save` `unsave` `saved` | 🔐 Browser |
74-
| **[medium](./browser/medium.md)** | `feed` `search` `user` | 🔐 Browser |
75-
| **[sinablog](./browser/sinablog.md)** | `hot` `search` `article` `user` | 🔐 Browser |
76-
| **[substack](./browser/substack.md)** | `feed` `search` `publication` | 🔐 Browser |
77-
| **[pixiv](./browser/pixiv.md)** | `ranking` `search` `user` `illusts` `detail` `download` | 🔐 Browser |
78-
| **[tiktok](./browser/tiktok.md)** | `explore` `search` `profile` `user` `following` `follow` `unfollow` `like` `unlike` `comment` `save` `unsave` `live` `notifications` `friends` | 🔐 Browser |
79-
| **[google](./browser/google.md)** | `news` `search` `suggest` `trends` | 🌐 / 🔐 |
80-
| **[jd](./browser/jd.md)** | `item` | 🔐 Browser |
81-
| **[amazon](./browser/amazon.md)** | `bestsellers` `search` `product` `offer` `discussion` `movers-shakers` `new-releases` | 🔐 Browser |
82-
| **[1688](./browser/1688.md)** | `search` `item` `assets` `download` `store` | 🔐 Browser |
83-
| **[gitee](./browser/gitee.md)** | `trending` `search` `user` | 🌐 / 🔐 |
84-
| **[web](./browser/web.md)** | `read` | 🔐 Browser |
85-
| **[weixin](./browser/weixin.md)** | `download` | 🔐 Browser |
86-
| **[36kr](./browser/36kr.md)** | `news` `hot` `search` `article` | 🌐 / 🔐 |
87-
| **[producthunt](./browser/producthunt.md)** | `posts` `today` `hot` `browse` | 🌐 / 🔐 |
88-
| **[ones](./browser/ones.md)** | `login` `me` `token-info` `tasks` `my-tasks` `task` `worklog` `logout` | 🔐 Browser Bridge + `ONES_BASE_URL` |
89-
| **[band](./browser/band.md)** | `bands` `posts` `post` `mentions` | 🔐 Browser |
90-
| **[zsxq](./browser/zsxq.md)** | `groups` `dynamics` `topics` `topic` `search` | 🔐 Browser |
91-
| **[bluesky](./browser/bluesky.md)** | `search` `profile` `user` `feeds` `followers` `following` `thread` `trending` `starter-packs` | 🌐 Public |
92-
| **[douyin](./browser/douyin.md)** | `profile` `videos` `user-videos` `activities` `collections` `hashtag` `location` `stats` `publish` `draft` `drafts` `delete` `update` | 🔐 Browser |
93-
| **[xianyu](./browser/xianyu.md)** | `search` `item` `chat` | 🔐 Browser |
94-
| **[quark](./browser/quark.md)** | `ls` `mkdir` `mv` `rename` `rm` `save` `share-tree` | 🔐 Browser |
9564

9665
## Public API Adapters
9766

skills/opencli-usage/SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
name: opencli-usage
3-
description: "Use when running OpenCLI commands to interact with websites (Bilibili, Twitter, Reddit, Xiaohongshu, etc.), desktop apps (Cursor, Notion), or public APIs (HackerNews, arXiv). Covers installation, command reference, and output formats for 79+ adapters."
4-
version: 1.6.9
3+
description: "Use when running OpenCLI commands to interact with websites (Bilibili, Twitter, Reddit, Xiaohongshu, etc.), desktop apps (Cursor, Notion), or public APIs (HackerNews, arXiv). Covers installation, command reference, and output formats for 87+ adapters."
4+
version: 1.7.0
55
author: jackwener
66
tags: [opencli, cli, browser, web, chrome-extension, cdp, bilibili, twitter, reddit, xiaohongshu, github, youtube, AI, agent, automation]
77
---

src/browser/base-page.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
waitForDomStableJs,
2525
} from './dom-helpers.js';
2626
import { formatSnapshot } from '../snapshotFormatter.js';
27+
import { log } from '../logger.js';
2728

2829
export abstract class BasePage implements IPage {
2930
protected _lastUrl: string | null = null;
@@ -155,7 +156,7 @@ export abstract class BasePage implements IPage {
155156
} catch (err) {
156157
// Log snapshot failure for debugging, then fallback to basic accessibility tree
157158
if (process.env.DEBUG_SNAPSHOT) {
158-
console.error('[snapshot] DOM snapshot failed, falling back to accessibility tree:', (err as Error)?.message?.slice(0, 200));
159+
log.debug(`[snapshot] DOM snapshot failed, falling back to accessibility tree: ${(err as Error)?.message?.slice(0, 200)}`);
159160
}
160161
return this._basicSnapshot(opts);
161162
}

src/cli.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { loadExternalClis, executeExternalCli, installExternalCli, registerExter
2121
import { registerAllCommands } from './commanderAdapter.js';
2222
import { EXIT_CODES, getErrorMessage } from './errors.js';
2323
import { daemonStop } from './commands/daemon.js';
24+
import { log } from './logger.js';
2425

2526
const CLI_FILE = fileURLToPath(import.meta.url);
2627

@@ -300,11 +301,11 @@ export function createProgram(BUILTIN_CLIS: string, USER_CLIS: string): Command
300301
} catch (err) {
301302
const msg = getErrorMessage(err);
302303
if (msg.includes('Extension not connected') || msg.includes('Daemon')) {
303-
console.error(`Browser not connected. Run 'opencli doctor' to diagnose.`);
304+
log.error(`Browser not connected. Run 'opencli doctor' to diagnose.`);
304305
} else if (msg.includes('attach failed') || msg.includes('chrome-extension://')) {
305-
console.error(`Browser attach failed — another extension may be interfering. Try disabling 1Password.`);
306+
log.error(`Browser attach failed — another extension may be interfering. Try disabling 1Password.`);
306307
} else {
307-
console.error(`Error: ${msg}`);
308+
log.error(msg);
308309
}
309310
process.exitCode = EXIT_CODES.GENERIC_ERROR;
310311
}

src/commands/daemon.test.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,10 @@ vi.mock('../browser/daemon-client.js', () => ({
1616
import { daemonStop } from './daemon.js';
1717

1818
describe('daemonStop', () => {
19-
let logSpy: ReturnType<typeof vi.spyOn>;
20-
let errorSpy: ReturnType<typeof vi.spyOn>;
19+
let stderrSpy: ReturnType<typeof vi.spyOn>;
2120

2221
beforeEach(() => {
23-
logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
24-
errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
22+
stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
2523
fetchDaemonStatusMock.mockReset();
2624
requestDaemonShutdownMock.mockReset();
2725
});
@@ -35,7 +33,7 @@ describe('daemonStop', () => {
3533

3634
await daemonStop();
3735

38-
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('not running'));
36+
expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('not running'));
3937
});
4038

4139
it('sends shutdown and reports success', async () => {
@@ -53,7 +51,7 @@ describe('daemonStop', () => {
5351
await daemonStop();
5452

5553
expect(requestDaemonShutdownMock).toHaveBeenCalledTimes(1);
56-
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Daemon stopped'));
54+
expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('Daemon stopped'));
5755
});
5856

5957
it('reports failure when shutdown request fails', async () => {
@@ -70,6 +68,6 @@ describe('daemonStop', () => {
7068

7169
await daemonStop();
7270

73-
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to stop daemon'));
71+
expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to stop daemon'));
7472
});
7573
});

src/commands/daemon.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,21 @@
33
* opencli daemon stop — graceful shutdown
44
*/
55

6-
import { styleText } from 'node:util';
76
import { fetchDaemonStatus, requestDaemonShutdown } from '../browser/daemon-client.js';
7+
import { log } from '../logger.js';
88

99
export async function daemonStop(): Promise<void> {
1010
const status = await fetchDaemonStatus();
1111
if (!status) {
12-
console.log(styleText('dim', 'Daemon is not running.'));
12+
log.info('Daemon is not running.');
1313
return;
1414
}
1515

1616
const ok = await requestDaemonShutdown();
1717
if (ok) {
18-
console.log(styleText('green', 'Daemon stopped.'));
18+
log.success('Daemon stopped.');
1919
} else {
20-
console.error(styleText('red', 'Failed to stop daemon.'));
20+
log.error('Failed to stop daemon.');
2121
process.exitCode = 1;
2222
}
2323
}

src/completion-fast.ts

Lines changed: 7 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,12 @@
66
*/
77

88
import * as fs from 'node:fs';
9-
10-
const BUILTIN_COMMANDS = [
11-
'list',
12-
'validate',
13-
'verify',
14-
'explore',
15-
'probe',
16-
'synthesize',
17-
'generate',
18-
'cascade',
19-
'doctor',
20-
'plugin',
21-
'install',
22-
'register',
23-
'completion',
24-
];
9+
import {
10+
BUILTIN_COMMANDS,
11+
bashCompletionScript,
12+
zshCompletionScript,
13+
fishCompletionScript,
14+
} from './completion-shared.js';
2515

2616
interface ManifestCompletionEntry {
2717
site: string;
@@ -83,48 +73,7 @@ export function getCompletionsFromManifest(words: string[], cursor: number, mani
8373
return [];
8474
}
8575

86-
// ── Shell script generators (pure strings, no registry dependency) ───────
87-
88-
export function bashCompletionScript(): string {
89-
return `# Bash completion for opencli
90-
# Add to ~/.bashrc: eval "$(opencli completion bash)"
91-
_opencli_completions() {
92-
local cur words cword
93-
_get_comp_words_by_ref -n : cur words cword
94-
95-
local completions
96-
completions=$(opencli --get-completions --cursor "$cword" "\${words[@]:1}" 2>/dev/null)
97-
98-
COMPREPLY=( $(compgen -W "$completions" -- "$cur") )
99-
__ltrim_colon_completions "$cur"
100-
}
101-
complete -F _opencli_completions opencli
102-
`;
103-
}
104-
105-
export function zshCompletionScript(): string {
106-
return `# Zsh completion for opencli
107-
# Add to ~/.zshrc: eval "$(opencli completion zsh)"
108-
_opencli() {
109-
local -a completions
110-
local cword=$((CURRENT - 1))
111-
completions=(\${(f)"$(opencli --get-completions --cursor "$cword" "\${words[@]:1}" 2>/dev/null)"})
112-
compadd -a completions
113-
}
114-
compdef _opencli opencli
115-
`;
116-
}
117-
118-
export function fishCompletionScript(): string {
119-
return `# Fish completion for opencli
120-
# Add to ~/.config/fish/config.fish: opencli completion fish | source
121-
complete -c opencli -f -a '(
122-
set -l tokens (commandline -cop)
123-
set -l cursor (count (commandline -cop))
124-
opencli --get-completions --cursor $cursor $tokens[2..] 2>/dev/null
125-
)'
126-
`;
127-
}
76+
// ── Shell script generators (re-exported from shared, no registry dependency) ───────
12877

12978
const SHELL_SCRIPTS: Record<string, () => string> = {
13079
bash: bashCompletionScript,

src/completion-shared.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* Shared constants and shell script generators for tab-completion.
3+
*
4+
* This module MUST remain lightweight (no registry, no discovery imports).
5+
* Both completion.ts (full path) and completion-fast.ts (manifest path) import from here.
6+
*/
7+
8+
/**
9+
* Built-in (non-dynamic) top-level commands.
10+
*/
11+
export const BUILTIN_COMMANDS = [
12+
'list',
13+
'validate',
14+
'verify',
15+
'explore',
16+
'probe', // alias for explore
17+
'synthesize',
18+
'generate',
19+
'cascade',
20+
'doctor',
21+
'plugin',
22+
'install',
23+
'register',
24+
'completion',
25+
];
26+
27+
// ── Shell script generators ────────────────────────────────────────────────
28+
29+
export function bashCompletionScript(): string {
30+
return `# Bash completion for opencli
31+
# Add to ~/.bashrc: eval "$(opencli completion bash)"
32+
_opencli_completions() {
33+
local cur words cword
34+
_get_comp_words_by_ref -n : cur words cword
35+
36+
local completions
37+
completions=$(opencli --get-completions --cursor "$cword" "\${words[@]:1}" 2>/dev/null)
38+
39+
COMPREPLY=( $(compgen -W "$completions" -- "$cur") )
40+
__ltrim_colon_completions "$cur"
41+
}
42+
complete -F _opencli_completions opencli
43+
`;
44+
}
45+
46+
export function zshCompletionScript(): string {
47+
return `# Zsh completion for opencli
48+
# Add to ~/.zshrc: eval "$(opencli completion zsh)"
49+
_opencli() {
50+
local -a completions
51+
local cword=$((CURRENT - 1))
52+
completions=(\${(f)"$(opencli --get-completions --cursor "$cword" "\${words[@]:1}" 2>/dev/null)"})
53+
compadd -a completions
54+
}
55+
compdef _opencli opencli
56+
`;
57+
}
58+
59+
export function fishCompletionScript(): string {
60+
return `# Fish completion for opencli
61+
# Add to ~/.config/fish/config.fish: opencli completion fish | source
62+
complete -c opencli -f -a '(
63+
set -l tokens (commandline -cop)
64+
set -l cursor (count (commandline -cop))
65+
opencli --get-completions --cursor $cursor $tokens[2..] 2>/dev/null
66+
)'
67+
`;
68+
}

0 commit comments

Comments
 (0)