@sovovs/bycli 2.1.39 → 2.1.41
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/cli-manifest.json +56 -3
- package/clis/ima/knowledge-list.js +48 -0
- package/clis/ima/knowledge.js +23 -0
- package/clis/ima/native-api.js +67 -10
- package/clis/ima/native-client.js +23 -7
- package/clis/ima/utils.js +1 -0
- package/clis/weixin/_wechat/article-artifact.js +55 -0
- package/clis/weixin/_wechat/article-identity.js +27 -0
- package/clis/weixin/_wechat/publish-analysis.js +8 -4
- package/clis/weixin/_wechat/publish-download.js +3 -1
- package/clis/weixin/download-publish-data.js +20 -0
- package/clis/weixin/download.js +15 -2
- package/dist/src/adapter-coordination.d.ts +26 -0
- package/dist/src/adapter-coordination.js +183 -0
- package/dist/src/adapter-coordination.test.d.ts +1 -0
- package/dist/src/adapter-execution-context.d.ts +6 -0
- package/dist/src/adapter-execution-context.js +8 -0
- package/dist/src/adapter-scheduler.d.ts +86 -0
- package/dist/src/adapter-scheduler.js +349 -0
- package/dist/src/adapter-scheduler.test.d.ts +1 -0
- package/dist/src/browser/daemon-client.d.ts +11 -0
- package/dist/src/browser/daemon-client.js +53 -1
- package/dist/src/browser/extension-capabilities.d.ts +1 -0
- package/dist/src/browser/extension-capabilities.js +18 -5
- package/dist/src/browser/page.d.ts +2 -1
- package/dist/src/browser/page.js +3 -0
- package/dist/src/build-manifest.js +1 -0
- package/dist/src/cli-argv-preprocess.d.ts +3 -0
- package/dist/src/cli-argv-preprocess.js +4 -0
- package/dist/src/commanderAdapter.js +11 -0
- package/dist/src/daemon.js +118 -0
- package/dist/src/discovery.js +1 -0
- package/dist/src/download/article-download.d.ts +2 -0
- package/dist/src/download/article-download.js +30 -4
- package/dist/src/errors.d.ts +3 -0
- package/dist/src/errors.js +5 -0
- package/dist/src/execution.d.ts +2 -0
- package/dist/src/execution.js +168 -105
- package/dist/src/help.d.ts +1 -0
- package/dist/src/help.js +40 -0
- package/dist/src/manifest-types.d.ts +4 -0
- package/dist/src/registry.d.ts +6 -0
- package/dist/src/registry.js +23 -0
- package/dist/src/serialization.d.ts +1 -0
- package/dist/src/serialization.js +1 -0
- package/dist/src/types.d.ts +2 -0
- package/package.json +3 -2
package/dist/src/execution.js
CHANGED
|
@@ -16,10 +16,11 @@ import * as fs from 'node:fs';
|
|
|
16
16
|
import * as path from 'node:path';
|
|
17
17
|
import { getUserClisDir } from './config-paths.js';
|
|
18
18
|
import { executePipeline } from './pipeline/index.js';
|
|
19
|
-
import { adapterLoadError, ArgumentError, CliError, CommandExecutionError, attachTraceReceipt, getErrorMessage } from './errors.js';
|
|
19
|
+
import { AdapterCoordinationError, adapterLoadError, ArgumentError, CliError, CommandExecutionError, TimeoutError, attachTraceReceipt, getErrorMessage } from './errors.js';
|
|
20
20
|
import { shouldUseBrowserSession } from './capabilityRouting.js';
|
|
21
21
|
import { getBrowserFactory, browserSession, runWithTimeout, DEFAULT_BROWSER_COMMAND_TIMEOUT } from './runtime.js';
|
|
22
22
|
import { resolveProfileContextId } from './browser/profile.js';
|
|
23
|
+
import { resolveAdapterLeaseContextId } from './browser/daemon-client.js';
|
|
23
24
|
import { emitHook } from './hooks.js';
|
|
24
25
|
import { log } from './logger.js';
|
|
25
26
|
import { isElectronApp } from './electron-apps.js';
|
|
@@ -27,6 +28,7 @@ import { probeCDP, resolveElectronEndpoint } from './launcher.js';
|
|
|
27
28
|
import { ObservationSession, exportObservationSession } from './observation/index.js';
|
|
28
29
|
import { resolveAdapterSourcePath } from './adapter-source.js';
|
|
29
30
|
import { canonicalizeManifestArgSchema, ManifestSchemaError } from './manifest-schema.js';
|
|
31
|
+
import { settleAdapterOperationAfterTimeout, withAdapterCommandLease } from './adapter-coordination.js';
|
|
30
32
|
import { capturedRegistryValues, closeRegistryTransaction, createRegistryTransaction, finalizeRegistryTransaction, resetRegistryTransactionStateForTests, rollbackRegistryTransaction, runRegistryTransaction, transactionGroupsForKey, } from './registry-transaction.js';
|
|
31
33
|
const _loadedModules = new Map();
|
|
32
34
|
/** Independent cache-busting generation; retained when an import promise is discarded. */
|
|
@@ -402,6 +404,8 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
402
404
|
let result;
|
|
403
405
|
try {
|
|
404
406
|
const resolvedBrowser = resolveBrowserRequirement(cmd, kwargs);
|
|
407
|
+
const adapterSession = normalizeAdapterSession(cmd, resolvedBrowser, opts.adapterSession);
|
|
408
|
+
const adapterQueueTimeoutSeconds = normalizeAdapterQueueTimeout(opts.adapterQueueTimeout, adapterSession);
|
|
405
409
|
const userTimeoutSec = readUserTimeoutSeconds(cmd, kwargs);
|
|
406
410
|
if (shouldUseBrowserSession(cmd, resolvedBrowser)) {
|
|
407
411
|
const electron = isElectronApp(cmd.site);
|
|
@@ -424,126 +428,154 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
424
428
|
const contextId = resolveProfileContextId(opts.profile);
|
|
425
429
|
const internal = cmd;
|
|
426
430
|
const siteSession = resolveSiteSession(cmd, opts.siteSession);
|
|
427
|
-
|
|
431
|
+
assertAdapterSessionLifecycle(siteSession, adapterSession);
|
|
432
|
+
const session = resolveAdapterBrowserSession(cmd, siteSession, adapterSession);
|
|
428
433
|
const keepTab = resolveKeepTab(siteSession, opts.keepTab);
|
|
429
434
|
const windowMode = resolveBrowserWindowMode('background', opts.windowMode);
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
if (preNavUrl && await shouldRunPreNav(cmd, page, siteSession, preNavUrl)) {
|
|
455
|
-
observation?.record({
|
|
456
|
-
stream: 'action',
|
|
457
|
-
name: 'pre_navigate',
|
|
458
|
-
phase: 'start',
|
|
459
|
-
data: { url: preNavUrl },
|
|
460
|
-
});
|
|
461
|
-
// Navigate directly — the extension's handleNavigate already has a fast-path
|
|
462
|
-
// that skips navigation if the tab is already at the target URL.
|
|
463
|
-
// This avoids an extra exec round-trip (getCurrentUrl) on first command and
|
|
464
|
-
// lets the extension create the automation window with the target URL directly
|
|
465
|
-
// instead of about:blank.
|
|
466
|
-
try {
|
|
467
|
-
await page.goto(preNavUrl);
|
|
468
|
-
observation?.record({
|
|
435
|
+
const executeInBrowser = () => browserSession(BrowserFactory, async (page) => {
|
|
436
|
+
// BrowserBridge.connect() has started/probed the daemon by this point. Resolve
|
|
437
|
+
// the daemon's canonical profile before acquiring a profile-scoped lease.
|
|
438
|
+
const leaseContextId = adapterSession
|
|
439
|
+
? await resolveAdapterLeaseContextId(contextId)
|
|
440
|
+
: undefined;
|
|
441
|
+
if (leaseContextId)
|
|
442
|
+
page.setContextId?.(leaseContextId);
|
|
443
|
+
const executeOnPage = async () => {
|
|
444
|
+
const observation = traceMode === 'off'
|
|
445
|
+
? null
|
|
446
|
+
: new ObservationSession({
|
|
447
|
+
scope: {
|
|
448
|
+
contextId,
|
|
449
|
+
session,
|
|
450
|
+
target: page.getActivePage?.(),
|
|
451
|
+
site: cmd.site,
|
|
452
|
+
command: fullName(cmd),
|
|
453
|
+
adapterSourcePath: resolveAdapterSourcePath(internal)
|
|
454
|
+
?? resolveAdapterSourcePath(initialTraceCommand),
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
if (observation) {
|
|
458
|
+
observation.record({
|
|
469
459
|
stream: 'action',
|
|
470
|
-
name: '
|
|
471
|
-
phase: '
|
|
472
|
-
data: {
|
|
460
|
+
name: 'command',
|
|
461
|
+
phase: 'start',
|
|
462
|
+
data: { args: kwargs },
|
|
473
463
|
});
|
|
464
|
+
await page.startNetworkCapture?.().catch(() => false);
|
|
474
465
|
}
|
|
475
|
-
|
|
466
|
+
const preNavUrl = resolvePreNav(cmd);
|
|
467
|
+
if (preNavUrl && await shouldRunPreNav(cmd, page, siteSession, preNavUrl)) {
|
|
476
468
|
observation?.record({
|
|
477
469
|
stream: 'action',
|
|
478
470
|
name: 'pre_navigate',
|
|
479
|
-
phase: '
|
|
480
|
-
data: { url: preNavUrl
|
|
471
|
+
phase: 'start',
|
|
472
|
+
data: { url: preNavUrl },
|
|
481
473
|
});
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
474
|
+
// Navigate directly — the extension's handleNavigate already has a fast-path
|
|
475
|
+
// that skips navigation if the tab is already at the target URL.
|
|
476
|
+
// This avoids an extra exec round-trip (getCurrentUrl) on first command and
|
|
477
|
+
// lets the extension create the automation window with the target URL directly
|
|
478
|
+
// instead of about:blank.
|
|
479
|
+
try {
|
|
480
|
+
await page.goto(preNavUrl);
|
|
481
|
+
observation?.record({
|
|
482
|
+
stream: 'action',
|
|
483
|
+
name: 'pre_navigate',
|
|
484
|
+
phase: 'end',
|
|
485
|
+
data: { url: preNavUrl },
|
|
490
486
|
});
|
|
491
|
-
await collectObservationEvidence(observation, page).catch(() => { });
|
|
492
|
-
exportTraceArtifact(observation, 'failure', wrapped, opts.onTraceExport);
|
|
493
487
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
488
|
+
catch (err) {
|
|
489
|
+
observation?.record({
|
|
490
|
+
stream: 'action',
|
|
491
|
+
name: 'pre_navigate',
|
|
492
|
+
phase: 'error',
|
|
493
|
+
data: { url: preNavUrl, error: err instanceof Error ? err.message : String(err) },
|
|
494
|
+
});
|
|
495
|
+
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.');
|
|
496
|
+
if (observation && (traceMode === 'on' || traceMode === 'retain-on-failure')) {
|
|
497
|
+
observation.record({
|
|
498
|
+
stream: 'error',
|
|
499
|
+
message: wrapped.message,
|
|
500
|
+
stack: wrapped.stack,
|
|
501
|
+
code: wrapped.code,
|
|
502
|
+
hint: wrapped.hint,
|
|
503
|
+
});
|
|
504
|
+
await collectObservationEvidence(observation, page).catch(() => { });
|
|
505
|
+
exportTraceArtifact(observation, 'failure', wrapped, opts.onTraceExport);
|
|
506
|
+
}
|
|
507
|
+
throw wrapped;
|
|
508
|
+
}
|
|
513
509
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
510
|
+
try {
|
|
511
|
+
const browserTimeout = userTimeoutSec !== null
|
|
512
|
+
? userTimeoutSec + RUNTIME_TIMEOUT_PADDING_SECONDS
|
|
513
|
+
: DEFAULT_BROWSER_COMMAND_TIMEOUT;
|
|
514
|
+
const commandOperation = runCommand(cmd, page, kwargs, debug);
|
|
515
|
+
const result = adapterSession
|
|
516
|
+
? await settleAdapterOperationAfterTimeout(commandOperation, browserTimeout * 1_000, new TimeoutError(fullName(cmd), browserTimeout), async () => { await page.closeWindow?.().catch(() => { }); })
|
|
517
|
+
: await runWithTimeout(commandOperation, {
|
|
518
|
+
timeout: browserTimeout,
|
|
519
|
+
label: fullName(cmd),
|
|
520
|
+
});
|
|
521
|
+
observation?.record({
|
|
524
522
|
stream: 'action',
|
|
525
523
|
name: 'command',
|
|
526
|
-
phase: '
|
|
527
|
-
data: { error: err instanceof Error ? err.message : String(err) },
|
|
528
|
-
});
|
|
529
|
-
observation.record({
|
|
530
|
-
stream: 'error',
|
|
531
|
-
message: err instanceof Error ? err.message : String(err),
|
|
532
|
-
stack: err instanceof Error ? err.stack : undefined,
|
|
524
|
+
phase: 'end',
|
|
533
525
|
});
|
|
534
|
-
if (
|
|
526
|
+
if (observation && traceMode === 'on') {
|
|
535
527
|
await collectObservationEvidence(observation, page).catch(() => { });
|
|
536
|
-
exportTraceArtifact(observation, '
|
|
528
|
+
exportTraceArtifact(observation, 'success', undefined, opts.onTraceExport);
|
|
537
529
|
}
|
|
530
|
+
// Adapter commands are one-shot — release the current tab lease immediately
|
|
531
|
+
// instead of waiting for the 30s idle timeout. The automation container
|
|
532
|
+
// window stays open for reuse.
|
|
533
|
+
if (!keepTab)
|
|
534
|
+
await page.closeWindow?.().catch(() => { });
|
|
535
|
+
return result;
|
|
538
536
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
537
|
+
catch (err) {
|
|
538
|
+
if (observation) {
|
|
539
|
+
observation.record({
|
|
540
|
+
stream: 'action',
|
|
541
|
+
name: 'command',
|
|
542
|
+
phase: 'error',
|
|
543
|
+
data: { error: err instanceof Error ? err.message : String(err) },
|
|
544
|
+
});
|
|
545
|
+
observation.record({
|
|
546
|
+
stream: 'error',
|
|
547
|
+
message: err instanceof Error ? err.message : String(err),
|
|
548
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
549
|
+
});
|
|
550
|
+
if (traceMode === 'on' || traceMode === 'retain-on-failure') {
|
|
551
|
+
await collectObservationEvidence(observation, page).catch(() => { });
|
|
552
|
+
exportTraceArtifact(observation, 'failure', err, opts.onTraceExport);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
// Release the tab lease on failure too — without this, the lease lingers
|
|
556
|
+
// until the extension's idle timer fires (unreliable on Windows where
|
|
557
|
+
// MV3 service workers may be suspended before setTimeout triggers).
|
|
558
|
+
if (!keepTab)
|
|
559
|
+
await page.closeWindow?.().catch(() => { });
|
|
560
|
+
throw err;
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
if (!adapterSession)
|
|
564
|
+
return executeOnPage();
|
|
565
|
+
return withAdapterCommandLease({
|
|
566
|
+
requestId: crypto.randomUUID(),
|
|
567
|
+
contextId: leaseContextId,
|
|
568
|
+
surface: 'adapter',
|
|
569
|
+
site: cmd.site,
|
|
570
|
+
adapterSession,
|
|
571
|
+
sessionKey: session,
|
|
572
|
+
queueTimeoutMs: (adapterQueueTimeoutSeconds ?? 300) * 1_000,
|
|
573
|
+
maxParallel: cmd.adapterConcurrency?.maxParallel ?? 1,
|
|
574
|
+
}, executeOnPage, {
|
|
575
|
+
onLeaseLost: async () => { await page.closeWindow?.().catch(() => { }); },
|
|
576
|
+
});
|
|
546
577
|
}, { session, cdpEndpoint, contextId, windowMode, surface: 'adapter', siteSession });
|
|
578
|
+
result = await executeInBrowser();
|
|
547
579
|
}
|
|
548
580
|
else {
|
|
549
581
|
// Non-browser commands: enforce a timeout only when the command exposes
|
|
@@ -674,9 +706,40 @@ function normalizeSiteSession(raw) {
|
|
|
674
706
|
function resolveSiteSession(cmd, rawOption) {
|
|
675
707
|
return normalizeSiteSession(rawOption) ?? cmd.siteSession ?? 'ephemeral';
|
|
676
708
|
}
|
|
677
|
-
function
|
|
678
|
-
if (
|
|
679
|
-
return
|
|
709
|
+
function normalizeAdapterSession(cmd, resolvedBrowser, rawOption) {
|
|
710
|
+
if (rawOption === undefined || rawOption === null || rawOption === '')
|
|
711
|
+
return undefined;
|
|
712
|
+
if (!resolvedBrowser || cmd.adapterConcurrency?.isolatedTabs !== true) {
|
|
713
|
+
throw new AdapterCoordinationError('ADAPTER_SESSION_NOT_SUPPORTED', `${fullName(cmd)} does not support named Adapter sessions`);
|
|
714
|
+
}
|
|
715
|
+
const value = String(rawOption);
|
|
716
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(value)) {
|
|
717
|
+
throw new AdapterCoordinationError('INVALID_ADAPTER_SESSION', '--adapter-session must match [A-Za-z0-9][A-Za-z0-9_-]{0,63}');
|
|
718
|
+
}
|
|
719
|
+
return value;
|
|
720
|
+
}
|
|
721
|
+
function normalizeAdapterQueueTimeout(rawOption, adapterSession) {
|
|
722
|
+
if (rawOption === undefined || rawOption === null || rawOption === '') {
|
|
723
|
+
return adapterSession ? 300 : undefined;
|
|
724
|
+
}
|
|
725
|
+
if (!adapterSession) {
|
|
726
|
+
throw new AdapterCoordinationError('INVALID_ADAPTER_QUEUE_TIMEOUT', '--adapter-queue-timeout requires --adapter-session');
|
|
727
|
+
}
|
|
728
|
+
const value = typeof rawOption === 'string' && /^\d+$/.test(rawOption) ? Number(rawOption) : NaN;
|
|
729
|
+
if (!Number.isInteger(value) || value < 1 || value > 3600) {
|
|
730
|
+
throw new AdapterCoordinationError('INVALID_ADAPTER_QUEUE_TIMEOUT', '--adapter-queue-timeout must be an integer between 1 and 3600 seconds');
|
|
731
|
+
}
|
|
732
|
+
return value;
|
|
733
|
+
}
|
|
734
|
+
function assertAdapterSessionLifecycle(siteSession, adapterSession) {
|
|
735
|
+
if (adapterSession && siteSession !== 'persistent') {
|
|
736
|
+
throw new AdapterCoordinationError('ADAPTER_SESSION_REQUIRES_PERSISTENT', '--adapter-session requires --site-session persistent');
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
function resolveAdapterBrowserSession(cmd, siteSession, adapterSession) {
|
|
740
|
+
if (siteSession === 'persistent') {
|
|
741
|
+
return adapterSession ? `site:${cmd.site}:${adapterSession}` : `site:${cmd.site}`;
|
|
742
|
+
}
|
|
680
743
|
return `site:${cmd.site}:${crypto.randomUUID()}`;
|
|
681
744
|
}
|
|
682
745
|
function normalizeBooleanOption(name, raw) {
|
package/dist/src/help.d.ts
CHANGED
|
@@ -82,6 +82,7 @@ export declare function siteHelpData(site: string, commands: readonly CliCommand
|
|
|
82
82
|
export declare function commandHelpData(cmd: CliCommand): Record<string, unknown>;
|
|
83
83
|
export declare function formatCommonOptionsHelpText(): string;
|
|
84
84
|
export declare function formatBrowserCommonOptionsHelpText(): string;
|
|
85
|
+
export declare function formatAdapterSessionOptionsHelpText(): string;
|
|
85
86
|
export declare function formatSiteHelpText(site: string, commands: readonly CliCommand[]): string;
|
|
86
87
|
export declare function formatCommandHelpText(cmd: CliCommand): string;
|
|
87
88
|
export declare function installStructuredHelp(command: Command, data: () => unknown, textSuffix?: string | (() => string)): void;
|
package/dist/src/help.js
CHANGED
|
@@ -48,6 +48,19 @@ const BROWSER_COMMON_OPTIONS = [
|
|
|
48
48
|
choices: ['true', 'false'],
|
|
49
49
|
},
|
|
50
50
|
];
|
|
51
|
+
const ADAPTER_SESSION_OPTIONS = [
|
|
52
|
+
{
|
|
53
|
+
flags: '--adapter-session <name>',
|
|
54
|
+
name: 'adapter-session',
|
|
55
|
+
help: 'Named persistent Adapter tab session',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
flags: '--adapter-queue-timeout <seconds>',
|
|
59
|
+
name: 'adapter-queue-timeout',
|
|
60
|
+
help: 'Seconds to wait for an Adapter command lease',
|
|
61
|
+
default: 300,
|
|
62
|
+
},
|
|
63
|
+
];
|
|
51
64
|
function normalizeStructuredHelpFormat(value) {
|
|
52
65
|
const normalized = value?.toLowerCase();
|
|
53
66
|
if (normalized === 'yaml' || normalized === 'yml')
|
|
@@ -369,6 +382,12 @@ function compactCommand(cmd) {
|
|
|
369
382
|
positionals: positionals(cmd).map(compactArg),
|
|
370
383
|
command_options: commandOptions(cmd).map(compactArg),
|
|
371
384
|
...(hasBrowserCapability(cmd) ? { browser_common_options: BROWSER_COMMON_OPTIONS.map(compactCommonOption) } : {}),
|
|
385
|
+
...(cmd.adapterConcurrency?.isolatedTabs === true
|
|
386
|
+
? {
|
|
387
|
+
adapterConcurrency: cmd.adapterConcurrency,
|
|
388
|
+
adapter_common_options: ADAPTER_SESSION_OPTIONS.map(compactCommonOption),
|
|
389
|
+
}
|
|
390
|
+
: {}),
|
|
372
391
|
example: formatCommandExample(cmd),
|
|
373
392
|
...(cmd.siteSession ? { siteSession: cmd.siteSession } : {}),
|
|
374
393
|
...(cmd.defaultFormat ? { defaultFormat: cmd.defaultFormat } : {}),
|
|
@@ -417,6 +436,9 @@ export function siteHelpData(site, commands) {
|
|
|
417
436
|
commands: unique.map(cmd => compactCommand(cmd)),
|
|
418
437
|
common_options: COMMON_OPTIONS.map(compactCommonOption),
|
|
419
438
|
...(unique.some(hasBrowserCapability) ? { browser_common_options: BROWSER_COMMON_OPTIONS.map(compactCommonOption) } : {}),
|
|
439
|
+
...(unique.some(cmd => cmd.adapterConcurrency?.isolatedTabs === true)
|
|
440
|
+
? { adapter_common_options: ADAPTER_SESSION_OPTIONS.map(compactCommonOption) }
|
|
441
|
+
: {}),
|
|
420
442
|
next: [
|
|
421
443
|
`bycli ${site} <command> --help -f yaml`,
|
|
422
444
|
`bycli ${site} <command> -f yaml`,
|
|
@@ -429,6 +451,12 @@ export function commandHelpData(cmd) {
|
|
|
429
451
|
...compactCommand(cmd),
|
|
430
452
|
common_options: COMMON_OPTIONS.map(compactCommonOption),
|
|
431
453
|
...(hasBrowserCapability(cmd) ? { browser_common_options: BROWSER_COMMON_OPTIONS.map(compactCommonOption) } : {}),
|
|
454
|
+
...(cmd.adapterConcurrency?.isolatedTabs === true
|
|
455
|
+
? {
|
|
456
|
+
adapterConcurrency: cmd.adapterConcurrency,
|
|
457
|
+
adapter_common_options: ADAPTER_SESSION_OPTIONS.map(compactCommonOption),
|
|
458
|
+
}
|
|
459
|
+
: {}),
|
|
432
460
|
output_formats: ['table', 'plain', 'yaml', 'json', 'md', 'csv'],
|
|
433
461
|
};
|
|
434
462
|
}
|
|
@@ -468,6 +496,15 @@ export function formatBrowserCommonOptionsHelpText() {
|
|
|
468
496
|
});
|
|
469
497
|
return ['Browser common options:', ...formatRows(rows)].join('\n');
|
|
470
498
|
}
|
|
499
|
+
export function formatAdapterSessionOptionsHelpText() {
|
|
500
|
+
const rows = ADAPTER_SESSION_OPTIONS.map(option => {
|
|
501
|
+
const details = [option.help];
|
|
502
|
+
if ('default' in option)
|
|
503
|
+
details.push(`default: ${option.default}`);
|
|
504
|
+
return [option.flags, details.join(' ')];
|
|
505
|
+
});
|
|
506
|
+
return ['Adapter session options:', ...formatRows(rows)].join('\n');
|
|
507
|
+
}
|
|
471
508
|
export function formatSiteHelpText(site, commands) {
|
|
472
509
|
const unique = [...new Map(commands.map(cmd => [fullName(cmd), cmd])).values()]
|
|
473
510
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -481,6 +518,7 @@ export function formatSiteHelpText(site, commands) {
|
|
|
481
518
|
'',
|
|
482
519
|
formatCommonOptionsHelpText(),
|
|
483
520
|
...(unique.some(hasBrowserCapability) ? ['', formatBrowserCommonOptionsHelpText()] : []),
|
|
521
|
+
...(unique.some(cmd => cmd.adapterConcurrency?.isolatedTabs === true) ? ['', formatAdapterSessionOptionsHelpText()] : []),
|
|
484
522
|
'',
|
|
485
523
|
`Agent tip: use 'bycli ${site} --help -f yaml' to get all command args/options in one structured response.`,
|
|
486
524
|
'',
|
|
@@ -511,6 +549,8 @@ export function formatCommandHelpText(cmd) {
|
|
|
511
549
|
lines.push(formatCommonOptionsHelpText(), '');
|
|
512
550
|
if (hasBrowserCapability(cmd))
|
|
513
551
|
lines.push(formatBrowserCommonOptionsHelpText(), '');
|
|
552
|
+
if (cmd.adapterConcurrency?.isolatedTabs === true)
|
|
553
|
+
lines.push(formatAdapterSessionOptionsHelpText(), '');
|
|
514
554
|
const meta = [];
|
|
515
555
|
meta.push(`Access: ${cmd.access}`);
|
|
516
556
|
meta.push(`Browser: ${browserRequirementLabel(cmd)}`);
|
|
@@ -38,4 +38,8 @@ export interface ManifestEntry {
|
|
|
38
38
|
navigateBefore?: boolean | string;
|
|
39
39
|
/** Site session lifecycle defaults — see CliCommand.siteSession */
|
|
40
40
|
siteSession?: 'ephemeral' | 'persistent';
|
|
41
|
+
adapterConcurrency?: {
|
|
42
|
+
isolatedTabs: true;
|
|
43
|
+
maxParallel: number;
|
|
44
|
+
};
|
|
41
45
|
}
|
package/dist/src/registry.d.ts
CHANGED
|
@@ -28,6 +28,10 @@ export type ConditionalBrowserCommandFunc = (page: IPage | null, kwargs: Command
|
|
|
28
28
|
export type NonBrowserCommandFunc = (kwargs: CommandArgs, debug?: boolean) => Promise<unknown>;
|
|
29
29
|
export type CommandAccess = 'read' | 'write';
|
|
30
30
|
export type SiteSessionMode = 'ephemeral' | 'persistent';
|
|
31
|
+
export interface AdapterConcurrency {
|
|
32
|
+
isolatedTabs: true;
|
|
33
|
+
maxParallel: number;
|
|
34
|
+
}
|
|
31
35
|
/**
|
|
32
36
|
* 所有已注册 adapter command 的共享元数据和运行选项。
|
|
33
37
|
*
|
|
@@ -82,6 +86,8 @@ interface BaseCliCommand {
|
|
|
82
86
|
navigateBefore?: boolean | string;
|
|
83
87
|
/** adapter 浏览器站点 session 生命周期:临时 session 或持久 session。 */
|
|
84
88
|
siteSession?: SiteSessionMode;
|
|
89
|
+
/** Opt-in authorization for isolated persistent Adapter tabs. */
|
|
90
|
+
adapterConcurrency?: AdapterConcurrency;
|
|
85
91
|
/** 用户没有传 `-f/--format` 时使用的默认输出格式。 */
|
|
86
92
|
defaultFormat?: 'table' | 'plain' | 'json' | 'yaml' | 'yml' | 'md' | 'markdown' | 'csv';
|
|
87
93
|
}
|
package/dist/src/registry.js
CHANGED
|
@@ -95,6 +95,7 @@ function rawCommandBase(opts) {
|
|
|
95
95
|
validateArgs: opts.validateArgs,
|
|
96
96
|
navigateBefore: opts.navigateBefore,
|
|
97
97
|
siteSession: opts.siteSession,
|
|
98
|
+
adapterConcurrency: opts.adapterConcurrency,
|
|
98
99
|
defaultFormat: opts.defaultFormat,
|
|
99
100
|
};
|
|
100
101
|
}
|
|
@@ -190,6 +191,27 @@ function assertSiteSession(cmd) {
|
|
|
190
191
|
throw new Error(`Command ${key} siteSession must be one of: ephemeral, persistent`);
|
|
191
192
|
}
|
|
192
193
|
}
|
|
194
|
+
function assertAdapterConcurrency(cmd) {
|
|
195
|
+
if (cmd.adapterConcurrency === undefined)
|
|
196
|
+
return;
|
|
197
|
+
const key = `${cmd.site}/${cmd.name}`;
|
|
198
|
+
if (!cmd.adapterConcurrency || typeof cmd.adapterConcurrency !== 'object') {
|
|
199
|
+
throw new Error(`Command ${key} adapterConcurrency must be an object`);
|
|
200
|
+
}
|
|
201
|
+
const metadata = cmd.adapterConcurrency;
|
|
202
|
+
if (metadata.isolatedTabs !== true) {
|
|
203
|
+
throw new Error(`Command ${key} adapterConcurrency.isolatedTabs must be true`);
|
|
204
|
+
}
|
|
205
|
+
if (!Number.isInteger(metadata.maxParallel)
|
|
206
|
+
|| metadata.maxParallel < 1
|
|
207
|
+
|| metadata.maxParallel > 3) {
|
|
208
|
+
throw new Error(`Command ${key} adapterConcurrency.maxParallel must be an integer between 1 and 3`);
|
|
209
|
+
}
|
|
210
|
+
if (cmd.browser === false
|
|
211
|
+
|| (cmd.browser === undefined && (cmd.strategy === Strategy.PUBLIC || cmd.strategy === Strategy.LOCAL))) {
|
|
212
|
+
throw new Error(`Command ${key} adapterConcurrency requires browser capability`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
193
215
|
export function registerCommand(cmd) {
|
|
194
216
|
registerCommandInput(cmd);
|
|
195
217
|
}
|
|
@@ -197,6 +219,7 @@ function registerCommandInput(cmd) {
|
|
|
197
219
|
withRegistryMutationGroup(() => {
|
|
198
220
|
assertCommandAccess(cmd);
|
|
199
221
|
assertSiteSession(cmd);
|
|
222
|
+
assertAdapterConcurrency(cmd);
|
|
200
223
|
if (cmd.browser === 'conditional') {
|
|
201
224
|
if (typeof cmd.requiresBrowser !== 'function') {
|
|
202
225
|
const key = `${cmd.site}/${cmd.name}`;
|
|
@@ -33,6 +33,7 @@ export declare function serializeCommand(cmd: CliCommand): {
|
|
|
33
33
|
example: string;
|
|
34
34
|
defaultFormat: "table" | "plain" | "json" | "yaml" | "yml" | "md" | "markdown" | "csv" | null;
|
|
35
35
|
siteSession: import("./registry.js").SiteSessionMode | null;
|
|
36
|
+
adapterConcurrency: import("./registry.js").AdapterConcurrency | null;
|
|
36
37
|
};
|
|
37
38
|
/** Human-readable arg summary: `<required> [optional]` style. */
|
|
38
39
|
export declare function formatArgSummary(args: Arg[]): string;
|
|
@@ -35,6 +35,7 @@ export function serializeCommand(cmd) {
|
|
|
35
35
|
example: formatCommandExample(cmd),
|
|
36
36
|
defaultFormat: cmd.defaultFormat ?? null,
|
|
37
37
|
siteSession: cmd.siteSession ?? null,
|
|
38
|
+
adapterConcurrency: cmd.adapterConcurrency ?? null,
|
|
38
39
|
};
|
|
39
40
|
}
|
|
40
41
|
// ── Formatting ──────────────────────────────────────────────────────────────
|
package/dist/src/types.d.ts
CHANGED
|
@@ -222,6 +222,8 @@ export interface IPage {
|
|
|
222
222
|
/** Focus the browser window containing the active page for interactive login. */
|
|
223
223
|
focusWindow?(): Promise<void>;
|
|
224
224
|
closeWindow?(): Promise<void>;
|
|
225
|
+
/** Pin subsequent commands to the daemon profile selected for this operation. */
|
|
226
|
+
setContextId?(contextId: string): void;
|
|
225
227
|
/** Returns the current page URL, or null if unavailable. */
|
|
226
228
|
getCurrentUrl?(): Promise<string | null>;
|
|
227
229
|
/** Returns the active page identity (targetId), or undefined if not yet resolved. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sovovs/bycli",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.41",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"./download/media-download": "./dist/src/download/media-download.js",
|
|
34
34
|
"./download/progress": "./dist/src/download/progress.js",
|
|
35
35
|
"./download/wechat-article": "./dist/src/download/wechat-article.js",
|
|
36
|
-
"./pipeline": "./dist/src/pipeline/index.js"
|
|
36
|
+
"./pipeline": "./dist/src/pipeline/index.js",
|
|
37
|
+
"./adapter-coordination": "./dist/src/adapter-coordination.js"
|
|
37
38
|
},
|
|
38
39
|
"files": [
|
|
39
40
|
"dist/src/",
|