@jackwener/opencli 1.7.8 → 1.7.9
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 +612 -29
- 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/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 -0
- package/dist/src/browser/bridge.js +36 -9
- 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/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 +462 -32
- package/dist/src/cli.test.js +209 -2
- package/dist/src/commanderAdapter.js +17 -9
- package/dist/src/commanderAdapter.test.js +67 -2
- package/dist/src/commands/daemon.js +6 -0
- 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 +4 -6
- package/dist/src/doctor.js +80 -22
- package/dist/src/doctor.test.js +82 -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
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { DEFAULT_DAEMON_PORT } from '../constants.js';
|
|
7
7
|
import { sleep } from '../utils.js';
|
|
8
8
|
import { classifyBrowserError } from './errors.js';
|
|
9
|
+
import { resolveProfileContextId } from './profile.js';
|
|
9
10
|
const DAEMON_PORT = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
|
|
10
11
|
const DAEMON_URL = `http://127.0.0.1:${DAEMON_PORT}`;
|
|
11
12
|
const OPENCLI_HEADERS = { 'X-OpenCLI': '1' };
|
|
@@ -13,6 +14,16 @@ let _idCounter = 0;
|
|
|
13
14
|
function generateId() {
|
|
14
15
|
return `cmd_${process.pid}_${Date.now()}_${++_idCounter}`;
|
|
15
16
|
}
|
|
17
|
+
export class BrowserCommandError extends Error {
|
|
18
|
+
code;
|
|
19
|
+
hint;
|
|
20
|
+
constructor(message, code, hint) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.code = code;
|
|
23
|
+
this.hint = hint;
|
|
24
|
+
this.name = 'BrowserCommandError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
16
27
|
async function requestDaemon(pathname, init) {
|
|
17
28
|
const { timeout = 2000, headers, ...rest } = init ?? {};
|
|
18
29
|
const controller = new AbortController();
|
|
@@ -30,7 +41,8 @@ async function requestDaemon(pathname, init) {
|
|
|
30
41
|
}
|
|
31
42
|
export async function fetchDaemonStatus(opts) {
|
|
32
43
|
try {
|
|
33
|
-
const
|
|
44
|
+
const params = opts?.contextId ? `?contextId=${encodeURIComponent(opts.contextId)}` : '';
|
|
45
|
+
const res = await requestDaemon(`/status${params}`, { timeout: opts?.timeout ?? 2000 });
|
|
34
46
|
if (!res.ok)
|
|
35
47
|
return null;
|
|
36
48
|
return await res.json();
|
|
@@ -47,6 +59,10 @@ export async function getDaemonHealth(opts) {
|
|
|
47
59
|
const status = await fetchDaemonStatus(opts);
|
|
48
60
|
if (!status)
|
|
49
61
|
return { state: 'stopped', status: null };
|
|
62
|
+
if (status.profileRequired)
|
|
63
|
+
return { state: 'profile-required', status };
|
|
64
|
+
if (status.profileDisconnected)
|
|
65
|
+
return { state: 'profile-disconnected', status };
|
|
50
66
|
if (!status.extensionConnected)
|
|
51
67
|
return { state: 'no-extension', status };
|
|
52
68
|
return { state: 'ready', status };
|
|
@@ -75,7 +91,8 @@ async function sendCommandRaw(action, params) {
|
|
|
75
91
|
const id = generateId();
|
|
76
92
|
const wf = process.env.OPENCLI_WINDOW_FOCUSED;
|
|
77
93
|
const windowFocused = (wf === '1' || wf === 'true') ? true : undefined;
|
|
78
|
-
const
|
|
94
|
+
const contextId = params.contextId ?? resolveProfileContextId();
|
|
95
|
+
const command = { id, action, ...params, ...(contextId && { contextId }), ...(windowFocused && { windowFocused }) };
|
|
79
96
|
try {
|
|
80
97
|
const res = await requestDaemon('/command', {
|
|
81
98
|
method: 'POST',
|
|
@@ -95,7 +112,7 @@ async function sendCommandRaw(action, params) {
|
|
|
95
112
|
await sleep(advice.delayMs);
|
|
96
113
|
continue;
|
|
97
114
|
}
|
|
98
|
-
throw new
|
|
115
|
+
throw new BrowserCommandError(result.error ?? 'Daemon command failed', result.errorCode, result.errorHint);
|
|
99
116
|
}
|
|
100
117
|
return result;
|
|
101
118
|
}
|
|
@@ -126,10 +143,10 @@ export async function sendCommandFull(action, params = {}) {
|
|
|
126
143
|
const result = await sendCommandRaw(action, params);
|
|
127
144
|
return { data: result.data, page: result.page };
|
|
128
145
|
}
|
|
129
|
-
export async function listSessions() {
|
|
130
|
-
const result = await sendCommand('sessions');
|
|
146
|
+
export async function listSessions(opts) {
|
|
147
|
+
const result = await sendCommand('sessions', { ...(opts?.contextId && { contextId: opts.contextId }) });
|
|
131
148
|
return Array.isArray(result) ? result : [];
|
|
132
149
|
}
|
|
133
|
-
export async function
|
|
134
|
-
return sendCommand('bind
|
|
150
|
+
export async function bindTab(workspace, opts = {}) {
|
|
151
|
+
return sendCommand('bind', { workspace, ...opts });
|
|
135
152
|
}
|
|
@@ -6,6 +6,7 @@ describe('daemon-client', () => {
|
|
|
6
6
|
});
|
|
7
7
|
afterEach(() => {
|
|
8
8
|
vi.restoreAllMocks();
|
|
9
|
+
vi.unstubAllEnvs();
|
|
9
10
|
});
|
|
10
11
|
it('fetchDaemonStatus sends the shared status request and returns parsed data', async () => {
|
|
11
12
|
const status = {
|
|
@@ -78,6 +79,43 @@ describe('daemon-client', () => {
|
|
|
78
79
|
});
|
|
79
80
|
await expect(getDaemonHealth()).resolves.toEqual({ state: 'ready', status });
|
|
80
81
|
});
|
|
82
|
+
it('getDaemonHealth returns profile-required when multiple profiles are connected without a selection', async () => {
|
|
83
|
+
const status = {
|
|
84
|
+
ok: true,
|
|
85
|
+
pid: 123,
|
|
86
|
+
uptime: 10,
|
|
87
|
+
extensionConnected: false,
|
|
88
|
+
profileRequired: true,
|
|
89
|
+
profiles: [
|
|
90
|
+
{ contextId: 'work', extensionConnected: true, pending: 0 },
|
|
91
|
+
{ contextId: 'personal', extensionConnected: true, pending: 0 },
|
|
92
|
+
],
|
|
93
|
+
pending: 0,
|
|
94
|
+
memoryMB: 32,
|
|
95
|
+
port: 19825,
|
|
96
|
+
};
|
|
97
|
+
vi.mocked(fetch).mockResolvedValue({
|
|
98
|
+
ok: true,
|
|
99
|
+
json: () => Promise.resolve(status),
|
|
100
|
+
});
|
|
101
|
+
await expect(getDaemonHealth()).resolves.toEqual({ state: 'profile-required', status });
|
|
102
|
+
});
|
|
103
|
+
it('fetchDaemonStatus includes contextId in the status query', async () => {
|
|
104
|
+
vi.mocked(fetch).mockResolvedValue({
|
|
105
|
+
ok: true,
|
|
106
|
+
json: () => Promise.resolve({
|
|
107
|
+
ok: true,
|
|
108
|
+
pid: 1,
|
|
109
|
+
uptime: 0,
|
|
110
|
+
extensionConnected: true,
|
|
111
|
+
pending: 0,
|
|
112
|
+
memoryMB: 1,
|
|
113
|
+
port: 19825,
|
|
114
|
+
}),
|
|
115
|
+
});
|
|
116
|
+
await fetchDaemonStatus({ contextId: 'work' });
|
|
117
|
+
expect(vi.mocked(fetch).mock.calls[0][0]).toMatch(/\/status\?contextId=work$/);
|
|
118
|
+
});
|
|
81
119
|
it('sendCommand includes the current pid in generated command ids', async () => {
|
|
82
120
|
vi.spyOn(Date, 'now').mockReturnValue(1_763_000_000_000);
|
|
83
121
|
vi.mocked(fetch).mockResolvedValue({
|
|
@@ -95,6 +133,17 @@ describe('daemon-client', () => {
|
|
|
95
133
|
expect(ids[1]).toMatch(new RegExp(`^cmd_${process.pid}_1763000000000_\\d+$`));
|
|
96
134
|
expect(ids[0]).not.toBe(ids[1]);
|
|
97
135
|
});
|
|
136
|
+
it('sendCommand forwards OPENCLI_PROFILE as command contextId', async () => {
|
|
137
|
+
vi.stubEnv('OPENCLI_PROFILE', 'work');
|
|
138
|
+
vi.spyOn(Date, 'now').mockReturnValue(1_763_000_000_000);
|
|
139
|
+
vi.mocked(fetch).mockResolvedValue({
|
|
140
|
+
status: 200,
|
|
141
|
+
json: () => Promise.resolve({ id: 'server', ok: true, data: 'ok' }),
|
|
142
|
+
});
|
|
143
|
+
await sendCommand('exec', { code: '1 + 1' });
|
|
144
|
+
const body = JSON.parse(String(vi.mocked(fetch).mock.calls[0][1]?.body));
|
|
145
|
+
expect(body.contextId).toBe('work');
|
|
146
|
+
});
|
|
98
147
|
it('sendCommand retries with a new id when daemon reports a duplicate pending id', async () => {
|
|
99
148
|
vi.spyOn(Date, 'now').mockReturnValue(1_763_000_000_123);
|
|
100
149
|
const fetchMock = vi.mocked(fetch);
|
|
@@ -15,7 +15,10 @@ const EXTENSION_TRANSIENT_PATTERNS = [
|
|
|
15
15
|
'Extension disconnected',
|
|
16
16
|
'Extension not connected',
|
|
17
17
|
'attach failed',
|
|
18
|
+
'Detached while handling command',
|
|
19
|
+
'Debugger is not attached to the tab',
|
|
18
20
|
'no longer exists',
|
|
21
|
+
'No tab with id',
|
|
19
22
|
'CDP connection',
|
|
20
23
|
'Daemon command failed',
|
|
21
24
|
'No window with id',
|
|
@@ -6,7 +6,10 @@ describe('classifyBrowserError', () => {
|
|
|
6
6
|
'Extension disconnected',
|
|
7
7
|
'Extension not connected',
|
|
8
8
|
'attach failed',
|
|
9
|
+
'Detached while handling command',
|
|
10
|
+
'Debugger is not attached to the tab: 123',
|
|
9
11
|
'no longer exists',
|
|
12
|
+
'No tab with id: 456',
|
|
10
13
|
'CDP connection reset',
|
|
11
14
|
'Daemon command failed',
|
|
12
15
|
'No window with id: 123',
|
|
@@ -15,8 +15,9 @@ import { BasePage } from './base-page.js';
|
|
|
15
15
|
*/
|
|
16
16
|
export declare class Page extends BasePage {
|
|
17
17
|
private readonly workspace;
|
|
18
|
+
readonly contextId?: string | undefined;
|
|
18
19
|
private readonly _idleTimeout;
|
|
19
|
-
constructor(workspace?: string, idleTimeout?: number);
|
|
20
|
+
constructor(workspace?: string, idleTimeout?: number, contextId?: string | undefined);
|
|
20
21
|
/** Active page identity (targetId), set after navigate and used in all subsequent commands */
|
|
21
22
|
private _page;
|
|
22
23
|
private _networkCaptureUnsupported;
|
|
@@ -28,6 +29,7 @@ export declare class Page extends BasePage {
|
|
|
28
29
|
goto(url: string, options?: {
|
|
29
30
|
waitUntil?: 'load' | 'none';
|
|
30
31
|
settleMs?: number;
|
|
32
|
+
allowBoundNavigation?: boolean;
|
|
31
33
|
}): Promise<void>;
|
|
32
34
|
/** Get the active page identity (targetId) */
|
|
33
35
|
getActivePage(): string | undefined;
|
package/dist/src/browser/page.js
CHANGED
|
@@ -27,10 +27,12 @@ function isUnsupportedNetworkCaptureError(err) {
|
|
|
27
27
|
*/
|
|
28
28
|
export class Page extends BasePage {
|
|
29
29
|
workspace;
|
|
30
|
+
contextId;
|
|
30
31
|
_idleTimeout;
|
|
31
|
-
constructor(workspace = 'default', idleTimeout) {
|
|
32
|
+
constructor(workspace = 'default', idleTimeout, contextId) {
|
|
32
33
|
super();
|
|
33
34
|
this.workspace = workspace;
|
|
35
|
+
this.contextId = contextId;
|
|
34
36
|
this._idleTimeout = idleTimeout;
|
|
35
37
|
}
|
|
36
38
|
/** Active page identity (targetId), set after navigate and used in all subsequent commands */
|
|
@@ -39,12 +41,17 @@ export class Page extends BasePage {
|
|
|
39
41
|
_networkCaptureWarned = false;
|
|
40
42
|
/** Helper: spread workspace into command params */
|
|
41
43
|
_wsOpt() {
|
|
42
|
-
return {
|
|
44
|
+
return {
|
|
45
|
+
workspace: this.workspace,
|
|
46
|
+
...(this.contextId && { contextId: this.contextId }),
|
|
47
|
+
...(this._idleTimeout != null && { idleTimeout: this._idleTimeout }),
|
|
48
|
+
};
|
|
43
49
|
}
|
|
44
50
|
/** Helper: spread workspace + page identity into command params */
|
|
45
51
|
_cmdOpts() {
|
|
46
52
|
return {
|
|
47
53
|
workspace: this.workspace,
|
|
54
|
+
...(this.contextId && { contextId: this.contextId }),
|
|
48
55
|
...(this._page !== undefined && { page: this._page }),
|
|
49
56
|
...(this._idleTimeout != null && { idleTimeout: this._idleTimeout }),
|
|
50
57
|
};
|
|
@@ -53,6 +60,7 @@ export class Page extends BasePage {
|
|
|
53
60
|
const result = await sendCommandFull('navigate', {
|
|
54
61
|
url,
|
|
55
62
|
...this._cmdOpts(),
|
|
63
|
+
...(options?.allowBoundNavigation === true && { allowBoundNavigation: true }),
|
|
56
64
|
});
|
|
57
65
|
// Remember the page identity (targetId) for subsequent calls
|
|
58
66
|
if (result.page) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const DEFAULT_CONTEXT_ID = "default";
|
|
2
|
+
export type ProfileConfig = {
|
|
3
|
+
version: 1;
|
|
4
|
+
defaultContextId?: string;
|
|
5
|
+
aliases: Record<string, string>;
|
|
6
|
+
};
|
|
7
|
+
export declare function normalizeContextId(value: string | undefined | null): string | undefined;
|
|
8
|
+
export declare function emptyProfileConfig(): ProfileConfig;
|
|
9
|
+
export declare function loadProfileConfig(): ProfileConfig;
|
|
10
|
+
export declare function saveProfileConfig(config: ProfileConfig): void;
|
|
11
|
+
export declare function resolveProfileContextId(profile?: string): string | undefined;
|
|
12
|
+
export declare function aliasForContextId(config: ProfileConfig, contextId: string): string | undefined;
|
|
13
|
+
export declare function renameProfile(contextId: string, alias: string): ProfileConfig;
|
|
14
|
+
export declare function setDefaultProfile(profile: string): ProfileConfig;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
export const DEFAULT_CONTEXT_ID = 'default';
|
|
5
|
+
function profileConfigPath() {
|
|
6
|
+
const baseDir = process.env.OPENCLI_CONFIG_DIR || path.join(os.homedir(), '.opencli');
|
|
7
|
+
return path.join(baseDir, 'browser-profiles.json');
|
|
8
|
+
}
|
|
9
|
+
export function normalizeContextId(value) {
|
|
10
|
+
const trimmed = value?.trim();
|
|
11
|
+
return trimmed || undefined;
|
|
12
|
+
}
|
|
13
|
+
export function emptyProfileConfig() {
|
|
14
|
+
return { version: 1, aliases: {} };
|
|
15
|
+
}
|
|
16
|
+
export function loadProfileConfig() {
|
|
17
|
+
try {
|
|
18
|
+
const raw = fs.readFileSync(profileConfigPath(), 'utf-8');
|
|
19
|
+
const parsed = JSON.parse(raw);
|
|
20
|
+
const aliases = parsed.aliases && typeof parsed.aliases === 'object'
|
|
21
|
+
? Object.fromEntries(Object.entries(parsed.aliases).filter((entry) => {
|
|
22
|
+
const [key, value] = entry;
|
|
23
|
+
return typeof key === 'string' && key.trim().length > 0
|
|
24
|
+
&& typeof value === 'string' && value.trim().length > 0;
|
|
25
|
+
}))
|
|
26
|
+
: {};
|
|
27
|
+
return {
|
|
28
|
+
version: 1,
|
|
29
|
+
aliases,
|
|
30
|
+
...(typeof parsed.defaultContextId === 'string' && parsed.defaultContextId.trim()
|
|
31
|
+
? { defaultContextId: parsed.defaultContextId.trim() }
|
|
32
|
+
: {}),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return emptyProfileConfig();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function saveProfileConfig(config) {
|
|
40
|
+
const target = profileConfigPath();
|
|
41
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
42
|
+
fs.writeFileSync(target, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
43
|
+
}
|
|
44
|
+
export function resolveProfileContextId(profile) {
|
|
45
|
+
const config = loadProfileConfig();
|
|
46
|
+
const requested = normalizeContextId(profile)
|
|
47
|
+
?? normalizeContextId(process.env.OPENCLI_PROFILE)
|
|
48
|
+
?? normalizeContextId(config.defaultContextId);
|
|
49
|
+
if (!requested)
|
|
50
|
+
return undefined;
|
|
51
|
+
return config.aliases[requested] ?? requested;
|
|
52
|
+
}
|
|
53
|
+
export function aliasForContextId(config, contextId) {
|
|
54
|
+
for (const [alias, id] of Object.entries(config.aliases)) {
|
|
55
|
+
if (id === contextId)
|
|
56
|
+
return alias;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
export function renameProfile(contextId, alias) {
|
|
61
|
+
const normalizedContextId = normalizeContextId(contextId);
|
|
62
|
+
const normalizedAlias = normalizeContextId(alias);
|
|
63
|
+
if (!normalizedContextId)
|
|
64
|
+
throw new Error('profile contextId is required');
|
|
65
|
+
if (!normalizedAlias)
|
|
66
|
+
throw new Error('profile alias is required');
|
|
67
|
+
const config = loadProfileConfig();
|
|
68
|
+
for (const [existingAlias, existingContextId] of Object.entries(config.aliases)) {
|
|
69
|
+
if (existingAlias !== normalizedAlias && existingContextId === normalizedContextId) {
|
|
70
|
+
delete config.aliases[existingAlias];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
config.aliases[normalizedAlias] = normalizedContextId;
|
|
74
|
+
saveProfileConfig(config);
|
|
75
|
+
return config;
|
|
76
|
+
}
|
|
77
|
+
export function setDefaultProfile(profile) {
|
|
78
|
+
const contextId = resolveProfileContextId(profile) ?? normalizeContextId(profile);
|
|
79
|
+
if (!contextId)
|
|
80
|
+
throw new Error('profile is required');
|
|
81
|
+
const config = loadProfileConfig();
|
|
82
|
+
config.defaultContextId = contextId;
|
|
83
|
+
saveProfileConfig(config);
|
|
84
|
+
return config;
|
|
85
|
+
}
|
|
@@ -39,5 +39,7 @@ export interface ManifestEntry {
|
|
|
39
39
|
/** Pre-navigation control — see CliCommand.navigateBefore */
|
|
40
40
|
navigateBefore?: boolean | string;
|
|
41
41
|
}
|
|
42
|
+
export declare function normalizeManifestPath(relativePath: string): string;
|
|
42
43
|
export declare function loadManifestEntries(filePath: string, site: string, importer?: (moduleHref: string) => Promise<unknown>): Promise<ManifestEntry[]>;
|
|
43
44
|
export declare function buildManifest(): Promise<ManifestEntry[]>;
|
|
45
|
+
export declare function serializeManifest(manifest: ManifestEntry[]): string;
|
|
@@ -36,6 +36,12 @@ function toModulePath(filePath, site) {
|
|
|
36
36
|
const baseName = path.basename(filePath, path.extname(filePath));
|
|
37
37
|
return `${site}/${baseName}.js`;
|
|
38
38
|
}
|
|
39
|
+
export function normalizeManifestPath(relativePath) {
|
|
40
|
+
return relativePath.replace(/\\/g, '/');
|
|
41
|
+
}
|
|
42
|
+
function toManifestRelativePath(filePath) {
|
|
43
|
+
return normalizeManifestPath(path.relative(CLIS_DIR, filePath));
|
|
44
|
+
}
|
|
39
45
|
function isCliCommandValue(value, site) {
|
|
40
46
|
return isRecord(value)
|
|
41
47
|
&& typeof value.site === 'string'
|
|
@@ -85,8 +91,9 @@ export async function loadManifestEntries(filePath, site, importer = moduleHref
|
|
|
85
91
|
return !previous || previous !== cmd;
|
|
86
92
|
})
|
|
87
93
|
.map(([, cmd]) => cmd);
|
|
88
|
-
//
|
|
89
|
-
|
|
94
|
+
// Manifest paths are cross-platform artifacts; keep them POSIX-style even
|
|
95
|
+
// when build-manifest runs on Windows.
|
|
96
|
+
const sourceRelative = toManifestRelativePath(filePath);
|
|
90
97
|
const seen = new Set();
|
|
91
98
|
return runtimeCommands
|
|
92
99
|
.filter((cmd) => {
|
|
@@ -128,10 +135,13 @@ export async function buildManifest() {
|
|
|
128
135
|
}
|
|
129
136
|
return [...manifest.values()].sort((a, b) => a.site.localeCompare(b.site) || a.name.localeCompare(b.name));
|
|
130
137
|
}
|
|
138
|
+
export function serializeManifest(manifest) {
|
|
139
|
+
return `${JSON.stringify(manifest, null, 2)}\n`;
|
|
140
|
+
}
|
|
131
141
|
async function main() {
|
|
132
142
|
const manifest = await buildManifest();
|
|
133
143
|
fs.mkdirSync(path.dirname(OUTPUT), { recursive: true });
|
|
134
|
-
fs.writeFileSync(OUTPUT,
|
|
144
|
+
fs.writeFileSync(OUTPUT, serializeManifest(manifest));
|
|
135
145
|
console.log(`✅ Manifest compiled: ${manifest.length} entries → ${OUTPUT}`);
|
|
136
146
|
// Restore executable permissions on bin entries.
|
|
137
147
|
// tsc does not preserve the +x bit, so after a clean rebuild the CLI
|
|
@@ -3,7 +3,7 @@ import * as fs from 'node:fs';
|
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { cli, getRegistry, Strategy } from './registry.js';
|
|
6
|
-
import { loadManifestEntries } from './build-manifest.js';
|
|
6
|
+
import { loadManifestEntries, normalizeManifestPath, serializeManifest } from './build-manifest.js';
|
|
7
7
|
describe('manifest helper rules', () => {
|
|
8
8
|
const tempDirs = [];
|
|
9
9
|
afterEach(() => {
|
|
@@ -76,8 +76,9 @@ describe('manifest helper rules', () => {
|
|
|
76
76
|
replacedBy: 'opencli demo new',
|
|
77
77
|
},
|
|
78
78
|
]);
|
|
79
|
-
// Verify sourceFile is included
|
|
79
|
+
// Verify sourceFile is included and stable for manifest consumers.
|
|
80
80
|
expect(entries[0].sourceFile).toBeDefined();
|
|
81
|
+
expect(entries[0].sourceFile).not.toContain('\\');
|
|
81
82
|
getRegistry().delete(key);
|
|
82
83
|
});
|
|
83
84
|
it('falls back to registry delta for side-effect-only cli modules', async () => {
|
|
@@ -139,4 +140,21 @@ describe('manifest helper rules', () => {
|
|
|
139
140
|
getRegistry().delete(screenKey);
|
|
140
141
|
getRegistry().delete(statusKey);
|
|
141
142
|
});
|
|
143
|
+
it('normalizes manifest paths to POSIX separators', () => {
|
|
144
|
+
expect(normalizeManifestPath('demo\\status.js')).toBe('demo/status.js');
|
|
145
|
+
expect(normalizeManifestPath('demo/status.js')).toBe('demo/status.js');
|
|
146
|
+
});
|
|
147
|
+
it('serializes manifest json with a trailing newline', () => {
|
|
148
|
+
const serialized = serializeManifest([{
|
|
149
|
+
site: 'demo',
|
|
150
|
+
name: 'status',
|
|
151
|
+
description: '',
|
|
152
|
+
strategy: 'public',
|
|
153
|
+
browser: false,
|
|
154
|
+
args: [],
|
|
155
|
+
type: 'js',
|
|
156
|
+
}]);
|
|
157
|
+
expect(serialized.endsWith('\n')).toBe(true);
|
|
158
|
+
expect(serialized).toContain('\n]');
|
|
159
|
+
});
|
|
142
160
|
});
|
package/dist/src/cli.d.ts
CHANGED
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { Command } from 'commander';
|
|
8
8
|
import { findPackageRoot } from './package-paths.js';
|
|
9
|
+
export declare function selectFreshByTimestamp<T extends {
|
|
10
|
+
timestamp?: unknown;
|
|
11
|
+
}>(items: T[], lastSeenTs: number): {
|
|
12
|
+
fresh: T[];
|
|
13
|
+
lastSeenTs: number;
|
|
14
|
+
};
|
|
9
15
|
/**
|
|
10
16
|
* Check whether the site-memory scaffolding exists under
|
|
11
17
|
* ~/.opencli/sites/<site>/. Agents have a strong tendency to forget to write
|