@jackwener/opencli 1.7.8 → 1.7.10
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 +646 -30
- 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/instagram/collection-create.js +57 -0
- package/clis/instagram/saved.js +21 -7
- 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 -2
- package/dist/src/browser/bridge.js +40 -41
- 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/daemon-lifecycle.d.ts +23 -0
- package/dist/src/browser/daemon-lifecycle.js +67 -0
- package/dist/src/browser/daemon-version.d.ts +4 -0
- package/dist/src/browser/daemon-version.js +12 -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 +477 -35
- package/dist/src/cli.test.js +303 -2
- package/dist/src/commanderAdapter.js +17 -9
- package/dist/src/commanderAdapter.test.js +67 -2
- package/dist/src/commands/daemon.d.ts +2 -0
- package/dist/src/commands/daemon.js +42 -1
- package/dist/src/commands/daemon.test.js +103 -2
- 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 +5 -6
- package/dist/src/doctor.js +77 -19
- package/dist/src/doctor.test.js +117 -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/execution.js
CHANGED
|
@@ -9,22 +9,32 @@
|
|
|
9
9
|
* 5. Lazy-loading of TS modules from manifest
|
|
10
10
|
* 6. Lifecycle hooks (onBeforeExecute / onAfterExecute)
|
|
11
11
|
*/
|
|
12
|
-
import { getRegistry, fullName } from './registry.js';
|
|
12
|
+
import { getRegistry, fullName, } from './registry.js';
|
|
13
13
|
import { pathToFileURL } from 'node:url';
|
|
14
14
|
import * as fs from 'node:fs';
|
|
15
15
|
import * as os from 'node:os';
|
|
16
16
|
import { executePipeline } from './pipeline/index.js';
|
|
17
|
-
import {
|
|
18
|
-
import { isDiagnosticEnabled, collectDiagnostic, emitDiagnostic } from './diagnostic.js';
|
|
17
|
+
import { adapterLoadError, ArgumentError, CommandExecutionError, attachTraceReceipt, getErrorMessage } from './errors.js';
|
|
19
18
|
import { shouldUseBrowserSession } from './capabilityRouting.js';
|
|
20
19
|
import { getBrowserFactory, browserSession, runWithTimeout, DEFAULT_BROWSER_COMMAND_TIMEOUT } from './runtime.js';
|
|
20
|
+
import { resolveProfileContextId } from './browser/profile.js';
|
|
21
21
|
import { emitHook } from './hooks.js';
|
|
22
|
+
import { log } from './logger.js';
|
|
22
23
|
import { isElectronApp } from './electron-apps.js';
|
|
23
24
|
import { probeCDP, resolveElectronEndpoint } from './launcher.js';
|
|
25
|
+
import { ObservationSession, exportObservationSession } from './observation/index.js';
|
|
26
|
+
import { resolveAdapterSourcePath } from './adapter-source.js';
|
|
24
27
|
const _loadedModules = new Map();
|
|
25
28
|
/** Track mtime of loaded user adapter files for hot-reload in daemon mode. */
|
|
26
29
|
const _moduleMtimes = new Map();
|
|
27
30
|
const _userClisDir = `${os.homedir()}/.opencli/clis/`;
|
|
31
|
+
function normalizeTraceMode(raw) {
|
|
32
|
+
if (raw === undefined || raw === null || raw === '' || raw === 'off')
|
|
33
|
+
return 'off';
|
|
34
|
+
if (raw === 'on' || raw === 'retain-on-failure')
|
|
35
|
+
return raw;
|
|
36
|
+
throw new ArgumentError(`--trace must be one of: off, on, retain-on-failure. Received: "${String(raw)}"`);
|
|
37
|
+
}
|
|
28
38
|
export function coerceAndValidateArgs(cmdArgs, kwargs) {
|
|
29
39
|
const result = { ...kwargs };
|
|
30
40
|
for (const argDef of cmdArgs) {
|
|
@@ -94,27 +104,32 @@ async function runCommand(cmd, page, kwargs, debug) {
|
|
|
94
104
|
catch { }
|
|
95
105
|
}, (err) => {
|
|
96
106
|
_loadedModules.delete(modulePath);
|
|
97
|
-
throw
|
|
107
|
+
throw adapterLoadError(`Failed to load adapter module ${modulePath}: ${getErrorMessage(err)}`, 'Check that the adapter file exists and has no syntax errors.');
|
|
98
108
|
});
|
|
99
109
|
_loadedModules.set(modulePath, loadPromise);
|
|
100
110
|
}
|
|
101
111
|
await _loadedModules.get(modulePath);
|
|
102
112
|
const updated = getRegistry().get(fullName(cmd));
|
|
103
113
|
if (updated?.func) {
|
|
104
|
-
|
|
105
|
-
throw new CommandExecutionError(`Command ${fullName(cmd)} requires a browser session but none was provided`);
|
|
106
|
-
}
|
|
107
|
-
return updated.func(page, kwargs, debug);
|
|
114
|
+
return runCommandFunc(updated, page, kwargs, debug);
|
|
108
115
|
}
|
|
109
116
|
if (updated?.pipeline)
|
|
110
117
|
return executePipeline(page, updated.pipeline, { args: kwargs, debug });
|
|
111
118
|
}
|
|
112
119
|
if (cmd.func)
|
|
113
|
-
return cmd
|
|
120
|
+
return runCommandFunc(cmd, page, kwargs, debug);
|
|
114
121
|
if (cmd.pipeline)
|
|
115
122
|
return executePipeline(page, cmd.pipeline, { args: kwargs, debug });
|
|
116
123
|
throw new CommandExecutionError(`Command ${fullName(cmd)} has no func or pipeline`, 'This is likely a bug in the adapter definition. Please report this issue.');
|
|
117
124
|
}
|
|
125
|
+
function runCommandFunc(cmd, page, kwargs, debug) {
|
|
126
|
+
if (cmd.browser === false)
|
|
127
|
+
return cmd.func(kwargs, debug);
|
|
128
|
+
if (!page) {
|
|
129
|
+
throw new CommandExecutionError(`Command ${fullName(cmd)} requires a browser session but none was provided`);
|
|
130
|
+
}
|
|
131
|
+
return cmd.func(page, kwargs, debug);
|
|
132
|
+
}
|
|
118
133
|
function resolvePreNav(cmd) {
|
|
119
134
|
if (cmd.navigateBefore === false)
|
|
120
135
|
return null;
|
|
@@ -142,6 +157,7 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
142
157
|
throw err;
|
|
143
158
|
throw new ArgumentError(getErrorMessage(err));
|
|
144
159
|
}
|
|
160
|
+
const traceMode = normalizeTraceMode(opts.trace);
|
|
145
161
|
const hookCtx = {
|
|
146
162
|
command: fullName(cmd),
|
|
147
163
|
args: kwargs,
|
|
@@ -149,7 +165,6 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
149
165
|
};
|
|
150
166
|
await emitHook('onBeforeExecute', hookCtx);
|
|
151
167
|
let result;
|
|
152
|
-
let diagnosticEmitted = false;
|
|
153
168
|
try {
|
|
154
169
|
if (shouldUseBrowserSession(cmd)) {
|
|
155
170
|
const electron = isElectronApp(cmd.site);
|
|
@@ -170,9 +185,38 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
170
185
|
}
|
|
171
186
|
ensureRequiredEnv(cmd);
|
|
172
187
|
const BrowserFactory = getBrowserFactory(cmd.site);
|
|
188
|
+
const contextId = resolveProfileContextId(opts.profile);
|
|
189
|
+
const internal = cmd;
|
|
173
190
|
result = await browserSession(BrowserFactory, async (page) => {
|
|
191
|
+
const observation = traceMode === 'off'
|
|
192
|
+
? null
|
|
193
|
+
: new ObservationSession({
|
|
194
|
+
scope: {
|
|
195
|
+
contextId,
|
|
196
|
+
workspace: `site:${cmd.site}`,
|
|
197
|
+
target: page.getActivePage?.(),
|
|
198
|
+
site: cmd.site,
|
|
199
|
+
command: fullName(cmd),
|
|
200
|
+
adapterSourcePath: resolveAdapterSourcePath(internal),
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
if (observation) {
|
|
204
|
+
observation.record({
|
|
205
|
+
stream: 'action',
|
|
206
|
+
name: 'command',
|
|
207
|
+
phase: 'start',
|
|
208
|
+
data: { args: kwargs },
|
|
209
|
+
});
|
|
210
|
+
await page.startNetworkCapture?.().catch(() => false);
|
|
211
|
+
}
|
|
174
212
|
const preNavUrl = resolvePreNav(cmd);
|
|
175
213
|
if (preNavUrl) {
|
|
214
|
+
observation?.record({
|
|
215
|
+
stream: 'action',
|
|
216
|
+
name: 'pre_navigate',
|
|
217
|
+
phase: 'start',
|
|
218
|
+
data: { url: preNavUrl },
|
|
219
|
+
});
|
|
176
220
|
// Navigate directly — the extension's handleNavigate already has a fast-path
|
|
177
221
|
// that skips navigation if the tab is already at the target URL.
|
|
178
222
|
// This avoids an extra exec round-trip (getCurrentUrl) on first command and
|
|
@@ -180,9 +224,33 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
180
224
|
// instead of about:blank.
|
|
181
225
|
try {
|
|
182
226
|
await page.goto(preNavUrl);
|
|
227
|
+
observation?.record({
|
|
228
|
+
stream: 'action',
|
|
229
|
+
name: 'pre_navigate',
|
|
230
|
+
phase: 'end',
|
|
231
|
+
data: { url: preNavUrl },
|
|
232
|
+
});
|
|
183
233
|
}
|
|
184
234
|
catch (err) {
|
|
185
|
-
|
|
235
|
+
observation?.record({
|
|
236
|
+
stream: 'action',
|
|
237
|
+
name: 'pre_navigate',
|
|
238
|
+
phase: 'error',
|
|
239
|
+
data: { url: preNavUrl, error: err instanceof Error ? err.message : String(err) },
|
|
240
|
+
});
|
|
241
|
+
const wrapped = new CommandExecutionError(`Pre-navigation to ${preNavUrl} failed: ${err instanceof Error ? err.message : err}`, 'Check that the site is reachable and the browser extension is running.');
|
|
242
|
+
if (observation && (traceMode === 'on' || traceMode === 'retain-on-failure')) {
|
|
243
|
+
observation.record({
|
|
244
|
+
stream: 'error',
|
|
245
|
+
message: wrapped.message,
|
|
246
|
+
stack: wrapped.stack,
|
|
247
|
+
code: wrapped.code,
|
|
248
|
+
hint: wrapped.hint,
|
|
249
|
+
});
|
|
250
|
+
await collectObservationEvidence(observation, page).catch(() => { });
|
|
251
|
+
exportTraceArtifact(observation, 'failure', wrapped, opts.onTraceExport);
|
|
252
|
+
}
|
|
253
|
+
throw wrapped;
|
|
186
254
|
}
|
|
187
255
|
}
|
|
188
256
|
// --live / OPENCLI_LIVE=1 keeps the automation window open after the
|
|
@@ -193,6 +261,15 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
193
261
|
timeout: cmd.timeoutSeconds ?? DEFAULT_BROWSER_COMMAND_TIMEOUT,
|
|
194
262
|
label: fullName(cmd),
|
|
195
263
|
});
|
|
264
|
+
observation?.record({
|
|
265
|
+
stream: 'action',
|
|
266
|
+
name: 'command',
|
|
267
|
+
phase: 'end',
|
|
268
|
+
});
|
|
269
|
+
if (observation && traceMode === 'on') {
|
|
270
|
+
await collectObservationEvidence(observation, page).catch(() => { });
|
|
271
|
+
exportTraceArtifact(observation, 'success', undefined, opts.onTraceExport);
|
|
272
|
+
}
|
|
196
273
|
// Adapter commands are one-shot — close the automation window immediately
|
|
197
274
|
// instead of waiting for the 30s idle timeout.
|
|
198
275
|
if (!keepOpen)
|
|
@@ -200,12 +277,22 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
200
277
|
return result;
|
|
201
278
|
}
|
|
202
279
|
catch (err) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
280
|
+
if (observation) {
|
|
281
|
+
observation.record({
|
|
282
|
+
stream: 'action',
|
|
283
|
+
name: 'command',
|
|
284
|
+
phase: 'error',
|
|
285
|
+
data: { error: err instanceof Error ? err.message : String(err) },
|
|
286
|
+
});
|
|
287
|
+
observation.record({
|
|
288
|
+
stream: 'error',
|
|
289
|
+
message: err instanceof Error ? err.message : String(err),
|
|
290
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
291
|
+
});
|
|
292
|
+
if (traceMode === 'on' || traceMode === 'retain-on-failure') {
|
|
293
|
+
await collectObservationEvidence(observation, page).catch(() => { });
|
|
294
|
+
exportTraceArtifact(observation, 'failure', err, opts.onTraceExport);
|
|
295
|
+
}
|
|
209
296
|
}
|
|
210
297
|
// Close the automation window on failure too — without this, the window
|
|
211
298
|
// lingers until the extension's idle timer fires (unreliable on Windows
|
|
@@ -214,7 +301,7 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
214
301
|
await page.closeWindow?.().catch(() => { });
|
|
215
302
|
throw err;
|
|
216
303
|
}
|
|
217
|
-
}, { workspace: `site:${cmd.site}`, cdpEndpoint });
|
|
304
|
+
}, { workspace: `site:${cmd.site}`, cdpEndpoint, contextId });
|
|
218
305
|
}
|
|
219
306
|
else {
|
|
220
307
|
// Non-browser commands: apply timeout only when explicitly configured.
|
|
@@ -232,13 +319,6 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
232
319
|
}
|
|
233
320
|
}
|
|
234
321
|
catch (err) {
|
|
235
|
-
// Emit diagnostic if not already emitted (browser session emits with page state;
|
|
236
|
-
// this fallback covers non-browser commands and pre-session failures like BrowserConnectError).
|
|
237
|
-
if (isDiagnosticEnabled() && !diagnosticEmitted) {
|
|
238
|
-
const internal = cmd;
|
|
239
|
-
const ctx = await collectDiagnostic(err, internal, null);
|
|
240
|
-
emitDiagnostic(ctx);
|
|
241
|
-
}
|
|
242
322
|
hookCtx.error = err;
|
|
243
323
|
hookCtx.finishedAt = Date.now();
|
|
244
324
|
await emitHook('onAfterExecute', hookCtx);
|
|
@@ -248,6 +328,74 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
248
328
|
await emitHook('onAfterExecute', hookCtx, result);
|
|
249
329
|
return result;
|
|
250
330
|
}
|
|
331
|
+
async function collectObservationEvidence(session, page) {
|
|
332
|
+
const target = page.getActivePage?.() ?? session.scope.target;
|
|
333
|
+
const [url, snapshot, networkEntries, consoleMessages, screenshot] = await Promise.all([
|
|
334
|
+
page.getCurrentUrl?.().catch(() => null) ?? Promise.resolve(null),
|
|
335
|
+
page.snapshot().catch(() => undefined),
|
|
336
|
+
page.readNetworkCapture?.().catch(() => []) ?? Promise.resolve([]),
|
|
337
|
+
page.consoleMessages('all').catch(() => []),
|
|
338
|
+
page.screenshot({ format: 'png' }).catch(() => undefined),
|
|
339
|
+
]);
|
|
340
|
+
if (snapshot !== undefined || url !== undefined) {
|
|
341
|
+
session.record({ stream: 'state', url, target, snapshot, label: 'final' });
|
|
342
|
+
}
|
|
343
|
+
for (const entry of Array.isArray(networkEntries) ? networkEntries : []) {
|
|
344
|
+
const record = entry;
|
|
345
|
+
session.record({
|
|
346
|
+
stream: 'network',
|
|
347
|
+
url: String(record.url ?? ''),
|
|
348
|
+
method: typeof record.method === 'string' ? record.method : undefined,
|
|
349
|
+
status: typeof record.responseStatus === 'number' ? record.responseStatus : undefined,
|
|
350
|
+
contentType: typeof record.responseContentType === 'string' ? record.responseContentType : undefined,
|
|
351
|
+
size: typeof record.responseBodyFullSize === 'number' ? record.responseBodyFullSize : undefined,
|
|
352
|
+
requestHeaders: record.requestHeaders,
|
|
353
|
+
responseHeaders: record.responseHeaders,
|
|
354
|
+
requestBody: record.requestBodyPreview,
|
|
355
|
+
responseBody: record.responsePreview,
|
|
356
|
+
ts: typeof record.timestamp === 'number' ? record.timestamp : undefined,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
for (const message of Array.isArray(consoleMessages) ? consoleMessages : []) {
|
|
360
|
+
if (message && typeof message === 'object') {
|
|
361
|
+
const record = message;
|
|
362
|
+
session.record({
|
|
363
|
+
stream: 'console',
|
|
364
|
+
level: String(record.type ?? record.level ?? 'log'),
|
|
365
|
+
text: String(record.text ?? record.message ?? ''),
|
|
366
|
+
ts: typeof record.timestamp === 'number' ? record.timestamp : undefined,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
session.record({ stream: 'console', level: 'log', text: String(message) });
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (typeof screenshot === 'string' && screenshot) {
|
|
374
|
+
session.record({ stream: 'screenshot', format: 'png', data: screenshot, label: 'final' });
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function exportTraceArtifact(session, status, error, onTraceExport) {
|
|
378
|
+
try {
|
|
379
|
+
const trace = exportObservationSession(session, { error, status });
|
|
380
|
+
if (status === 'failure' && error !== undefined) {
|
|
381
|
+
attachTraceReceipt(error, trace.receipt);
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
process.stderr.write(`OpenCLI trace artifact: ${trace.dir}\n`);
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
onTraceExport?.(trace);
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
log.warn(`[trace] Trace export callback failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
391
|
+
}
|
|
392
|
+
return trace;
|
|
393
|
+
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
log.warn(`[trace] Failed to export trace artifact: ${err instanceof Error ? err.message : String(err)}`);
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
251
399
|
export function prepareCommandArgs(cmd, rawKwargs) {
|
|
252
400
|
const kwargs = coerceAndValidateArgs(cmd.args, rawKwargs);
|
|
253
401
|
cmd.validateArgs?.(kwargs);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
2
5
|
import { executeCommand, prepareCommandArgs } from './execution.js';
|
|
3
|
-
import { TimeoutError } from './errors.js';
|
|
6
|
+
import { TimeoutError, toEnvelope } from './errors.js';
|
|
4
7
|
import { cli, Strategy } from './registry.js';
|
|
5
8
|
import { withTimeoutMs } from './runtime.js';
|
|
6
9
|
import * as runtime from './runtime.js';
|
|
@@ -130,4 +133,171 @@ describe('executeCommand — non-browser timeout', () => {
|
|
|
130
133
|
await executeCommand(cmd, kwargs, false, { prepared: true });
|
|
131
134
|
expect(validateArgs).toHaveBeenCalledTimes(1);
|
|
132
135
|
});
|
|
136
|
+
it('exports a profile-scoped trace artifact on browser command failure when requested', async () => {
|
|
137
|
+
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-exec-trace-'));
|
|
138
|
+
const prevConfigDir = process.env.OPENCLI_CONFIG_DIR;
|
|
139
|
+
process.env.OPENCLI_CONFIG_DIR = baseDir;
|
|
140
|
+
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
141
|
+
const closeWindow = vi.fn().mockResolvedValue(undefined);
|
|
142
|
+
const mockPage = {
|
|
143
|
+
closeWindow,
|
|
144
|
+
startNetworkCapture: vi.fn().mockResolvedValue(true),
|
|
145
|
+
readNetworkCapture: vi.fn().mockResolvedValue([
|
|
146
|
+
{
|
|
147
|
+
url: 'https://api.example.com/data?token=secret',
|
|
148
|
+
method: 'GET',
|
|
149
|
+
responseStatus: 500,
|
|
150
|
+
responseContentType: 'application/json',
|
|
151
|
+
responsePreview: JSON.stringify({ password: 'secret', ok: false }),
|
|
152
|
+
requestHeaders: { authorization: 'Bearer secret' },
|
|
153
|
+
timestamp: Date.now(),
|
|
154
|
+
},
|
|
155
|
+
]),
|
|
156
|
+
consoleMessages: vi.fn().mockResolvedValue([{ type: 'error', text: 'boom password=secret', timestamp: Date.now() }]),
|
|
157
|
+
snapshot: vi.fn().mockResolvedValue({ html: '<input type="password" value="secret">' }),
|
|
158
|
+
screenshot: vi.fn().mockResolvedValue(Buffer.from('png').toString('base64')),
|
|
159
|
+
getCurrentUrl: vi.fn().mockResolvedValue('https://api.example.com/app'),
|
|
160
|
+
getActivePage: vi.fn().mockReturnValue('tab-1'),
|
|
161
|
+
};
|
|
162
|
+
vi.spyOn(capRouting, 'shouldUseBrowserSession').mockReturnValue(true);
|
|
163
|
+
vi.spyOn(runtime, 'browserSession').mockImplementation(async (_Factory, fn) => fn(mockPage));
|
|
164
|
+
try {
|
|
165
|
+
const cmd = cli({
|
|
166
|
+
site: 'test-execution',
|
|
167
|
+
name: 'browser-trace-failure',
|
|
168
|
+
description: 'test trace export',
|
|
169
|
+
browser: true,
|
|
170
|
+
strategy: Strategy.PUBLIC,
|
|
171
|
+
func: async () => { throw new Error('adapter failure'); },
|
|
172
|
+
});
|
|
173
|
+
const thrown = await executeCommand(cmd, {}, false, { trace: 'retain-on-failure' }).catch((err) => err);
|
|
174
|
+
expect(thrown).toBeInstanceOf(Error);
|
|
175
|
+
expect(thrown.message).toContain('adapter failure');
|
|
176
|
+
const tracesRoot = path.join(baseDir, 'profiles', 'default', 'traces');
|
|
177
|
+
const traceId = fs.readdirSync(tracesRoot)[0];
|
|
178
|
+
const traceDir = path.join(tracesRoot, traceId);
|
|
179
|
+
expect(fs.existsSync(path.join(traceDir, 'trace.jsonl'))).toBe(true);
|
|
180
|
+
expect(fs.existsSync(path.join(traceDir, 'receipt.json'))).toBe(true);
|
|
181
|
+
const trace = fs.readFileSync(path.join(traceDir, 'trace.jsonl'), 'utf-8');
|
|
182
|
+
expect(trace).toContain('token=[REDACTED]');
|
|
183
|
+
expect(trace).toContain('"authorization":"[REDACTED]"');
|
|
184
|
+
expect(trace).not.toContain('password=secret');
|
|
185
|
+
expect(stderrSpy.mock.calls.flat().join('\n')).not.toContain('___OPENCLI_TRACE___');
|
|
186
|
+
expect(toEnvelope(thrown).trace).toMatchObject({
|
|
187
|
+
traceId,
|
|
188
|
+
dir: traceDir,
|
|
189
|
+
summaryPath: path.join(traceDir, 'summary.md'),
|
|
190
|
+
receiptPath: path.join(traceDir, 'receipt.json'),
|
|
191
|
+
status: 'failure',
|
|
192
|
+
});
|
|
193
|
+
expect(closeWindow).toHaveBeenCalledTimes(1);
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
if (prevConfigDir === undefined)
|
|
197
|
+
delete process.env.OPENCLI_CONFIG_DIR;
|
|
198
|
+
else
|
|
199
|
+
process.env.OPENCLI_CONFIG_DIR = prevConfigDir;
|
|
200
|
+
stderrSpy.mockRestore();
|
|
201
|
+
fs.rmSync(baseDir, { recursive: true, force: true });
|
|
202
|
+
vi.restoreAllMocks();
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
it('exports a trace receipt on browser command success when trace is on', async () => {
|
|
206
|
+
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-exec-trace-success-'));
|
|
207
|
+
const prevConfigDir = process.env.OPENCLI_CONFIG_DIR;
|
|
208
|
+
process.env.OPENCLI_CONFIG_DIR = baseDir;
|
|
209
|
+
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
210
|
+
const onTraceExport = vi.fn();
|
|
211
|
+
const closeWindow = vi.fn().mockResolvedValue(undefined);
|
|
212
|
+
const mockPage = {
|
|
213
|
+
closeWindow,
|
|
214
|
+
startNetworkCapture: vi.fn().mockResolvedValue(true),
|
|
215
|
+
readNetworkCapture: vi.fn().mockResolvedValue([]),
|
|
216
|
+
consoleMessages: vi.fn().mockResolvedValue([]),
|
|
217
|
+
snapshot: vi.fn().mockResolvedValue('snapshot'),
|
|
218
|
+
screenshot: vi.fn().mockResolvedValue(Buffer.from('png').toString('base64')),
|
|
219
|
+
getCurrentUrl: vi.fn().mockResolvedValue('https://example.com'),
|
|
220
|
+
getActivePage: vi.fn().mockReturnValue('tab-1'),
|
|
221
|
+
};
|
|
222
|
+
vi.spyOn(capRouting, 'shouldUseBrowserSession').mockReturnValue(true);
|
|
223
|
+
vi.spyOn(runtime, 'browserSession').mockImplementation(async (_Factory, fn) => fn(mockPage));
|
|
224
|
+
try {
|
|
225
|
+
const cmd = cli({
|
|
226
|
+
site: 'test-execution',
|
|
227
|
+
name: 'browser-trace-success',
|
|
228
|
+
description: 'test trace export on success',
|
|
229
|
+
browser: true,
|
|
230
|
+
strategy: Strategy.PUBLIC,
|
|
231
|
+
func: async () => [{ ok: true }],
|
|
232
|
+
});
|
|
233
|
+
await expect(executeCommand(cmd, {}, false, { trace: 'on', onTraceExport })).resolves.toEqual([{ ok: true }]);
|
|
234
|
+
const stderr = stderrSpy.mock.calls.flat().join('\n');
|
|
235
|
+
expect(stderr).toContain('OpenCLI trace artifact:');
|
|
236
|
+
const tracesRoot = path.join(baseDir, 'profiles', 'default', 'traces');
|
|
237
|
+
const traceId = fs.readdirSync(tracesRoot)[0];
|
|
238
|
+
const receipt = JSON.parse(fs.readFileSync(path.join(tracesRoot, traceId, 'receipt.json'), 'utf-8'));
|
|
239
|
+
expect(receipt.status).toBe('success');
|
|
240
|
+
expect(receipt.traceDir).toContain(path.join(baseDir, 'profiles', 'default', 'traces'));
|
|
241
|
+
expect(receipt.scope).toMatchObject({
|
|
242
|
+
site: 'test-execution',
|
|
243
|
+
command: 'test-execution/browser-trace-success',
|
|
244
|
+
});
|
|
245
|
+
expect(receipt.error).toBeUndefined();
|
|
246
|
+
expect(onTraceExport).toHaveBeenCalledWith(expect.objectContaining({
|
|
247
|
+
traceId,
|
|
248
|
+
receipt: expect.objectContaining({ status: 'success' }),
|
|
249
|
+
}));
|
|
250
|
+
expect(closeWindow).toHaveBeenCalledTimes(1);
|
|
251
|
+
}
|
|
252
|
+
finally {
|
|
253
|
+
if (prevConfigDir === undefined)
|
|
254
|
+
delete process.env.OPENCLI_CONFIG_DIR;
|
|
255
|
+
else
|
|
256
|
+
process.env.OPENCLI_CONFIG_DIR = prevConfigDir;
|
|
257
|
+
stderrSpy.mockRestore();
|
|
258
|
+
fs.rmSync(baseDir, { recursive: true, force: true });
|
|
259
|
+
vi.restoreAllMocks();
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
it('keeps the original adapter error when trace export fails', async () => {
|
|
263
|
+
const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-exec-trace-fail-'));
|
|
264
|
+
const blockedPath = path.join(baseDir, 'not-a-dir');
|
|
265
|
+
fs.writeFileSync(blockedPath, 'file');
|
|
266
|
+
const prevConfigDir = process.env.OPENCLI_CONFIG_DIR;
|
|
267
|
+
process.env.OPENCLI_CONFIG_DIR = blockedPath;
|
|
268
|
+
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
269
|
+
const mockPage = {
|
|
270
|
+
closeWindow: vi.fn().mockResolvedValue(undefined),
|
|
271
|
+
startNetworkCapture: vi.fn().mockResolvedValue(true),
|
|
272
|
+
readNetworkCapture: vi.fn().mockResolvedValue([]),
|
|
273
|
+
consoleMessages: vi.fn().mockResolvedValue([]),
|
|
274
|
+
snapshot: vi.fn().mockResolvedValue('snapshot'),
|
|
275
|
+
screenshot: vi.fn().mockResolvedValue(Buffer.from('png').toString('base64')),
|
|
276
|
+
getCurrentUrl: vi.fn().mockResolvedValue('https://example.com'),
|
|
277
|
+
getActivePage: vi.fn().mockReturnValue('tab-1'),
|
|
278
|
+
};
|
|
279
|
+
vi.spyOn(capRouting, 'shouldUseBrowserSession').mockReturnValue(true);
|
|
280
|
+
vi.spyOn(runtime, 'browserSession').mockImplementation(async (_Factory, fn) => fn(mockPage));
|
|
281
|
+
try {
|
|
282
|
+
const cmd = cli({
|
|
283
|
+
site: 'test-execution',
|
|
284
|
+
name: 'browser-trace-export-fails',
|
|
285
|
+
description: 'test trace export failure handling',
|
|
286
|
+
browser: true,
|
|
287
|
+
strategy: Strategy.PUBLIC,
|
|
288
|
+
func: async () => { throw new Error('adapter failure'); },
|
|
289
|
+
});
|
|
290
|
+
await expect(executeCommand(cmd, {}, false, { trace: 'retain-on-failure' })).rejects.toThrow('adapter failure');
|
|
291
|
+
expect(stderrSpy.mock.calls.flat().join('\n')).toContain('[trace] Failed to export trace artifact');
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
if (prevConfigDir === undefined)
|
|
295
|
+
delete process.env.OPENCLI_CONFIG_DIR;
|
|
296
|
+
else
|
|
297
|
+
process.env.OPENCLI_CONFIG_DIR = prevConfigDir;
|
|
298
|
+
stderrSpy.mockRestore();
|
|
299
|
+
fs.rmSync(baseDir, { recursive: true, force: true });
|
|
300
|
+
vi.restoreAllMocks();
|
|
301
|
+
}
|
|
302
|
+
});
|
|
133
303
|
});
|
package/dist/src/main.js
CHANGED
|
@@ -20,6 +20,7 @@ import { getCompletionsFromManifest, hasAllManifests, printCompletionScriptFast
|
|
|
20
20
|
import { findPackageRoot, getCliManifestPath } from './package-paths.js';
|
|
21
21
|
import { PKG_VERSION } from './version.js';
|
|
22
22
|
import { EXIT_CODES } from './errors.js';
|
|
23
|
+
import { isSupportedNodeVersion, MIN_SUPPORTED_NODE_MAJOR } from './runtime-detect.js';
|
|
23
24
|
const __filename = fileURLToPath(import.meta.url);
|
|
24
25
|
const __dirname = path.dirname(__filename);
|
|
25
26
|
// Adapters are JS-first and live at <package-root>/clis/.
|
|
@@ -45,6 +46,15 @@ const USER_CLIS = path.join(os.homedir(), '.opencli', 'clis');
|
|
|
45
46
|
// ── Ultra-fast path: lightweight commands bypass full discovery ──────────
|
|
46
47
|
// These are high-frequency or trivial paths that must not pay the startup tax.
|
|
47
48
|
const argv = process.argv.slice(2);
|
|
49
|
+
if (typeof globalThis.Bun === 'undefined' && !isSupportedNodeVersion(process.version)) {
|
|
50
|
+
process.stderr.write([
|
|
51
|
+
`OpenCLI requires Node.js >= ${MIN_SUPPORTED_NODE_MAJOR}.0.0.`,
|
|
52
|
+
`Current runtime: ${process.version}`,
|
|
53
|
+
'Upgrade Node.js, then retry the same command.',
|
|
54
|
+
'',
|
|
55
|
+
].join('\n'));
|
|
56
|
+
process.exit(EXIT_CODES.CONFIG_ERROR);
|
|
57
|
+
}
|
|
48
58
|
// Fast path: --version (only when it's the top-level intent, not passed to a subcommand)
|
|
49
59
|
// e.g. `opencli --version` or `opencli -V`, but NOT `opencli gh --version`
|
|
50
60
|
if (argv[0] === '--version' || argv[0] === '-V') {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ObservationExportResult, ObservationExportStatus, ObservationTraceReceipt } from './events.js';
|
|
2
|
+
import { ObservationSession } from './session.js';
|
|
3
|
+
import { type TraceRetentionPolicyInput } from './retention.js';
|
|
4
|
+
export interface ExportObservationOptions {
|
|
5
|
+
baseDir?: string;
|
|
6
|
+
error?: unknown;
|
|
7
|
+
status?: ObservationExportStatus;
|
|
8
|
+
retentionPolicy?: TraceRetentionPolicyInput;
|
|
9
|
+
}
|
|
10
|
+
export declare function getTraceDirectory(contextId: string | undefined, traceId: string, baseDir?: string): string;
|
|
11
|
+
export declare function exportObservationSession(session: ObservationSession, opts?: ExportObservationOptions): ObservationExportResult;
|
|
12
|
+
export declare function buildTraceReceipt(result: Pick<ObservationExportResult, 'traceId' | 'dir' | 'summaryPath' | 'receiptPath'>, status: ObservationExportStatus, error?: unknown, opts?: {
|
|
13
|
+
createdAt?: string;
|
|
14
|
+
scope?: ObservationSession['scope'];
|
|
15
|
+
retentionPolicy?: TraceRetentionPolicyInput;
|
|
16
|
+
}): ObservationTraceReceipt;
|