@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
package/dist/src/daemon.js
CHANGED
|
@@ -25,19 +25,96 @@ import { DEFAULT_DAEMON_PORT } from './constants.js';
|
|
|
25
25
|
import { EXIT_CODES } from './errors.js';
|
|
26
26
|
import { log } from './logger.js';
|
|
27
27
|
import { PKG_VERSION } from './version.js';
|
|
28
|
+
import { DEFAULT_CONTEXT_ID } from './browser/profile.js';
|
|
29
|
+
import { recordExtensionVersion } from './update-check.js';
|
|
28
30
|
const PORT = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
|
|
29
|
-
|
|
30
|
-
let extensionWs = null;
|
|
31
|
-
let extensionVersion = null;
|
|
32
|
-
let extensionCompatRange = null;
|
|
31
|
+
const extensionProfiles = new Map();
|
|
33
32
|
const pending = new Map();
|
|
34
33
|
const LOG_BUFFER_SIZE = 200;
|
|
35
34
|
const logBuffer = [];
|
|
35
|
+
class DaemonCommandFailure extends Error {
|
|
36
|
+
errorCode;
|
|
37
|
+
errorHint;
|
|
38
|
+
status;
|
|
39
|
+
constructor(message, errorCode, errorHint, status = 400) {
|
|
40
|
+
super(message);
|
|
41
|
+
this.errorCode = errorCode;
|
|
42
|
+
this.errorHint = errorHint;
|
|
43
|
+
this.status = status;
|
|
44
|
+
this.name = 'DaemonCommandFailure';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
36
47
|
function pushLog(entry) {
|
|
37
48
|
logBuffer.push(entry);
|
|
38
49
|
if (logBuffer.length > LOG_BUFFER_SIZE)
|
|
39
50
|
logBuffer.shift();
|
|
40
51
|
}
|
|
52
|
+
function activeProfiles() {
|
|
53
|
+
return [...extensionProfiles.values()].filter((entry) => entry.ws.readyState === WebSocket.OPEN);
|
|
54
|
+
}
|
|
55
|
+
function resolveExtensionConnection(contextId) {
|
|
56
|
+
const requestedContextId = typeof contextId === 'string' && contextId.trim() ? contextId.trim() : undefined;
|
|
57
|
+
if (requestedContextId) {
|
|
58
|
+
const connection = extensionProfiles.get(requestedContextId);
|
|
59
|
+
if (connection?.ws.readyState === WebSocket.OPEN)
|
|
60
|
+
return { connection };
|
|
61
|
+
return {
|
|
62
|
+
errorCode: 'profile_disconnected',
|
|
63
|
+
error: `Browser profile "${requestedContextId}" is not connected.`,
|
|
64
|
+
errorHint: 'Open that Chrome profile and make sure the OpenCLI extension is enabled, or choose another profile with opencli profile use <name>.',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const connected = activeProfiles();
|
|
68
|
+
if (connected.length === 1)
|
|
69
|
+
return { connection: connected[0] };
|
|
70
|
+
if (connected.length > 1) {
|
|
71
|
+
return {
|
|
72
|
+
errorCode: 'profile_required',
|
|
73
|
+
error: 'Multiple Browser Bridge profiles are connected; choose one with --profile.',
|
|
74
|
+
errorHint: 'Run opencli profile list, then use opencli --profile <name> ... or opencli profile use <name>.',
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
errorCode: 'extension_not_connected',
|
|
79
|
+
error: 'Extension not connected. Please install the opencli Browser Bridge extension.',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function registerExtensionConnection(ws, rawContextId) {
|
|
83
|
+
const contextId = typeof rawContextId === 'string' && rawContextId.trim()
|
|
84
|
+
? rawContextId.trim()
|
|
85
|
+
: DEFAULT_CONTEXT_ID;
|
|
86
|
+
const previous = extensionProfiles.get(contextId);
|
|
87
|
+
if (previous && previous.ws !== ws) {
|
|
88
|
+
previous.ws.close();
|
|
89
|
+
}
|
|
90
|
+
const existing = [...extensionProfiles.entries()].find(([, entry]) => entry.ws === ws);
|
|
91
|
+
if (existing && existing[0] !== contextId)
|
|
92
|
+
extensionProfiles.delete(existing[0]);
|
|
93
|
+
const current = extensionProfiles.get(contextId);
|
|
94
|
+
const connection = {
|
|
95
|
+
contextId,
|
|
96
|
+
ws,
|
|
97
|
+
extensionVersion: current?.ws === ws ? current.extensionVersion : null,
|
|
98
|
+
extensionCompatRange: current?.ws === ws ? current.extensionCompatRange : null,
|
|
99
|
+
lastSeenAt: Date.now(),
|
|
100
|
+
};
|
|
101
|
+
extensionProfiles.set(contextId, connection);
|
|
102
|
+
return connection;
|
|
103
|
+
}
|
|
104
|
+
function unregisterExtensionConnection(ws) {
|
|
105
|
+
for (const [contextId, connection] of extensionProfiles.entries()) {
|
|
106
|
+
if (connection.ws !== ws)
|
|
107
|
+
continue;
|
|
108
|
+
extensionProfiles.delete(contextId);
|
|
109
|
+
for (const [id, p] of pending) {
|
|
110
|
+
if (p.contextId !== contextId)
|
|
111
|
+
continue;
|
|
112
|
+
clearTimeout(p.timer);
|
|
113
|
+
p.reject(new DaemonCommandFailure(`Browser profile "${contextId}" disconnected`, 'profile_disconnected', 'Open that Chrome profile and make sure the OpenCLI extension is enabled, or choose another profile with opencli profile use <name>.', 503));
|
|
114
|
+
pending.delete(id);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
41
118
|
// ─── HTTP Server ─────────────────────────────────────────────────────
|
|
42
119
|
const MAX_BODY = 1024 * 1024; // 1 MB — commands are tiny; this prevents OOM
|
|
43
120
|
function readBody(req) {
|
|
@@ -118,14 +195,29 @@ async function handleRequest(req, res) {
|
|
|
118
195
|
if (req.method === 'GET' && pathname === '/status') {
|
|
119
196
|
const uptime = process.uptime();
|
|
120
197
|
const mem = process.memoryUsage();
|
|
198
|
+
const params = new URL(url, `http://localhost:${PORT}`).searchParams;
|
|
199
|
+
const requestedContextId = params.get('contextId')?.trim() || undefined;
|
|
200
|
+
const route = resolveExtensionConnection(requestedContextId);
|
|
201
|
+
const profiles = activeProfiles().map((profile) => ({
|
|
202
|
+
contextId: profile.contextId,
|
|
203
|
+
extensionConnected: true,
|
|
204
|
+
extensionVersion: profile.extensionVersion ?? undefined,
|
|
205
|
+
extensionCompatRange: profile.extensionCompatRange ?? undefined,
|
|
206
|
+
pending: [...pending.values()].filter((entry) => entry.contextId === profile.contextId).length,
|
|
207
|
+
lastSeenAt: profile.lastSeenAt,
|
|
208
|
+
}));
|
|
121
209
|
jsonResponse(res, 200, {
|
|
122
210
|
ok: true,
|
|
123
211
|
pid: process.pid,
|
|
124
212
|
uptime,
|
|
125
213
|
daemonVersion: PKG_VERSION,
|
|
126
|
-
extensionConnected:
|
|
127
|
-
extensionVersion,
|
|
128
|
-
extensionCompatRange,
|
|
214
|
+
extensionConnected: !!route.connection,
|
|
215
|
+
extensionVersion: route.connection?.extensionVersion ?? undefined,
|
|
216
|
+
extensionCompatRange: route.connection?.extensionCompatRange ?? undefined,
|
|
217
|
+
contextId: route.connection?.contextId ?? requestedContextId,
|
|
218
|
+
profileRequired: route.errorCode === 'profile_required',
|
|
219
|
+
profileDisconnected: route.errorCode === 'profile_disconnected',
|
|
220
|
+
profiles,
|
|
129
221
|
pending: pending.size,
|
|
130
222
|
memoryMB: Math.round(mem.rss / 1024 / 1024 * 10) / 10,
|
|
131
223
|
port: PORT,
|
|
@@ -158,8 +250,15 @@ async function handleRequest(req, res) {
|
|
|
158
250
|
jsonResponse(res, 400, { ok: false, error: 'Missing command id' });
|
|
159
251
|
return;
|
|
160
252
|
}
|
|
161
|
-
|
|
162
|
-
|
|
253
|
+
const route = resolveExtensionConnection(typeof body.contextId === 'string' ? body.contextId : undefined);
|
|
254
|
+
if (!route.connection) {
|
|
255
|
+
jsonResponse(res, route.errorCode === 'profile_required' ? 409 : 503, {
|
|
256
|
+
id: body.id,
|
|
257
|
+
ok: false,
|
|
258
|
+
errorCode: route.errorCode,
|
|
259
|
+
error: route.error,
|
|
260
|
+
...(route.errorHint ? { errorHint: route.errorHint } : {}),
|
|
261
|
+
});
|
|
163
262
|
return;
|
|
164
263
|
}
|
|
165
264
|
const timeoutMs = typeof body.timeout === 'number' && body.timeout > 0
|
|
@@ -178,15 +277,18 @@ async function handleRequest(req, res) {
|
|
|
178
277
|
pending.delete(body.id);
|
|
179
278
|
reject(new Error(`Command timeout (${timeoutMs / 1000}s)`));
|
|
180
279
|
}, timeoutMs);
|
|
181
|
-
pending.set(body.id, { resolve, reject, timer });
|
|
182
|
-
|
|
280
|
+
pending.set(body.id, { contextId: route.connection.contextId, resolve, reject, timer });
|
|
281
|
+
route.connection.ws.send(JSON.stringify(body));
|
|
183
282
|
});
|
|
184
283
|
jsonResponse(res, 200, result);
|
|
185
284
|
}
|
|
186
285
|
catch (err) {
|
|
187
|
-
|
|
286
|
+
const commandFailure = err instanceof DaemonCommandFailure ? err : null;
|
|
287
|
+
jsonResponse(res, commandFailure?.status ?? (err instanceof Error && err.message.includes('timeout') ? 408 : 400), {
|
|
188
288
|
ok: false,
|
|
189
289
|
error: err instanceof Error ? err.message : 'Invalid request',
|
|
290
|
+
...(commandFailure?.errorCode ? { errorCode: commandFailure.errorCode } : {}),
|
|
291
|
+
...(commandFailure?.errorHint ? { errorHint: commandFailure.errorHint } : {}),
|
|
190
292
|
});
|
|
191
293
|
}
|
|
192
294
|
return;
|
|
@@ -209,9 +311,6 @@ const wss = new WebSocketServer({
|
|
|
209
311
|
});
|
|
210
312
|
wss.on('connection', (ws) => {
|
|
211
313
|
log.info('[daemon] Extension connected');
|
|
212
|
-
extensionWs = ws;
|
|
213
|
-
extensionVersion = null; // cleared until hello message arrives
|
|
214
|
-
extensionCompatRange = null;
|
|
215
314
|
// ── Heartbeat: ping every 15s, close if 2 pongs missed ──
|
|
216
315
|
let missedPongs = 0;
|
|
217
316
|
const heartbeatInterval = setInterval(() => {
|
|
@@ -236,8 +335,13 @@ wss.on('connection', (ws) => {
|
|
|
236
335
|
const msg = JSON.parse(data.toString());
|
|
237
336
|
// Handle hello message from extension (version handshake)
|
|
238
337
|
if (msg.type === 'hello') {
|
|
239
|
-
|
|
240
|
-
|
|
338
|
+
const connection = registerExtensionConnection(ws, msg.contextId);
|
|
339
|
+
connection.extensionVersion = typeof msg.version === 'string' ? msg.version : null;
|
|
340
|
+
connection.extensionCompatRange = typeof msg.compatRange === 'string' ? msg.compatRange : null;
|
|
341
|
+
connection.lastSeenAt = Date.now();
|
|
342
|
+
if (connection.extensionVersion)
|
|
343
|
+
recordExtensionVersion(connection.extensionVersion);
|
|
344
|
+
log.info(`[daemon] Extension profile connected: ${connection.contextId}`);
|
|
241
345
|
return;
|
|
242
346
|
}
|
|
243
347
|
// Handle log messages from extension
|
|
@@ -266,31 +370,11 @@ wss.on('connection', (ws) => {
|
|
|
266
370
|
ws.on('close', () => {
|
|
267
371
|
log.info('[daemon] Extension disconnected');
|
|
268
372
|
clearInterval(heartbeatInterval);
|
|
269
|
-
|
|
270
|
-
extensionWs = null;
|
|
271
|
-
extensionVersion = null;
|
|
272
|
-
extensionCompatRange = null;
|
|
273
|
-
// Reject all pending requests since the extension is gone
|
|
274
|
-
for (const [id, p] of pending) {
|
|
275
|
-
clearTimeout(p.timer);
|
|
276
|
-
p.reject(new Error('Extension disconnected'));
|
|
277
|
-
}
|
|
278
|
-
pending.clear();
|
|
279
|
-
}
|
|
373
|
+
unregisterExtensionConnection(ws);
|
|
280
374
|
});
|
|
281
375
|
ws.on('error', () => {
|
|
282
376
|
clearInterval(heartbeatInterval);
|
|
283
|
-
|
|
284
|
-
extensionWs = null;
|
|
285
|
-
extensionVersion = null;
|
|
286
|
-
extensionCompatRange = null;
|
|
287
|
-
// Reject pending requests in case 'close' does not follow this 'error'
|
|
288
|
-
for (const [, p] of pending) {
|
|
289
|
-
clearTimeout(p.timer);
|
|
290
|
-
p.reject(new Error('Extension disconnected'));
|
|
291
|
-
}
|
|
292
|
-
pending.clear();
|
|
293
|
-
}
|
|
377
|
+
unregisterExtensionConnection(ws);
|
|
294
378
|
});
|
|
295
379
|
});
|
|
296
380
|
// ─── Start ───────────────────────────────────────────────────────────
|
|
@@ -313,8 +397,8 @@ function shutdown() {
|
|
|
313
397
|
p.reject(new Error('Daemon shutting down'));
|
|
314
398
|
}
|
|
315
399
|
pending.clear();
|
|
316
|
-
|
|
317
|
-
|
|
400
|
+
for (const profile of extensionProfiles.values())
|
|
401
|
+
profile.ws.close();
|
|
318
402
|
httpServer.close();
|
|
319
403
|
process.exit(EXIT_CODES.SUCCESS);
|
|
320
404
|
}
|
package/dist/src/doctor.d.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Simplified for the daemon-based architecture.
|
|
5
5
|
*/
|
|
6
|
+
import type { BrowserSessionInfo } from './types.js';
|
|
7
|
+
import type { BrowserProfileStatus } from './browser/daemon-client.js';
|
|
6
8
|
export type DoctorOptions = {
|
|
7
9
|
yes?: boolean;
|
|
8
10
|
live?: boolean;
|
|
@@ -24,12 +26,8 @@ export type DoctorReport = {
|
|
|
24
26
|
extensionVersion?: string;
|
|
25
27
|
latestExtensionVersion?: string;
|
|
26
28
|
connectivity?: ConnectivityResult;
|
|
27
|
-
sessions?:
|
|
28
|
-
|
|
29
|
-
windowId: number;
|
|
30
|
-
tabCount: number;
|
|
31
|
-
idleMsRemaining: number;
|
|
32
|
-
}>;
|
|
29
|
+
sessions?: BrowserSessionInfo[];
|
|
30
|
+
profiles?: BrowserProfileStatus[];
|
|
33
31
|
issues: string[];
|
|
34
32
|
};
|
|
35
33
|
/**
|
package/dist/src/doctor.js
CHANGED
|
@@ -10,6 +10,7 @@ import { getDaemonHealth, listSessions } from './browser/daemon-client.js';
|
|
|
10
10
|
import { getErrorMessage } from './errors.js';
|
|
11
11
|
import { getRuntimeLabel } from './runtime-detect.js';
|
|
12
12
|
import { getCachedLatestExtensionVersion } from './update-check.js';
|
|
13
|
+
import { aliasForContextId, loadProfileConfig } from './browser/profile.js';
|
|
13
14
|
const DOCTOR_LIVE_TIMEOUT_SECONDS = 8;
|
|
14
15
|
/** Parse a semver string into [major, minor, patch]. Returns null on invalid input. */
|
|
15
16
|
function parseSemver(v) {
|
|
@@ -48,9 +49,14 @@ export async function checkConnectivity(opts) {
|
|
|
48
49
|
try {
|
|
49
50
|
const bridge = new BrowserBridge();
|
|
50
51
|
const page = await bridge.connect({ timeout: opts?.timeout ?? DOCTOR_LIVE_TIMEOUT_SECONDS });
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
try {
|
|
53
|
+
// Try a simple eval to verify end-to-end connectivity.
|
|
54
|
+
await page.evaluate('1 + 1');
|
|
55
|
+
await page.closeWindow?.();
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
await bridge.close();
|
|
59
|
+
}
|
|
54
60
|
return { ok: true, durationMs: Date.now() - start };
|
|
55
61
|
}
|
|
56
62
|
catch (err) {
|
|
@@ -84,9 +90,20 @@ export async function runBrowserDoctor(opts = {}) {
|
|
|
84
90
|
const extensionConnected = health.state === 'ready';
|
|
85
91
|
const daemonFlaky = !!(connectivity?.ok && !daemonRunning);
|
|
86
92
|
const extensionFlaky = !!(connectivity?.ok && daemonRunning && !extensionConnected);
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
const profiles = health.status?.profiles;
|
|
94
|
+
let sessions;
|
|
95
|
+
if (opts.sessions) {
|
|
96
|
+
if (profiles && profiles.length > 0) {
|
|
97
|
+
const grouped = await Promise.all(profiles.map(async (profile) => {
|
|
98
|
+
const rows = await listSessions({ contextId: profile.contextId }).catch(() => []);
|
|
99
|
+
return rows.map((row) => ({ ...row, contextId: row.contextId ?? profile.contextId }));
|
|
100
|
+
}));
|
|
101
|
+
sessions = grouped.flat();
|
|
102
|
+
}
|
|
103
|
+
else if (health.state === 'ready') {
|
|
104
|
+
sessions = await listSessions();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
90
107
|
const extensionVersion = health.status?.extensionVersion;
|
|
91
108
|
const issues = [];
|
|
92
109
|
if (daemonFlaky) {
|
|
@@ -101,23 +118,33 @@ export async function runBrowserDoctor(opts = {}) {
|
|
|
101
118
|
'This usually means the Browser Bridge service worker is reconnecting slowly or Chrome suspended it.');
|
|
102
119
|
}
|
|
103
120
|
else if (daemonRunning && !extensionConnected) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
'The daemon was started by an older CLI version and may have missed the extension registration.\n' +
|
|
112
|
-
' Quick fix: opencli daemon stop && opencli doctor');
|
|
121
|
+
if (health.state === 'profile-required') {
|
|
122
|
+
issues.push('Multiple Chrome profiles are connected to the daemon, but no default profile was selected.\n' +
|
|
123
|
+
' Run opencli profile list, then opencli profile use <name>, or pass --profile <name>.');
|
|
124
|
+
}
|
|
125
|
+
else if (health.state === 'profile-disconnected') {
|
|
126
|
+
issues.push(`Selected browser profile is not connected: ${health.status?.contextId ?? 'unknown'}.\n` +
|
|
127
|
+
' Open that Chrome profile and make sure the OpenCLI extension is enabled.');
|
|
113
128
|
}
|
|
114
129
|
else {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
130
|
+
const daemonVersion = health.status?.daemonVersion;
|
|
131
|
+
const isStale = opts.cliVersion && (!daemonVersion || daemonVersion !== opts.cliVersion);
|
|
132
|
+
if (isStale) {
|
|
133
|
+
const reason = daemonVersion
|
|
134
|
+
? `daemon v${daemonVersion} ≠ CLI v${opts.cliVersion}`
|
|
135
|
+
: `daemon predates version reporting, CLI is v${opts.cliVersion}`;
|
|
136
|
+
issues.push(`Stale daemon detected: ${reason}.\n` +
|
|
137
|
+
'The daemon was started by an older CLI version and may have missed the extension registration.\n' +
|
|
138
|
+
' Quick fix: opencli daemon stop && opencli doctor');
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
issues.push('Daemon is running but the Chrome/Chromium extension is not connected.\n' +
|
|
142
|
+
'If the extension is already installed, try: opencli daemon stop && opencli doctor\n' +
|
|
143
|
+
'If the extension is not installed:\n' +
|
|
144
|
+
' 1. Download from https://github.com/jackwener/opencli/releases\n' +
|
|
145
|
+
' 2. Open chrome://extensions/ → Enable Developer Mode\n' +
|
|
146
|
+
' 3. Click "Load unpacked" → select the extension folder');
|
|
147
|
+
}
|
|
121
148
|
}
|
|
122
149
|
}
|
|
123
150
|
if (extensionConnected && !extensionVersion) {
|
|
@@ -162,6 +189,7 @@ export async function runBrowserDoctor(opts = {}) {
|
|
|
162
189
|
latestExtensionVersion,
|
|
163
190
|
connectivity,
|
|
164
191
|
sessions,
|
|
192
|
+
profiles,
|
|
165
193
|
issues,
|
|
166
194
|
};
|
|
167
195
|
}
|
|
@@ -191,6 +219,17 @@ export function renderBrowserDoctorReport(report) {
|
|
|
191
219
|
? 'unstable (connected during live check, then disconnected)'
|
|
192
220
|
: report.extensionConnected ? 'connected' : 'not connected';
|
|
193
221
|
lines.push(`${extIcon} Extension: ${extLabel}${extVersion}`);
|
|
222
|
+
if (report.profiles && report.profiles.length > 0) {
|
|
223
|
+
const config = loadProfileConfig();
|
|
224
|
+
lines.push('', styleText('bold', 'Profiles:'));
|
|
225
|
+
for (const profile of report.profiles) {
|
|
226
|
+
const alias = aliasForContextId(config, profile.contextId);
|
|
227
|
+
const aliasText = alias ? ` (${alias})` : '';
|
|
228
|
+
const defaultText = config.defaultContextId === profile.contextId ? ', default' : '';
|
|
229
|
+
const version = profile.extensionVersion ? `v${profile.extensionVersion}` : 'version unknown';
|
|
230
|
+
lines.push(styleText('dim', ` • ${profile.contextId}${aliasText}: connected ${version}${defaultText}`));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
194
233
|
// Connectivity
|
|
195
234
|
if (report.connectivity) {
|
|
196
235
|
const connIcon = report.connectivity.ok ? styleText('green', '[OK]') : styleText('red', '[FAIL]');
|
|
@@ -208,8 +247,27 @@ export function renderBrowserDoctorReport(report) {
|
|
|
208
247
|
lines.push(styleText('dim', ' • no active automation sessions'));
|
|
209
248
|
}
|
|
210
249
|
else {
|
|
250
|
+
const byContext = new Map();
|
|
211
251
|
for (const session of report.sessions) {
|
|
212
|
-
|
|
252
|
+
const contextId = typeof session.contextId === 'string' && session.contextId ? session.contextId : 'default';
|
|
253
|
+
const rows = byContext.get(contextId) ?? [];
|
|
254
|
+
rows.push(session);
|
|
255
|
+
byContext.set(contextId, rows);
|
|
256
|
+
}
|
|
257
|
+
for (const [contextId, rows] of byContext) {
|
|
258
|
+
if (byContext.size > 1)
|
|
259
|
+
lines.push(styleText('dim', ` [profile: ${contextId}]`));
|
|
260
|
+
for (const session of rows) {
|
|
261
|
+
const idle = session.idleMsRemaining == null
|
|
262
|
+
? 'none'
|
|
263
|
+
: `${Math.ceil(session.idleMsRemaining / 1000)}s`;
|
|
264
|
+
const target = session.preferredTabId != null
|
|
265
|
+
? `tab ${session.preferredTabId}`
|
|
266
|
+
: `window ${session.windowId ?? 'unknown'}`;
|
|
267
|
+
const mode = session.ownership ?? (session.owned === false ? 'borrowed' : 'owned');
|
|
268
|
+
const surface = session.surface ? `, surface=${session.surface}` : '';
|
|
269
|
+
lines.push(styleText('dim', ` • ${session.workspace ?? 'default'} → ${target}, mode=${mode}${surface}, tabs=${session.tabCount ?? 0}, idle=${idle}`));
|
|
270
|
+
}
|
|
213
271
|
}
|
|
214
272
|
}
|
|
215
273
|
}
|
package/dist/src/doctor.test.js
CHANGED
|
@@ -78,6 +78,64 @@ describe('doctor report rendering', () => {
|
|
|
78
78
|
}));
|
|
79
79
|
expect(text).toContain('[SKIP] Connectivity: skipped (--no-live)');
|
|
80
80
|
});
|
|
81
|
+
it('renders sessions with tab leases and no idle timer', () => {
|
|
82
|
+
const text = strip(renderBrowserDoctorReport({
|
|
83
|
+
daemonRunning: true,
|
|
84
|
+
extensionConnected: true,
|
|
85
|
+
issues: [],
|
|
86
|
+
sessions: [
|
|
87
|
+
{
|
|
88
|
+
workspace: 'bound:default',
|
|
89
|
+
windowId: 2,
|
|
90
|
+
preferredTabId: 42,
|
|
91
|
+
ownership: 'borrowed',
|
|
92
|
+
surface: 'borrowed-user-tab',
|
|
93
|
+
tabCount: 1,
|
|
94
|
+
idleMsRemaining: null,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
}));
|
|
98
|
+
expect(text).toContain('bound:default → tab 42, mode=borrowed, surface=borrowed-user-tab, tabs=1, idle=none');
|
|
99
|
+
});
|
|
100
|
+
it('renders connected profiles and groups sessions by profile', () => {
|
|
101
|
+
const text = strip(renderBrowserDoctorReport({
|
|
102
|
+
daemonRunning: true,
|
|
103
|
+
extensionConnected: false,
|
|
104
|
+
profiles: [
|
|
105
|
+
{ contextId: 'work', extensionConnected: true, extensionVersion: '1.2.3', pending: 0 },
|
|
106
|
+
{ contextId: 'personal', extensionConnected: true, extensionVersion: '1.2.3', pending: 0 },
|
|
107
|
+
],
|
|
108
|
+
issues: [],
|
|
109
|
+
sessions: [
|
|
110
|
+
{
|
|
111
|
+
contextId: 'work',
|
|
112
|
+
workspace: 'bound:default',
|
|
113
|
+
windowId: 2,
|
|
114
|
+
preferredTabId: 42,
|
|
115
|
+
ownership: 'borrowed',
|
|
116
|
+
surface: 'borrowed-user-tab',
|
|
117
|
+
tabCount: 1,
|
|
118
|
+
idleMsRemaining: null,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
contextId: 'personal',
|
|
122
|
+
workspace: 'site:foo',
|
|
123
|
+
windowId: 1,
|
|
124
|
+
preferredTabId: 10,
|
|
125
|
+
ownership: 'owned',
|
|
126
|
+
surface: 'dedicated-container',
|
|
127
|
+
tabCount: 1,
|
|
128
|
+
idleMsRemaining: 1000,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
}));
|
|
132
|
+
expect(text).toContain('Profiles:');
|
|
133
|
+
expect(text).toContain('work: connected v1.2.3');
|
|
134
|
+
expect(text).toContain('[profile: work]');
|
|
135
|
+
expect(text).toContain('[profile: personal]');
|
|
136
|
+
expect(text).toContain('bound:default → tab 42');
|
|
137
|
+
expect(text).toContain('site:foo → tab 10');
|
|
138
|
+
});
|
|
81
139
|
it('renders unstable extension state when live connectivity and status disagree', () => {
|
|
82
140
|
const text = strip(renderBrowserDoctorReport({
|
|
83
141
|
daemonRunning: true,
|
|
@@ -142,16 +200,19 @@ describe('doctor report rendering', () => {
|
|
|
142
200
|
});
|
|
143
201
|
it('uses the fast default timeout for live connectivity checks', async () => {
|
|
144
202
|
let timeoutSeen;
|
|
203
|
+
const closeWindow = vi.fn().mockResolvedValue(undefined);
|
|
145
204
|
mockConnect.mockImplementationOnce(async (opts) => {
|
|
146
205
|
timeoutSeen = opts?.timeout;
|
|
147
206
|
return {
|
|
148
207
|
evaluate: vi.fn().mockResolvedValue(2),
|
|
208
|
+
closeWindow,
|
|
149
209
|
};
|
|
150
210
|
});
|
|
151
211
|
mockClose.mockResolvedValueOnce(undefined);
|
|
152
212
|
mockGetDaemonHealth.mockResolvedValueOnce({ state: 'ready', status: { extensionConnected: true } });
|
|
153
213
|
await runBrowserDoctor({ live: true });
|
|
154
214
|
expect(timeoutSeen).toBe(8);
|
|
215
|
+
expect(closeWindow).toHaveBeenCalledTimes(1);
|
|
155
216
|
});
|
|
156
217
|
it('skips auto-start in no-live mode when daemon is already running', async () => {
|
|
157
218
|
mockGetDaemonHealth.mockResolvedValueOnce({ state: 'no-extension', status: { extensionConnected: false } });
|
|
@@ -177,4 +238,25 @@ describe('doctor report rendering', () => {
|
|
|
177
238
|
expect.stringContaining('did not report a version'),
|
|
178
239
|
]));
|
|
179
240
|
});
|
|
241
|
+
it('reports profile-required when multiple profiles are connected without a selection', async () => {
|
|
242
|
+
const status = {
|
|
243
|
+
state: 'profile-required',
|
|
244
|
+
status: {
|
|
245
|
+
extensionConnected: false,
|
|
246
|
+
profileRequired: true,
|
|
247
|
+
profiles: [
|
|
248
|
+
{ contextId: 'work', extensionConnected: true, pending: 0 },
|
|
249
|
+
{ contextId: 'personal', extensionConnected: true, pending: 0 },
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
mockGetDaemonHealth
|
|
254
|
+
.mockResolvedValueOnce(status)
|
|
255
|
+
.mockResolvedValueOnce(status);
|
|
256
|
+
const report = await runBrowserDoctor({ live: false });
|
|
257
|
+
expect(report.profiles).toHaveLength(2);
|
|
258
|
+
expect(report.issues).toEqual(expect.arrayContaining([
|
|
259
|
+
expect.stringContaining('Multiple Chrome profiles are connected'),
|
|
260
|
+
]));
|
|
261
|
+
});
|
|
180
262
|
});
|
package/dist/src/engine.test.js
CHANGED
|
@@ -137,6 +137,7 @@ describe('discoverPlugins', () => {
|
|
|
137
137
|
const symlinkTargetDir = path.join(os.tmpdir(), '__test-plugin-symlink-target__');
|
|
138
138
|
const symlinkPluginDir = path.join(PLUGINS_DIR, '__test-plugin-symlink__');
|
|
139
139
|
const brokenSymlinkDir = path.join(PLUGINS_DIR, '__test-plugin-broken__');
|
|
140
|
+
const dirSymlinkType = process.platform === 'win32' ? 'junction' : 'dir';
|
|
140
141
|
afterEach(async () => {
|
|
141
142
|
try {
|
|
142
143
|
await fs.promises.rm(testPluginDir, { recursive: true });
|
|
@@ -183,14 +184,14 @@ description: Test plugin greeting via symlink
|
|
|
183
184
|
strategy: public
|
|
184
185
|
browser: false
|
|
185
186
|
`);
|
|
186
|
-
await fs.promises.symlink(symlinkTargetDir, symlinkPluginDir,
|
|
187
|
+
await fs.promises.symlink(symlinkTargetDir, symlinkPluginDir, dirSymlinkType);
|
|
187
188
|
await discoverPlugins();
|
|
188
189
|
const cmd = getRegistry().get('__test-plugin-symlink__/hello');
|
|
189
190
|
expect(cmd).toBeUndefined();
|
|
190
191
|
});
|
|
191
192
|
it('skips broken plugin symlinks without throwing', async () => {
|
|
192
193
|
await fs.promises.mkdir(PLUGINS_DIR, { recursive: true });
|
|
193
|
-
await fs.promises.symlink(path.join(os.tmpdir(), '__missing-plugin-target__'), brokenSymlinkDir,
|
|
194
|
+
await fs.promises.symlink(path.join(os.tmpdir(), '__missing-plugin-target__'), brokenSymlinkDir, dirSymlinkType);
|
|
194
195
|
await expect(discoverPlugins()).resolves.not.toThrow();
|
|
195
196
|
expect(getRegistry().get('__test-plugin-broken__/hello')).toBeUndefined();
|
|
196
197
|
});
|
|
@@ -210,7 +211,7 @@ describe('executeCommand', () => {
|
|
|
210
211
|
args: [
|
|
211
212
|
{ name: 'note-id', required: true, help: 'Note ID' },
|
|
212
213
|
],
|
|
213
|
-
func: async (
|
|
214
|
+
func: async (kwargs) => [{ noteId: kwargs['note-id'] }],
|
|
214
215
|
});
|
|
215
216
|
const result = await executeCommand(cmd, { 'note-id': 'abc123' });
|
|
216
217
|
expect(result).toEqual([{ noteId: 'abc123' }]);
|
|
@@ -222,7 +223,7 @@ describe('executeCommand', () => {
|
|
|
222
223
|
description: 'test command with func',
|
|
223
224
|
browser: false,
|
|
224
225
|
strategy: Strategy.PUBLIC,
|
|
225
|
-
func: async (
|
|
226
|
+
func: async (kwargs) => {
|
|
226
227
|
return [{ title: kwargs.query ?? 'default' }];
|
|
227
228
|
},
|
|
228
229
|
});
|
|
@@ -260,7 +261,7 @@ describe('executeCommand', () => {
|
|
|
260
261
|
name: 'debug-test',
|
|
261
262
|
description: 'debug test',
|
|
262
263
|
browser: false,
|
|
263
|
-
func: async (
|
|
264
|
+
func: async (_kwargs, debug) => {
|
|
264
265
|
receivedDebug = debug ?? false;
|
|
265
266
|
return [];
|
|
266
267
|
},
|
package/dist/src/errors.d.ts
CHANGED
|
@@ -13,12 +13,13 @@
|
|
|
13
13
|
* 1 Generic / unexpected error
|
|
14
14
|
* 2 Argument / usage error (ArgumentError)
|
|
15
15
|
* 66 No input / empty result (EmptyResultError)
|
|
16
|
-
* 69 Service unavailable (BrowserConnectError,
|
|
16
|
+
* 69 Service unavailable (BrowserConnectError, adapter load failures)
|
|
17
17
|
* 75 Temporary failure, retry later (TimeoutError) EX_TEMPFAIL
|
|
18
18
|
* 77 Permission denied / auth needed (AuthRequiredError)
|
|
19
19
|
* 78 Configuration error (ConfigError)
|
|
20
20
|
* 130 Interrupted by Ctrl-C (set by tui.ts SIGINT handler)
|
|
21
21
|
*/
|
|
22
|
+
import type { ObservationTraceReceipt } from './observation/events.js';
|
|
22
23
|
export declare const EXIT_CODES: {
|
|
23
24
|
readonly SUCCESS: 0;
|
|
24
25
|
readonly GENERIC_ERROR: 1;
|
|
@@ -40,14 +41,13 @@ export declare class CliError extends Error {
|
|
|
40
41
|
readonly exitCode: ExitCode;
|
|
41
42
|
constructor(code: string, message: string, hint?: string, exitCode?: ExitCode);
|
|
42
43
|
}
|
|
43
|
-
export
|
|
44
|
+
export declare function attachTraceReceipt(err: unknown, receipt: ObservationTraceReceipt): void;
|
|
45
|
+
export declare function getTraceReceipt(err: unknown): ObservationTraceReceipt | undefined;
|
|
46
|
+
export type BrowserConnectKind = 'daemon-not-running' | 'extension-not-connected' | 'profile-required' | 'profile-disconnected' | 'command-failed' | 'unknown';
|
|
44
47
|
export declare class BrowserConnectError extends CliError {
|
|
45
48
|
readonly kind: BrowserConnectKind;
|
|
46
49
|
constructor(message: string, hint?: string, kind?: BrowserConnectKind);
|
|
47
50
|
}
|
|
48
|
-
export declare class AdapterLoadError extends CliError {
|
|
49
|
-
constructor(message: string, hint?: string);
|
|
50
|
-
}
|
|
51
51
|
export declare class CommandExecutionError extends CliError {
|
|
52
52
|
constructor(message: string, hint?: string);
|
|
53
53
|
}
|
|
@@ -67,9 +67,8 @@ export declare class ArgumentError extends CliError {
|
|
|
67
67
|
export declare class EmptyResultError extends CliError {
|
|
68
68
|
constructor(command: string, hint?: string);
|
|
69
69
|
}
|
|
70
|
-
export declare
|
|
71
|
-
|
|
72
|
-
}
|
|
70
|
+
export declare function adapterLoadError(message: string, hint?: string): CliError;
|
|
71
|
+
export declare function selectorError(selector: string, hint?: string): CliError;
|
|
73
72
|
export declare class PluginError extends CliError {
|
|
74
73
|
constructor(message: string, hint?: string);
|
|
75
74
|
}
|
|
@@ -84,6 +83,13 @@ export interface ErrorEnvelope {
|
|
|
84
83
|
stack?: string;
|
|
85
84
|
cause?: string;
|
|
86
85
|
};
|
|
86
|
+
trace?: {
|
|
87
|
+
traceId: string;
|
|
88
|
+
dir: string;
|
|
89
|
+
summaryPath: string;
|
|
90
|
+
receiptPath: string;
|
|
91
|
+
status: ObservationTraceReceipt['status'];
|
|
92
|
+
};
|
|
87
93
|
}
|
|
88
94
|
/** Extract a human-readable message from an unknown caught value. */
|
|
89
95
|
export declare function getErrorMessage(error: unknown): string;
|