@sovovs/bycli 2.0.0 → 2.1.0
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/clis/twitter/search.js +3 -3
- package/dist/src/browser/cdp.js +3 -0
- package/dist/src/browser/daemon-client.d.ts +2 -0
- package/dist/src/browser/daemon-client.js +1 -1
- package/dist/src/browser/extension-capabilities.d.ts +13 -0
- package/dist/src/browser/extension-capabilities.js +22 -0
- package/dist/src/browser/extension-capabilities.test.d.ts +1 -0
- package/dist/src/browser/extension-version-metadata.test.d.ts +1 -0
- package/dist/src/browser/page.d.ts +1 -0
- package/dist/src/browser/page.js +20 -1
- package/dist/src/build-manifest.js +4 -2
- package/dist/src/capabilityRouting.d.ts +3 -2
- package/dist/src/capabilityRouting.js +10 -2
- package/dist/src/cli.js +1 -1
- package/dist/src/commanderAdapter.js +5 -5
- package/dist/src/daemon.js +16 -0
- package/dist/src/discovery.d.ts +5 -0
- package/dist/src/discovery.js +12 -4
- package/dist/src/discovery.test.d.ts +1 -0
- package/dist/src/execution.d.ts +5 -0
- package/dist/src/execution.js +269 -50
- package/dist/src/help.js +8 -8
- package/dist/src/manifest-schema.d.ts +9 -0
- package/dist/src/manifest-schema.js +162 -0
- package/dist/src/manifest-schema.test.d.ts +1 -0
- package/dist/src/manifest-types.d.ts +1 -1
- package/dist/src/observation/redaction.js +10 -4
- package/dist/src/recorder/runner/verify-runner-main.d.ts +6 -5
- package/dist/src/recorder/runner/verify-runner-main.js +22 -5
- package/dist/src/registry-api.d.ts +1 -1
- package/dist/src/registry-api.types.test.d.ts +1 -0
- package/dist/src/registry-transaction.d.ts +42 -0
- package/dist/src/registry-transaction.js +194 -0
- package/dist/src/registry-transaction.test.d.ts +1 -0
- package/dist/src/registry.d.ts +58 -16
- package/dist/src/registry.js +131 -15
- package/dist/src/serialization.d.ts +1 -1
- package/dist/src/serialization.js +3 -3
- package/dist/src/types.d.ts +2 -0
- package/package.json +1 -1
- package/scripts/recorder.sh +0 -186
package/clis/twitter/search.js
CHANGED
|
@@ -268,10 +268,10 @@ cli({
|
|
|
268
268
|
args: [
|
|
269
269
|
{ name: 'query', type: 'string', required: true, positional: true, help: 'Search query. Raw X operators (e.g. "exact phrase", #tag, OR, lang:en, since:YYYY-MM-DD, from:, since:) are passed through unchanged.' },
|
|
270
270
|
{ name: 'filter', type: 'string', default: 'top', choices: ['top', 'live'], help: 'Legacy alias for --product. Kept for backwards compatibility; if --product is set it wins.' },
|
|
271
|
-
{ name: 'product', type: 'string', choices: PRODUCT_CHOICES, help: 'Which X search tab to read: top (default), live (Latest), photos, videos. Maps to the f= URL param.' },
|
|
271
|
+
{ name: 'product', type: 'string', choices: [...PRODUCT_CHOICES], help: 'Which X search tab to read: top (default), live (Latest), photos, videos. Maps to the f= URL param.' },
|
|
272
272
|
{ name: 'from', type: 'string', help: 'Restrict to tweets authored by <user>. Leading @ is stripped. Equivalent to appending `from:<user>` to the query.' },
|
|
273
|
-
{ name: 'has', type: 'string', choices: HAS_CHOICES, help: 'Restrict to tweets that have media|images|videos|links|replies. Maps to X\'s `filter:<has>` operator.' },
|
|
274
|
-
{ name: 'exclude', type: 'string', choices: EXCLUDE_CHOICES, help: 'Exclude tweets matching <type>: replies|retweets|media|links. Maps to X\'s `-filter:<x>` operator (retweets → -filter:nativeretweets).' },
|
|
273
|
+
{ name: 'has', type: 'string', choices: [...HAS_CHOICES], help: 'Restrict to tweets that have media|images|videos|links|replies. Maps to X\'s `filter:<has>` operator.' },
|
|
274
|
+
{ name: 'exclude', type: 'string', choices: [...EXCLUDE_CHOICES], help: 'Exclude tweets matching <type>: replies|retweets|media|links. Maps to X\'s `-filter:<x>` operator (retweets → -filter:nativeretweets).' },
|
|
275
275
|
{ name: 'limit', type: 'int', default: 15, help: 'Maximum number of tweets to return (default 15). Result count after server-side filtering.' },
|
|
276
276
|
{ name: 'top-by-engagement', type: 'int', default: 0, help: 'When set to N>0, re-rank the results by weighted engagement (likes×1 + retweets×3 + replies×2 + bookmarks×5 + log10(views+1)×0.5) and return the top N. Default 0 keeps X\'s native ordering.' },
|
|
277
277
|
],
|
package/dist/src/browser/cdp.js
CHANGED
|
@@ -258,6 +258,9 @@ class CDPPage extends BasePage {
|
|
|
258
258
|
? cookies.filter((cookie) => isCookie(cookie) && matchesCookieDomain(cookie.domain, domain))
|
|
259
259
|
: cookies;
|
|
260
260
|
}
|
|
261
|
+
async focusWindow() {
|
|
262
|
+
await this.bridge.send('Page.bringToFront');
|
|
263
|
+
}
|
|
261
264
|
async screenshot(options = {}) {
|
|
262
265
|
const fullPage = options.fullPage === true;
|
|
263
266
|
const overrideWidth = options.width && options.width > 0 ? Math.ceil(options.width) : undefined;
|
|
@@ -68,6 +68,7 @@ export interface DaemonStatus {
|
|
|
68
68
|
extensionConnected: boolean;
|
|
69
69
|
extensionVersion?: string;
|
|
70
70
|
extensionCompatRange?: string;
|
|
71
|
+
extensionCapabilities?: string[];
|
|
71
72
|
contextId?: string;
|
|
72
73
|
profileRequired?: boolean;
|
|
73
74
|
profileDisconnected?: boolean;
|
|
@@ -82,6 +83,7 @@ export interface BrowserProfileStatus {
|
|
|
82
83
|
extensionConnected: boolean;
|
|
83
84
|
extensionVersion?: string;
|
|
84
85
|
extensionCompatRange?: string;
|
|
86
|
+
extensionCapabilities?: string[];
|
|
85
87
|
pending: number;
|
|
86
88
|
lastSeenAt?: number;
|
|
87
89
|
}
|
|
@@ -109,7 +109,7 @@ async function sendCommandRaw(action, params) {
|
|
|
109
109
|
throw new BrowserCommandError(result.error ?? 'Browser command result is unknown', result.errorCode, result.errorHint);
|
|
110
110
|
}
|
|
111
111
|
const isDuplicateCommandId = res.status === 409
|
|
112
|
-
|
|
112
|
+
&& (result.error ?? '').includes('Duplicate command id');
|
|
113
113
|
if (isDuplicateCommandId && attempt < maxRetries) {
|
|
114
114
|
continue;
|
|
115
115
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const FOCUS_WINDOW_CAPABILITY = "focus-window-v1";
|
|
2
|
+
export declare const EXTENSION_CAPABILITY_MISSING_ERROR_CODE = "extension_capability_missing";
|
|
3
|
+
export declare const EXTENSION_CAPABILITY_MISSING_HTTP_STATUS = 412;
|
|
4
|
+
export declare function normalizeExtensionCapabilities(value: unknown): string[];
|
|
5
|
+
export declare function requiredExtensionCapability(command: {
|
|
6
|
+
action?: unknown;
|
|
7
|
+
op?: unknown;
|
|
8
|
+
}): string | undefined;
|
|
9
|
+
export declare function missingRequiredExtensionCapability(command: {
|
|
10
|
+
action?: unknown;
|
|
11
|
+
op?: unknown;
|
|
12
|
+
}, capabilities: readonly string[]): string | undefined;
|
|
13
|
+
export declare function extensionCapabilityHint(capability: string): string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const FOCUS_WINDOW_CAPABILITY = 'focus-window-v1';
|
|
2
|
+
export const EXTENSION_CAPABILITY_MISSING_ERROR_CODE = 'extension_capability_missing';
|
|
3
|
+
export const EXTENSION_CAPABILITY_MISSING_HTTP_STATUS = 412;
|
|
4
|
+
export function normalizeExtensionCapabilities(value) {
|
|
5
|
+
if (!Array.isArray(value))
|
|
6
|
+
return [];
|
|
7
|
+
return [...new Set(value.filter((entry) => typeof entry === 'string' && entry.length > 0))];
|
|
8
|
+
}
|
|
9
|
+
export function requiredExtensionCapability(command) {
|
|
10
|
+
return command.action === 'tabs' && command.op === 'focus'
|
|
11
|
+
? FOCUS_WINDOW_CAPABILITY
|
|
12
|
+
: undefined;
|
|
13
|
+
}
|
|
14
|
+
export function missingRequiredExtensionCapability(command, capabilities) {
|
|
15
|
+
const required = requiredExtensionCapability(command);
|
|
16
|
+
return required && !capabilities.includes(required) ? required : undefined;
|
|
17
|
+
}
|
|
18
|
+
export function extensionCapabilityHint(capability) {
|
|
19
|
+
return capability === FOCUS_WINDOW_CAPABILITY
|
|
20
|
+
? 'Update and reload the byCLI Browser Bridge extension, then retry the login flow.'
|
|
21
|
+
: 'Update and reload the byCLI Browser Bridge extension, then retry.';
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -44,6 +44,7 @@ export declare class Page extends BasePage {
|
|
|
44
44
|
domain?: string;
|
|
45
45
|
url?: string;
|
|
46
46
|
}): Promise<BrowserCookie[]>;
|
|
47
|
+
focusWindow(): Promise<void>;
|
|
47
48
|
/** Release the current browser session lease in the extension */
|
|
48
49
|
closeWindow(): Promise<void>;
|
|
49
50
|
tabs(): Promise<unknown[]>;
|
package/dist/src/browser/page.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* by the navigate action and pass it to all subsequent commands. This ensures
|
|
9
9
|
* page-scoped operations target the correct page without guessing.
|
|
10
10
|
*/
|
|
11
|
-
import { sendCommand, sendCommandFull } from './daemon-client.js';
|
|
11
|
+
import { fetchDaemonStatus, sendCommand, sendCommandFull } from './daemon-client.js';
|
|
12
|
+
import { extensionCapabilityHint, FOCUS_WINDOW_CAPABILITY } from './extension-capabilities.js';
|
|
12
13
|
import { buildEvaluateExpression } from './utils.js';
|
|
13
14
|
import { saveBase64ToFile } from '../utils.js';
|
|
14
15
|
import { generateStealthJs } from './stealth.js';
|
|
@@ -180,6 +181,24 @@ export class Page extends BasePage {
|
|
|
180
181
|
const result = await sendCommand('cookies', { ...this._sessionOpts(), ...opts });
|
|
181
182
|
return Array.isArray(result) ? result : [];
|
|
182
183
|
}
|
|
184
|
+
async focusWindow() {
|
|
185
|
+
const status = await fetchDaemonStatus(this.contextId ? { contextId: this.contextId } : undefined);
|
|
186
|
+
const selectedExtensionIsConnected = status?.extensionConnected === true
|
|
187
|
+
&& status.profileRequired !== true
|
|
188
|
+
&& status.profileDisconnected !== true;
|
|
189
|
+
const capabilities = status?.extensionCapabilities;
|
|
190
|
+
const capabilityListIsDefinitive = Array.isArray(capabilities);
|
|
191
|
+
if (selectedExtensionIsConnected
|
|
192
|
+
&& capabilityListIsDefinitive
|
|
193
|
+
&& !capabilities.includes(FOCUS_WINDOW_CAPABILITY)) {
|
|
194
|
+
throw new Error(`Connected Browser Bridge does not advertise ${FOCUS_WINDOW_CAPABILITY}. ${extensionCapabilityHint(FOCUS_WINDOW_CAPABILITY)}`);
|
|
195
|
+
}
|
|
196
|
+
await sendCommandFull('tabs', {
|
|
197
|
+
op: 'focus',
|
|
198
|
+
...this._cmdOpts(),
|
|
199
|
+
...this._sessionOpts(),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
183
202
|
/** Release the current browser session lease in the extension */
|
|
184
203
|
async closeWindow() {
|
|
185
204
|
try {
|
|
@@ -28,6 +28,7 @@ import { getErrorMessage } from './errors.js';
|
|
|
28
28
|
import { fullName, getRegistry } from './registry.js';
|
|
29
29
|
import { findPackageRoot, getCliManifestPath } from './package-paths.js';
|
|
30
30
|
import { isRecord } from './utils.js';
|
|
31
|
+
import { canonicalizeManifestArgSchema } from './manifest-schema.js';
|
|
31
32
|
const PACKAGE_ROOT = findPackageRoot(fileURLToPath(import.meta.url));
|
|
32
33
|
const CLIS_DIR = path.join(PACKAGE_ROOT, 'clis');
|
|
33
34
|
// Write manifest next to clis/ so both dev and installed runtime can find it.
|
|
@@ -58,7 +59,7 @@ function toManifestArgs(args) {
|
|
|
58
59
|
return args.map(arg => ({
|
|
59
60
|
name: arg.name,
|
|
60
61
|
type: arg.type ?? 'str',
|
|
61
|
-
default: arg.default,
|
|
62
|
+
...(Object.prototype.hasOwnProperty.call(arg, 'default') ? { default: arg.default } : {}),
|
|
62
63
|
required: !!arg.required,
|
|
63
64
|
valueRequired: !!arg.valueRequired || undefined,
|
|
64
65
|
positional: arg.positional || undefined,
|
|
@@ -85,6 +86,7 @@ function isCliCommandValue(value, site) {
|
|
|
85
86
|
&& Array.isArray(value.args);
|
|
86
87
|
}
|
|
87
88
|
function toManifestEntry(cmd, modulePath, sourceFile) {
|
|
89
|
+
canonicalizeManifestArgSchema(cmd.args, `Command ${fullName(cmd)}`);
|
|
88
90
|
return {
|
|
89
91
|
site: cmd.site,
|
|
90
92
|
name: cmd.name,
|
|
@@ -94,7 +96,7 @@ function toManifestEntry(cmd, modulePath, sourceFile) {
|
|
|
94
96
|
example: cmd.example,
|
|
95
97
|
domain: cmd.domain,
|
|
96
98
|
strategy: (cmd.strategy ?? 'public').toString().toLowerCase(),
|
|
97
|
-
browser: cmd.browser
|
|
99
|
+
browser: cmd.browser,
|
|
98
100
|
args: toManifestArgs(cmd.args),
|
|
99
101
|
columns: cmd.columns,
|
|
100
102
|
defaultFormat: cmd.defaultFormat,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CliCommand } from './registry.js';
|
|
1
|
+
import type { BrowserCliCommand, CliCommand, NonBrowserCliCommand } from './registry.js';
|
|
2
2
|
/**
|
|
3
3
|
* Pipeline steps that require a live browser session.
|
|
4
4
|
*
|
|
@@ -16,4 +16,5 @@ export declare const BROWSER_ONLY_STEPS: Set<string>;
|
|
|
16
16
|
export declare function _validateBrowserOnlyStepsAgainstRegistry(): {
|
|
17
17
|
extras: string[];
|
|
18
18
|
};
|
|
19
|
-
export declare function shouldUseBrowserSession(cmd:
|
|
19
|
+
export declare function shouldUseBrowserSession(cmd: BrowserCliCommand | NonBrowserCliCommand): boolean;
|
|
20
|
+
export declare function shouldUseBrowserSession(cmd: CliCommand, resolvedBrowser: boolean): boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CommandExecutionError } from './errors.js';
|
|
1
2
|
import { getRegisteredStepNames } from './pipeline/registry.js';
|
|
2
3
|
/**
|
|
3
4
|
* Pipeline steps that require a live browser session.
|
|
@@ -40,8 +41,15 @@ function pipelineNeedsBrowserSession(pipeline) {
|
|
|
40
41
|
return Object.keys(step).some((op) => BROWSER_ONLY_STEPS.has(op));
|
|
41
42
|
});
|
|
42
43
|
}
|
|
43
|
-
export function shouldUseBrowserSession(cmd) {
|
|
44
|
-
|
|
44
|
+
export function shouldUseBrowserSession(cmd, resolvedBrowser) {
|
|
45
|
+
let browserRequired = resolvedBrowser;
|
|
46
|
+
if (browserRequired === undefined) {
|
|
47
|
+
if (cmd.browser === 'conditional') {
|
|
48
|
+
throw new CommandExecutionError(`Conditional browser requirement for ${cmd.site}/${cmd.name} must be resolved before capability routing`);
|
|
49
|
+
}
|
|
50
|
+
browserRequired = cmd.browser;
|
|
51
|
+
}
|
|
52
|
+
if (!browserRequired)
|
|
45
53
|
return false;
|
|
46
54
|
if (cmd.func)
|
|
47
55
|
return true;
|
package/dist/src/cli.js
CHANGED
|
@@ -506,7 +506,7 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
|
|
|
506
506
|
description: c.description,
|
|
507
507
|
access: c.access,
|
|
508
508
|
strategy: strategyLabel(c),
|
|
509
|
-
browser:
|
|
509
|
+
browser: c.browser,
|
|
510
510
|
args: formatArgSummary(c.args),
|
|
511
511
|
}));
|
|
512
512
|
renderOutput(rows, {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { log } from './logger.js';
|
|
13
13
|
import yaml from 'js-yaml';
|
|
14
|
-
import { fullName, getRegistry } from './registry.js';
|
|
14
|
+
import { fullName, getRegistry, hasBrowserCapability } from './registry.js';
|
|
15
15
|
import { render as renderOutput } from './output.js';
|
|
16
16
|
import { executeCommand, prepareCommandArgs } from './execution.js';
|
|
17
17
|
import { commandHelpData, formatCommandHelpText, formatCommandListTerm, formatSiteCommandDescription, formatSiteHelpText, getRequestedHelpFormat, renderStructuredHelp, siteHelpData, } from './help.js';
|
|
@@ -48,7 +48,7 @@ export function registerCommandToProgram(siteCmd, cmd) {
|
|
|
48
48
|
.option('-f, --format <fmt>', 'Output format: table, plain, json, yaml, md, csv', 'table')
|
|
49
49
|
.option('--trace <mode>', 'Trace capture: off, on, retain-on-failure', 'off')
|
|
50
50
|
.option('-v, --verbose', 'Debug output', false);
|
|
51
|
-
if (cmd
|
|
51
|
+
if (hasBrowserCapability(cmd)) {
|
|
52
52
|
subCmd
|
|
53
53
|
.option('--window <mode>', 'Browser window mode: foreground or background')
|
|
54
54
|
.option('--site-session <mode>', 'Adapter site session lifecycle: ephemeral or persistent')
|
|
@@ -108,9 +108,9 @@ export function registerCommandToProgram(siteCmd, cmd) {
|
|
|
108
108
|
prepared: true,
|
|
109
109
|
...(typeof globals.profile === 'string' && globals.profile.trim() ? { profile: globals.profile.trim() } : {}),
|
|
110
110
|
...(typeof optionsRecord.trace === 'string' && optionsRecord.trace !== 'off' ? { trace: optionsRecord.trace } : {}),
|
|
111
|
-
...(cmd
|
|
112
|
-
...(cmd
|
|
113
|
-
...(cmd
|
|
111
|
+
...(hasBrowserCapability(cmd) && typeof optionsRecord.window === 'string' ? { windowMode: optionsRecord.window } : {}),
|
|
112
|
+
...(hasBrowserCapability(cmd) && typeof optionsRecord.siteSession === 'string' ? { siteSession: optionsRecord.siteSession } : {}),
|
|
113
|
+
...(hasBrowserCapability(cmd) && typeof optionsRecord.keepTab === 'string' ? { keepTab: optionsRecord.keepTab } : {}),
|
|
114
114
|
});
|
|
115
115
|
if (result === null || result === undefined) {
|
|
116
116
|
return;
|
package/dist/src/daemon.js
CHANGED
|
@@ -35,6 +35,7 @@ import { reapOrphanedVerifyRuns } from './recorder/runner/reap.js';
|
|
|
35
35
|
import { resolveTempPolicy, resolveRunnerConfig, resolveTempCapacity } from './recorder/runner/config.js';
|
|
36
36
|
import { defaultSessionKeyRegistry } from './recorder/runner/session-keys.js';
|
|
37
37
|
import { recordExtensionVersion } from './update-check.js';
|
|
38
|
+
import { EXTENSION_CAPABILITY_MISSING_ERROR_CODE, EXTENSION_CAPABILITY_MISSING_HTTP_STATUS, extensionCapabilityHint, missingRequiredExtensionCapability, normalizeExtensionCapabilities, } from './browser/extension-capabilities.js';
|
|
38
39
|
import { buildCommandDispatchFailure, buildExtensionDisconnectFailure, getResponseCorsHeaders, } from './daemon-utils.js';
|
|
39
40
|
const PORT = parseInt(process.env.BYCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
|
|
40
41
|
// The verify runner (M6b) spawns child processes that connect back to THIS daemon for a
|
|
@@ -123,6 +124,7 @@ function registerExtensionConnection(ws, rawContextId) {
|
|
|
123
124
|
ws,
|
|
124
125
|
extensionVersion: current?.ws === ws ? current.extensionVersion : null,
|
|
125
126
|
extensionCompatRange: current?.ws === ws ? current.extensionCompatRange : null,
|
|
127
|
+
extensionCapabilities: current?.ws === ws ? current.extensionCapabilities : [],
|
|
126
128
|
lastSeenAt: Date.now(),
|
|
127
129
|
};
|
|
128
130
|
extensionProfiles.set(contextId, connection);
|
|
@@ -357,6 +359,7 @@ async function handleRequest(req, res) {
|
|
|
357
359
|
extensionConnected: true,
|
|
358
360
|
extensionVersion: profile.extensionVersion ?? undefined,
|
|
359
361
|
extensionCompatRange: profile.extensionCompatRange ?? undefined,
|
|
362
|
+
extensionCapabilities: profile.extensionCapabilities,
|
|
360
363
|
pending: [...pending.values()].filter((entry) => entry.contextId === profile.contextId).length,
|
|
361
364
|
lastSeenAt: profile.lastSeenAt,
|
|
362
365
|
}));
|
|
@@ -368,6 +371,7 @@ async function handleRequest(req, res) {
|
|
|
368
371
|
extensionConnected: !!route.connection,
|
|
369
372
|
extensionVersion: route.connection?.extensionVersion ?? undefined,
|
|
370
373
|
extensionCompatRange: route.connection?.extensionCompatRange ?? undefined,
|
|
374
|
+
extensionCapabilities: route.connection?.extensionCapabilities,
|
|
371
375
|
contextId: route.connection?.contextId ?? requestedContextId,
|
|
372
376
|
profileRequired: route.errorCode === 'profile_required',
|
|
373
377
|
profileDisconnected: route.errorCode === 'profile_disconnected',
|
|
@@ -423,6 +427,17 @@ async function handleRequest(req, res) {
|
|
|
423
427
|
});
|
|
424
428
|
return;
|
|
425
429
|
}
|
|
430
|
+
const requiredCapability = missingRequiredExtensionCapability(body, route.connection.extensionCapabilities);
|
|
431
|
+
if (requiredCapability) {
|
|
432
|
+
jsonResponse(res, EXTENSION_CAPABILITY_MISSING_HTTP_STATUS, {
|
|
433
|
+
id: body.id,
|
|
434
|
+
ok: false,
|
|
435
|
+
errorCode: EXTENSION_CAPABILITY_MISSING_ERROR_CODE,
|
|
436
|
+
error: `Connected Browser Bridge does not advertise ${requiredCapability}.`,
|
|
437
|
+
errorHint: extensionCapabilityHint(requiredCapability),
|
|
438
|
+
});
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
426
441
|
const timeoutMs = typeof body.timeout === 'number' && body.timeout > 0
|
|
427
442
|
? body.timeout * 1000
|
|
428
443
|
: 120000;
|
|
@@ -529,6 +544,7 @@ wss.on('connection', (ws) => {
|
|
|
529
544
|
const connection = registerExtensionConnection(ws, msg.contextId);
|
|
530
545
|
connection.extensionVersion = typeof msg.version === 'string' ? msg.version : null;
|
|
531
546
|
connection.extensionCompatRange = typeof msg.compatRange === 'string' ? msg.compatRange : null;
|
|
547
|
+
connection.extensionCapabilities = normalizeExtensionCapabilities(msg.capabilities);
|
|
532
548
|
connection.lastSeenAt = Date.now();
|
|
533
549
|
if (connection.extensionVersion)
|
|
534
550
|
recordExtensionVersion(connection.extensionVersion);
|
package/dist/src/discovery.d.ts
CHANGED
|
@@ -37,6 +37,11 @@ export declare function ensureUserAdapters(): Promise<void>;
|
|
|
37
37
|
* Uses pre-compiled manifest when available for instant startup.
|
|
38
38
|
*/
|
|
39
39
|
export declare function discoverClis(...dirs: string[]): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Fast-path: register commands from pre-compiled manifest.
|
|
42
|
+
* TS modules are deferred — loaded lazily on first execution.
|
|
43
|
+
*/
|
|
44
|
+
export declare function loadFromManifest(manifestPath: string, clisDir: string): Promise<boolean>;
|
|
40
45
|
/**
|
|
41
46
|
* Discover and register plugins from ~/.bycli/plugins/.
|
|
42
47
|
* Each subdirectory is treated as a plugin (site = directory name).
|
package/dist/src/discovery.js
CHANGED
|
@@ -113,7 +113,7 @@ export async function discoverClis(...dirs) {
|
|
|
113
113
|
* Fast-path: register commands from pre-compiled manifest.
|
|
114
114
|
* TS modules are deferred — loaded lazily on first execution.
|
|
115
115
|
*/
|
|
116
|
-
async function loadFromManifest(manifestPath, clisDir) {
|
|
116
|
+
export async function loadFromManifest(manifestPath, clisDir) {
|
|
117
117
|
try {
|
|
118
118
|
const raw = await fs.promises.readFile(manifestPath, 'utf-8');
|
|
119
119
|
const manifest = JSON.parse(raw);
|
|
@@ -121,7 +121,7 @@ async function loadFromManifest(manifestPath, clisDir) {
|
|
|
121
121
|
if (!entry.modulePath)
|
|
122
122
|
continue;
|
|
123
123
|
const modulePath = path.resolve(clisDir, entry.modulePath);
|
|
124
|
-
const
|
|
124
|
+
const common = {
|
|
125
125
|
site: entry.site,
|
|
126
126
|
name: entry.name,
|
|
127
127
|
aliases: entry.aliases,
|
|
@@ -130,7 +130,6 @@ async function loadFromManifest(manifestPath, clisDir) {
|
|
|
130
130
|
example: entry.example,
|
|
131
131
|
domain: entry.domain,
|
|
132
132
|
strategy: parseStrategy(entry.strategy),
|
|
133
|
-
browser: entry.browser,
|
|
134
133
|
args: entry.args ?? [],
|
|
135
134
|
columns: entry.columns,
|
|
136
135
|
defaultFormat: entry.defaultFormat,
|
|
@@ -141,7 +140,16 @@ async function loadFromManifest(manifestPath, clisDir) {
|
|
|
141
140
|
_lazy: true,
|
|
142
141
|
_modulePath: modulePath,
|
|
143
142
|
};
|
|
144
|
-
|
|
143
|
+
const cmd = entry.browser === 'conditional'
|
|
144
|
+
? {
|
|
145
|
+
...common,
|
|
146
|
+
browser: 'conditional',
|
|
147
|
+
requiresBrowser: () => {
|
|
148
|
+
throw new Error(`Conditional manifest placeholder ${entry.site}/${entry.name} was not hydrated`);
|
|
149
|
+
},
|
|
150
|
+
_hydrateBeforeBrowserRouting: true,
|
|
151
|
+
}
|
|
152
|
+
: { ...common, browser: entry.browser };
|
|
145
153
|
registerCommand(cmd);
|
|
146
154
|
}
|
|
147
155
|
return true;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/src/execution.d.ts
CHANGED
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { type CliCommand, type Arg, type CommandArgs } from './registry.js';
|
|
13
13
|
import { type ObservationExportResult } from './observation/index.js';
|
|
14
|
+
/** Internal test-only reset. Never use this to recover a production process. */
|
|
15
|
+
export declare function _resetLazyModuleStateForTests(): void;
|
|
16
|
+
export declare function _getLazyModuleFingerprintReadCountForTests(): number;
|
|
14
17
|
export declare function coerceAndValidateArgs(cmdArgs: Arg[], kwargs: CommandArgs): CommandArgs;
|
|
15
18
|
export declare function executeCommand(cmd: CliCommand, rawKwargs: CommandArgs, debug?: boolean, opts?: {
|
|
16
19
|
prepared?: boolean;
|
|
@@ -22,3 +25,5 @@ export declare function executeCommand(cmd: CliCommand, rawKwargs: CommandArgs,
|
|
|
22
25
|
onTraceExport?: (trace: ObservationExportResult) => void;
|
|
23
26
|
}): Promise<unknown>;
|
|
24
27
|
export declare function prepareCommandArgs(cmd: CliCommand, rawKwargs: CommandArgs): CommandArgs;
|
|
28
|
+
/** Prepare adapter arguments using the execution boundary's public error contract. */
|
|
29
|
+
export declare function prepareCommandArgsOrThrowArgumentError(cmd: CliCommand, rawKwargs: CommandArgs): CommandArgs;
|