Sign In

@panerelay/cli

Package Overview
Dependencies
Maintainers
2
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@panerelay/cli - npm Package Compare versions

Comparing version
0.10.0
to
0.10.1
+3
-10
dist/cli.d.ts
#!/usr/bin/env node
import { clearBrowserDefault, listBrowserRegistrations, readBrowserDefault, selectBrowserRegistration, setBrowserDefault } from '@panerelay/browser-registry';
import { listBrowserRegistrations, readBrowserDefault, selectBrowserRegistration, setBrowserDefault } from '@panerelay/browser-registry';
import { setBrowserUseEnvironmentMode } from '@panerelay/browser-use/environment';
import type { CliAdapterMode } from '@panerelay/protocol';
import { type SupportedLocale } from './i18n.js';
import { resolveCliConnection, saveCliConnectionMode } from './adapter-dispatcher.js';
import { runCliConnectionCommand } from './command-runner.js';
import { saveCliConnectionMode } from './adapter-dispatcher.js';
import { readFetchAdapterRegistry } from './fetch-adapter-registry.js';
import { runFetchCommand } from './fetch-command.js';
export type CliOperation = 'browsers' | 'browser-use' | 'browser-clear' | 'connection-use' | 'connection-resolve' | 'fetch' | 'run';
export type CliOperation = 'browsers' | 'browser-use' | 'connection-use' | 'fetch';
export interface ParsedCliArgs {

@@ -15,6 +14,3 @@ browserSelector?: string;

connectionMode?: CliAdapterMode;
childCommand?: string[];
fetchArguments?: string[];
actorName?: string;
sessionLabel?: string;
help: boolean;

@@ -26,3 +22,2 @@ language?: SupportedLocale;

export interface CliDependencies {
clearBrowserDefault?: typeof clearBrowserDefault;
environment?: NodeJS.ProcessEnv;

@@ -34,6 +29,4 @@ listBrowserRegistrations?: typeof listBrowserRegistrations;

setBrowserDefault?: typeof setBrowserDefault;
resolveCliConnection?: typeof resolveCliConnection;
saveCliConnectionMode?: typeof saveCliConnectionMode;
setBrowserUseEnvironmentMode?: typeof setBrowserUseEnvironmentMode;
runCliConnectionCommand?: typeof runCliConnectionCommand;
runFetchCommand?: typeof runFetchCommand;

@@ -40,0 +33,0 @@ systemLocale?: string;

@@ -1,1 +0,1 @@

{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAGL,mBAAmB,EACnB,wBAAwB,EACxB,kBAAkB,EAClB,yBAAyB,EACzB,iBAAiB,EAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,4BAA4B,EAAE,MAAM,oCAAoC,CAAC;AAClF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAK1D,OAAO,EAA6C,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5F,OAAO,EAEL,oBAAoB,EACpB,qBAAqB,EACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAIrD,MAAM,MAAM,YAAY,GACpB,UAAU,GACV,aAAa,GACb,eAAe,GACf,gBAAgB,GAChB,oBAAoB,GACpB,OAAO,GACP,KAAK,CAAC;AAEV,MAAM,WAAW,aAAa;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,SAAS,CAAC,EAAE,YAAY,CAAC;CAC1B;AAwID,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,CAwI1D;AAMD,MAAM,WAAW,eAAe;IAC9B,mBAAmB,CAAC,EAAE,OAAO,mBAAmB,CAAC;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,wBAAwB,CAAC,EAAE,OAAO,wBAAwB,CAAC;IAC3D,kBAAkB,CAAC,EAAE,OAAO,kBAAkB,CAAC;IAC/C,wBAAwB,CAAC,EAAE,OAAO,wBAAwB,CAAC;IAC3D,yBAAyB,CAAC,EAAE,OAAO,yBAAyB,CAAC;IAC7D,iBAAiB,CAAC,EAAE,OAAO,iBAAiB,CAAC;IAC7C,oBAAoB,CAAC,EAAE,OAAO,oBAAoB,CAAC;IACnD,qBAAqB,CAAC,EAAE,OAAO,qBAAqB,CAAC;IACrD,4BAA4B,CAAC,EAAE,OAAO,4BAA4B,CAAC;IACnE,uBAAuB,CAAC,EAAE,OAAO,uBAAuB,CAAC;IACzD,eAAe,CAAC,EAAE,OAAO,eAAe,CAAC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAwDD,wBAAsB,IAAI,CACxB,IAAI,GAAE,MAAM,EAA0B,EACtC,YAAY,GAAE,eAAoB,GACjC,OAAO,CAAC,MAAM,CAAC,CAwLjB"}
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAGL,wBAAwB,EACxB,kBAAkB,EAClB,yBAAyB,EACzB,iBAAiB,EAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,4BAA4B,EAAE,MAAM,oCAAoC,CAAC;AAClF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAK1D,OAAO,EAA6C,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5F,OAAO,EAA2B,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AACzF,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAIrD,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,aAAa,GAAG,gBAAgB,GAAG,OAAO,CAAC;AAEnF,MAAM,WAAW,aAAa;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,SAAS,CAAC,EAAE,YAAY,CAAC;CAC1B;AAiID,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,CAqE1D;AAMD,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,wBAAwB,CAAC,EAAE,OAAO,wBAAwB,CAAC;IAC3D,kBAAkB,CAAC,EAAE,OAAO,kBAAkB,CAAC;IAC/C,wBAAwB,CAAC,EAAE,OAAO,wBAAwB,CAAC;IAC3D,yBAAyB,CAAC,EAAE,OAAO,yBAAyB,CAAC;IAC7D,iBAAiB,CAAC,EAAE,OAAO,iBAAiB,CAAC;IAC7C,qBAAqB,CAAC,EAAE,OAAO,qBAAqB,CAAC;IACrD,4BAA4B,CAAC,EAAE,OAAO,4BAA4B,CAAC;IACnE,eAAe,CAAC,EAAE,OAAO,eAAe,CAAC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAuDD,wBAAsB,IAAI,CACxB,IAAI,GAAE,MAAM,EAA0B,EACtC,YAAY,GAAE,eAAoB,GACjC,OAAO,CAAC,MAAM,CAAC,CAkIjB"}
#!/usr/bin/env node
import { PANERELAY_BROWSER_ID_ENV, PANERELAY_BROWSER_ENV, clearBrowserDefault, listBrowserRegistrations, readBrowserDefault, selectBrowserRegistration, setBrowserDefault, } from '@panerelay/browser-registry';
import { PANERELAY_BROWSER_ID_ENV, PANERELAY_BROWSER_ENV, listBrowserRegistrations, readBrowserDefault, selectBrowserRegistration, setBrowserDefault, } from '@panerelay/browser-registry';
import { setBrowserUseEnvironmentMode } from '@panerelay/browser-use/environment';

@@ -9,7 +9,6 @@ import { realpathSync } from 'node:fs';

import { normalizeLocale, resolveLocale, translate } from './i18n.js';
import { CliAdapterDispatchError, resolveCliConnection, saveCliConnectionMode, } from './adapter-dispatcher.js';
import { runCliConnectionCommand } from './command-runner.js';
import { CliAdapterDispatchError, saveCliConnectionMode } from './adapter-dispatcher.js';
import { readFetchAdapterRegistry } from './fetch-adapter-registry.js';
import { runFetchCommand } from './fetch-command.js';
const BUILT_IN_COMMANDS = new Set(['browsers', 'browser', 'connection', 'fetch', 'run']);
const BUILT_IN_COMMANDS = new Set(['browsers', 'browser', 'connection', 'fetch']);
function topLevelArguments(argv) {

@@ -71,8 +70,2 @@ const separator = argv.indexOf('--');

}
function optionAndInlineValue(argument) {
const separator = argument.indexOf('=');
return separator < 0
? [argument, undefined]
: [argument.slice(0, separator), argument.slice(separator + 1)];
}
function fetchAdapterCommandOptionStart(argv) {

@@ -171,5 +164,2 @@ const fetchIndex = argv.indexOf('fetch');

let connectionMode;
let childCommand;
let actorName;
let sessionLabel;
let operation;

@@ -191,6 +181,2 @@ let optionStart;

}
else if (action === 'clear') {
operation = 'browser-clear';
optionStart = 2;
}
else {

@@ -202,3 +188,3 @@ throw new Error(`Unknown command: browser${action ? ` ${action}` : ''}`);

const action = localized.argv[1];
if (action !== 'use' && action !== 'resolve') {
if (action !== 'use') {
throw new Error(`Unknown command: connection${action ? ` ${action}` : ''}`);

@@ -209,41 +195,9 @@ }

throw new Error('ADAPTER_ID_MISSING');
if (action === 'use') {
const rawMode = localized.argv[3];
if (rawMode !== 'direct' && rawMode !== 'extension') {
throw new Error('ADAPTER_MODE_INVALID');
}
connectionMode = rawMode;
operation = 'connection-use';
optionStart = 4;
const rawMode = localized.argv[3];
if (rawMode !== 'direct' && rawMode !== 'extension') {
throw new Error('ADAPTER_MODE_INVALID');
}
else {
operation = 'connection-resolve';
optionStart = 3;
for (let index = optionStart; index < localized.argv.length; index += 1) {
const argument = localized.argv[index];
const [option, inlineValue] = optionAndInlineValue(argument);
if (!['--mode', '--browser', '--actor', '--session-label'].includes(option)) {
throw new Error(`Unknown option: ${argument}`);
}
const value = inlineValue ?? localized.argv[++index];
if (!value || value.startsWith('-'))
throw new Error(`OPTION_VALUE_MISSING:${option}`);
if (option === '--mode') {
if (value !== 'direct' && value !== 'extension') {
throw new Error('ADAPTER_MODE_INVALID');
}
connectionMode = value;
}
else if (option === '--browser') {
browserSelector = value;
}
else if (option === '--actor') {
actorName = value;
}
else {
sessionLabel = value;
}
}
optionStart = localized.argv.length;
}
connectionMode = rawMode;
operation = 'connection-use';
optionStart = 4;
}

@@ -254,39 +208,2 @@ else if (selectedCommand === 'fetch') {

}
else if (selectedCommand === 'run') {
adapterId = localized.argv[1];
if (!adapterId || adapterId.startsWith('-'))
throw new Error('ADAPTER_ID_MISSING');
const separator = localized.argv.indexOf('--', 2);
if (separator < 0 || !localized.argv[separator + 1])
throw new Error('CHILD_COMMAND_MISSING');
for (let index = 2; index < separator; index += 1) {
const argument = localized.argv[index];
const [option, inlineValue] = optionAndInlineValue(argument);
if (!['--mode', '--browser', '--actor', '--session-label'].includes(option)) {
throw new Error(`Unknown option: ${argument}`);
}
const value = inlineValue ?? localized.argv[++index];
if (!value || value.startsWith('-') || index >= separator) {
throw new Error(`OPTION_VALUE_MISSING:${option}`);
}
if (option === '--mode') {
if (value !== 'direct' && value !== 'extension') {
throw new Error('ADAPTER_MODE_INVALID');
}
connectionMode = value;
}
else if (option === '--browser') {
browserSelector = value;
}
else if (option === '--actor') {
actorName = value;
}
else {
sessionLabel = value;
}
}
childCommand = localized.argv.slice(separator + 1);
operation = 'run';
optionStart = localized.argv.length;
}
else {

@@ -302,6 +219,3 @@ throw new Error(`Unknown command: ${selectedCommand}`);

...(connectionMode ? { connectionMode } : {}),
...(childCommand ? { childCommand } : {}),
...(operation === 'fetch' ? { fetchArguments: localized.argv.slice(1) } : {}),
...(actorName ? { actorName } : {}),
...(sessionLabel ? { sessionLabel } : {}),
help: false,

@@ -326,4 +240,2 @@ language: localized.language,

return translate(locale, 'errorAdapterIdMissing');
if (message === 'CHILD_COMMAND_MISSING')
return translate(locale, 'errorChildCommandMissing');
if (message === 'ADAPTER_MODE_INVALID')

@@ -478,46 +390,2 @@ return translate(locale, 'errorAdapterModeInvalid');

}
if (parsed.operation === 'connection-resolve') {
const result = await (dependencies.resolveCliConnection ?? resolveCliConnection)({
adapterId: parsed.adapterId,
actor: {
name: parsed.actorName ?? parsed.adapterId,
...(parsed.sessionLabel ? { sessionLabel: parsed.sessionLabel } : {}),
},
...(parsed.browserSelector ? { browserSelector: parsed.browserSelector } : {}),
...(parsed.connectionMode ? { mode: parsed.connectionMode } : {}),
}, {
adapterPreferences: { environment: dependencies.environment },
adapterRegistry: { environment: dependencies.environment },
environment: dependencies.environment,
});
console.log(JSON.stringify({
protocol: 'panerelay.connection.v1',
adapterId: result.adapterId,
mode: result.mode,
connection: result.connection,
...(result.expiresAt ? { expiresAt: result.expiresAt } : {}),
...(result.concurrencyKey ? { concurrencyKey: result.concurrencyKey } : {}),
environmentKeys: Object.keys(result.environment).sort(),
}));
return 0;
}
if (parsed.operation === 'run') {
return await (dependencies.runCliConnectionCommand ?? runCliConnectionCommand)({
adapterId: parsed.adapterId,
actor: {
name: parsed.actorName ?? parsed.adapterId,
...(parsed.sessionLabel ? { sessionLabel: parsed.sessionLabel } : {}),
},
childCommand: parsed.childCommand,
...(parsed.browserSelector ? { browserSelector: parsed.browserSelector } : {}),
...(parsed.connectionMode ? { mode: parsed.connectionMode } : {}),
}, {
adapterPreferences: { environment: dependencies.environment },
adapterRegistry: { environment: dependencies.environment },
environment: dependencies.environment,
concurrencyLock: { environment: dependencies.environment },
});
}
await (dependencies.clearBrowserDefault ?? clearBrowserDefault)(undefined, registryOptions);
console.log(translate(locale, 'browserDefaultCleared'));
return 0;

@@ -524,0 +392,0 @@ }

export type SupportedLocale = 'en' | 'zh-CN';
declare const englishMessages: {
readonly browserDefaultCleared: "Saved browser default cleared.";
readonly browserDefaultMarker: "default";

@@ -20,3 +19,2 @@ readonly browserDefaultSet: "Default browser: {name} ({id})";

readonly errorBrowserUnavailable: "The selected Panerelay browser is unavailable.";
readonly errorChildCommandMissing: "run requires -- followed by the child command";
readonly errorGenerationChanged: "The selected browser connection changed. Run the command again.";

@@ -32,3 +30,3 @@ readonly errorConnectionNotReady: "The selected connection is not ready.";

readonly errorUnknownOption: "Unknown option: {option}";
readonly help: "Panerelay CLI\n\nUsage:\n panerelay browsers [--lang <language>]\n panerelay browser use <registration-id|family> [--lang <language>]\n panerelay browser clear [--lang <language>]\n panerelay connection use <adapter-id> <direct|extension> [--lang <language>]\n panerelay connection resolve <adapter-id> [--mode <mode>] [--browser <selector>]\n panerelay <site> <command> [site arguments]\n panerelay fetch <url|site> [request or site arguments]\n panerelay run <adapter-id> [--mode <mode>] [--browser <selector>] -- <command> [arguments...]\n\nCommands:\n browsers List live Panerelay browser registrations\n browser use Save one live registration as the routing default\n browser clear\n Clear the saved browser default\n connection use\n Save a connection mode for one installed adapter\n connection resolve\n Resolve connection material without running an automation command\n fetch Send a browser-backed request or explicitly run a site adapter\n run Run the caller's command with the selected connection\n\nOptions:\n --lang Use en or zh-CN instead of the system language\n --version, -v\n Show the version\n --help, -h Show this help\n\nOccasional use without a global installation:\n npx --yes @panerelay/cli browsers\n\nInstalled site adapters can run directly; use fetch for raw URLs or disambiguation.";
readonly help: "Panerelay CLI\n\nCommon usage:\n panerelay <site> <command> [site arguments]\n Run an installed site adapter\n panerelay fetch <url|site> [request or site arguments]\n Send a browser-authenticated request or disambiguate a site command\n panerelay browsers\n List connected Panerelay browsers\n panerelay browser use <registration-id|family>\n Choose the default when multiple browsers are connected\n panerelay connection use <adapter-id> <direct|extension>\n Choose the connection mode for an installed automation adapter\n\nSetup and site adapters:\n npx --yes @panerelay/setup\n Install or update the Native Host and global CLI\n npx --yes @panerelay/setup add <adapter>\n Install or update one site adapter\n npx --yes @panerelay/setup add --all\n Install or update the complete built-in site catalog\n\nOptions:\n --lang Use en or zh-CN instead of the system language\n --version, -v\n Show the version\n --help, -h Show this help\n\nRun panerelay fetch --help to see installed sites and complete Fetch options.";
};

@@ -35,0 +33,0 @@ type MessageKey = keyof typeof englishMessages;

@@ -1,1 +0,1 @@

{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,OAAO,CAAC;AAE7C,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+DX,CAAC;AAEX,KAAK,UAAU,GAAG,MAAM,OAAO,eAAe,CAAC;AAmE/C,MAAM,WAAW,uBAAuB;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,eAAe,GAAG,SAAS,CAMtF;AAyBD,wBAAgB,aAAa,CAAC,OAAO,GAAE,uBAA4B,GAAG,eAAe,CAWpF;AAED,wBAAgB,SAAS,CACvB,MAAM,EAAE,eAAe,EACvB,GAAG,EAAE,UAAU,EACf,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,MAAM,CAGR"}
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,OAAO,CAAC;AAE7C,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwDX,CAAC;AAEX,KAAK,UAAU,GAAG,MAAM,OAAO,eAAe,CAAC;AA4D/C,MAAM,WAAW,uBAAuB;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,eAAe,GAAG,SAAS,CAMtF;AAyBD,wBAAgB,aAAa,CAAC,OAAO,GAAE,uBAA4B,GAAG,eAAe,CAWpF;AAED,wBAAgB,SAAS,CACvB,MAAM,EAAE,eAAe,EACvB,GAAG,EAAE,UAAU,EACf,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,MAAM,CAGR"}
import { execFileSync } from 'node:child_process';
const englishMessages = {
browserDefaultCleared: 'Saved browser default cleared.',
browserDefaultMarker: 'default',

@@ -20,3 +19,2 @@ browserDefaultSet: 'Default browser: {name} ({id})',

errorBrowserUnavailable: 'The selected Panerelay browser is unavailable.',
errorChildCommandMissing: 'run requires -- followed by the child command',
errorGenerationChanged: 'The selected browser connection changed. Run the command again.',

@@ -34,23 +32,21 @@ errorConnectionNotReady: 'The selected connection is not ready.',

Usage:
panerelay browsers [--lang <language>]
panerelay browser use <registration-id|family> [--lang <language>]
panerelay browser clear [--lang <language>]
panerelay connection use <adapter-id> <direct|extension> [--lang <language>]
panerelay connection resolve <adapter-id> [--mode <mode>] [--browser <selector>]
Common usage:
panerelay <site> <command> [site arguments]
Run an installed site adapter
panerelay fetch <url|site> [request or site arguments]
panerelay run <adapter-id> [--mode <mode>] [--browser <selector>] -- <command> [arguments...]
Send a browser-authenticated request or disambiguate a site command
panerelay browsers
List connected Panerelay browsers
panerelay browser use <registration-id|family>
Choose the default when multiple browsers are connected
panerelay connection use <adapter-id> <direct|extension>
Choose the connection mode for an installed automation adapter
Commands:
browsers List live Panerelay browser registrations
browser use Save one live registration as the routing default
browser clear
Clear the saved browser default
connection use
Save a connection mode for one installed adapter
connection resolve
Resolve connection material without running an automation command
fetch Send a browser-backed request or explicitly run a site adapter
run Run the caller's command with the selected connection
Setup and site adapters:
npx --yes @panerelay/setup
Install or update the Native Host and global CLI
npx --yes @panerelay/setup add <adapter>
Install or update one site adapter
npx --yes @panerelay/setup add --all
Install or update the complete built-in site catalog

@@ -63,9 +59,5 @@ Options:

Occasional use without a global installation:
npx --yes @panerelay/cli browsers
Installed site adapters can run directly; use fetch for raw URLs or disambiguation.`,
Run panerelay fetch --help to see installed sites and complete Fetch options.`,
};
const chineseMessages = {
browserDefaultCleared: '已清除保存的默认浏览器。',
browserDefaultMarker: '默认',

@@ -87,3 +79,2 @@ browserDefaultSet: '默认浏览器:{name}({id})',

errorBrowserUnavailable: '所选 Panerelay 浏览器当前不可用。',
errorChildCommandMissing: 'run 需要在 -- 后指定要运行的子命令',
errorGenerationChanged: '所选浏览器连接已变化,请重新运行命令。',

@@ -101,23 +92,21 @@ errorConnectionNotReady: '所选连接尚未就绪。',

用法:
panerelay browsers [--lang <语言>]
panerelay browser use <注册 ID|浏览器类型> [--lang <语言>]
panerelay browser clear [--lang <语言>]
panerelay connection use <adapter ID> <direct|extension> [--lang <语言>]
panerelay connection resolve <adapter ID> [--mode <模式>] [--browser <选择器>]
常用用法:
panerelay <站点> <命令> [站点参数]
运行已安装的站点适配器
panerelay fetch <URL|站点> [请求或站点参数]
panerelay run <adapter ID> [--mode <模式>] [--browser <选择器>] -- <命令> [参数...]
发送浏览器登录态请求,或消歧站点命令
panerelay browsers
列出已连接的 Panerelay 浏览器
panerelay browser use <注册 ID|浏览器类型>
在连接多个浏览器时选择默认浏览器
panerelay connection use <adapter ID> <direct|extension>
选择已安装自动化 adapter 的连接模式
命令:
browsers 列出在线的 Panerelay 浏览器注册
browser use 保存一个在线注册作为路由默认浏览器
browser clear
清除保存的默认浏览器
connection use
保存一个已安装 adapter 的连接模式
connection resolve
解析连接信息,但不运行自动化命令
fetch 发送浏览器请求或显式运行站点适配器
run 使用所选连接运行调用者指定的命令
安装与站点适配器:
npx --yes @panerelay/setup
安装或更新 Native Host 和全局 CLI
npx --yes @panerelay/setup add <适配器>
安装或更新一个站点适配器
npx --yes @panerelay/setup add --all
安装或更新完整的内置站点目录

@@ -130,6 +119,3 @@ 选项:

无需全局安装的临时用法:
npx --yes @panerelay/cli browsers
已安装的站点适配器可直接运行;原始 URL 请求或命令消歧请使用 fetch。`,
运行 panerelay fetch --help 查看已安装站点和完整 Fetch 选项。`,
};

@@ -136,0 +122,0 @@ export function normalizeLocale(value) {

@@ -6,8 +6,4 @@ export { main, parseCliArgs } from './cli.js';

export { CliAdapterDispatchError, resolveCliAdapterSpawn, resolveCliConnection, saveCliConnectionMode, } from './adapter-dispatcher.js';
export { runCliConnectionCommand } from './command-runner.js';
export { createBrowserFetchSession, requestBrowserFetchPermission, releaseBrowserFetchSession, runBrowserFetch, runBrowserFetchInSession, } from './browser-fetch-client.js';
export type { ActiveBrowserFetchSession, BrowserFetchClientOptions, BrowserFetchHttpClient, } from './browser-fetch-client.js';
export type { CliCommandRunnerDependencies, CliCommandRunnerOptions, RunCliConnectionInput, } from './command-runner.js';
export { PANERELAY_CLI_ADAPTER_LOCK_DIRECTORY_ENV, acquireCliConcurrencyLock, } from './concurrency-lock.js';
export type { CliConcurrencyLock, CliConcurrencyLockOptions } from './concurrency-lock.js';
export type { CliAdapterInvocationOptions, CliConnectionResolverDependencies, CliConnectionResolverOptions, ResolveCliConnectionInput, ResolvedCliConnection, } from './adapter-dispatcher.js';

@@ -14,0 +10,0 @@ export { PANERELAY_CLI_ADAPTER_REGISTRY_PATH_ENV, PANERELAY_CLI_ADAPTER_REGISTRY_VERSION, cliAdapterDataDirectory, cliAdapterRegistryPath, isCliAdapterRegistration, readCliAdapterRegistration, readCliAdapterRegistry, registerCliAdapter, removeCliAdapterRegistration, } from './adapter-registry.js';

@@ -1,1 +0,1 @@

{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC9C,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC7E,OAAO,EACL,0CAA0C,EAC1C,yCAAyC,EACzC,yBAAyB,EACzB,kBAAkB,EAClB,yBAAyB,EACzB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,2BAA2B,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACnG,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EACL,yBAAyB,EACzB,6BAA6B,EAC7B,0BAA0B,EAC1B,eAAe,EACf,wBAAwB,GACzB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,yBAAyB,EACzB,yBAAyB,EACzB,sBAAsB,GACvB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,4BAA4B,EAC5B,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,wCAAwC,EACxC,yBAAyB,GAC1B,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAC3F,YAAY,EACV,2BAA2B,EAC3B,iCAAiC,EACjC,4BAA4B,EAC5B,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uCAAuC,EACvC,sCAAsC,EACtC,uBAAuB,EACvB,sBAAsB,EACtB,wBAAwB,EACxB,0BAA0B,EAC1B,sBAAsB,EACtB,kBAAkB,EAClB,4BAA4B,GAC7B,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,sBAAsB,EACtB,kBAAkB,EAClB,yBAAyB,GAC1B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtE,YAAY,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC1E,OAAO,EACL,yCAAyC,EACzC,yBAAyB,EACzB,wBAAwB,EACxB,4BAA4B,EAC5B,wBAAwB,GACzB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAC/E,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC/F,YAAY,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AACjF,OAAO,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AAC3E,YAAY,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AACjF,OAAO,EACL,gCAAgC,EAChC,sBAAsB,EACtB,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,wBAAwB,EACxB,mBAAmB,EACnB,cAAc,EACd,wBAAwB,GACzB,MAAM,oBAAoB,CAAC"}
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC9C,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC7E,OAAO,EACL,0CAA0C,EAC1C,yCAAyC,EACzC,yBAAyB,EACzB,kBAAkB,EAClB,yBAAyB,EACzB,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,2BAA2B,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACnG,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,yBAAyB,EACzB,6BAA6B,EAC7B,0BAA0B,EAC1B,eAAe,EACf,wBAAwB,GACzB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,yBAAyB,EACzB,yBAAyB,EACzB,sBAAsB,GACvB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,2BAA2B,EAC3B,iCAAiC,EACjC,4BAA4B,EAC5B,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uCAAuC,EACvC,sCAAsC,EACtC,uBAAuB,EACvB,sBAAsB,EACtB,wBAAwB,EACxB,0BAA0B,EAC1B,sBAAsB,EACtB,kBAAkB,EAClB,4BAA4B,GAC7B,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,sBAAsB,EACtB,kBAAkB,EAClB,yBAAyB,GAC1B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtE,YAAY,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC1E,OAAO,EACL,yCAAyC,EACzC,yBAAyB,EACzB,wBAAwB,EACxB,4BAA4B,EAC5B,wBAAwB,GACzB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAC/E,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAC/F,YAAY,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AACjF,OAAO,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AAC3E,YAAY,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AACjF,OAAO,EACL,gCAAgC,EAChC,sBAAsB,EACtB,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,wBAAwB,EACxB,mBAAmB,EACnB,cAAc,EACd,wBAAwB,GACzB,MAAM,oBAAoB,CAAC"}
export { main, parseCliArgs } from './cli.js';
export { PANERELAY_CLI_ADAPTER_PREFERENCES_PATH_ENV, PANERELAY_CLI_ADAPTER_PREFERENCES_VERSION, cliAdapterPreferencesPath, readCliAdapterMode, readCliAdapterPreferences, removeCliAdapterMode, setCliAdapterMode, } from './adapter-preferences.js';
export { CliAdapterDispatchError, resolveCliAdapterSpawn, resolveCliConnection, saveCliConnectionMode, } from './adapter-dispatcher.js';
export { runCliConnectionCommand } from './command-runner.js';
export { createBrowserFetchSession, requestBrowserFetchPermission, releaseBrowserFetchSession, runBrowserFetch, runBrowserFetchInSession, } from './browser-fetch-client.js';
export { PANERELAY_CLI_ADAPTER_LOCK_DIRECTORY_ENV, acquireCliConcurrencyLock, } from './concurrency-lock.js';
export { PANERELAY_CLI_ADAPTER_REGISTRY_PATH_ENV, PANERELAY_CLI_ADAPTER_REGISTRY_VERSION, cliAdapterDataDirectory, cliAdapterRegistryPath, isCliAdapterRegistration, readCliAdapterRegistration, readCliAdapterRegistry, registerCliAdapter, removeCliAdapterRegistration, } from './adapter-registry.js';

@@ -8,0 +6,0 @@ export { normalizeLocale, resolveLocale, translate } from './i18n.js';

{
"name": "@panerelay/cli",
"version": "0.10.0",
"version": "0.10.1",
"description": "Engine-neutral browser administration CLI for Panerelay.",

@@ -46,5 +46,5 @@ "type": "module",

"dependencies": {
"@panerelay/browser-registry": "0.10.0",
"@panerelay/protocol": "0.10.0",
"@panerelay/browser-use": "0.10.0"
"@panerelay/browser-registry": "0.10.1",
"@panerelay/browser-use": "0.10.1",
"@panerelay/protocol": "0.10.1"
},

@@ -51,0 +51,0 @@ "scripts": {

# `@panerelay/cli`
Optional, engine-neutral browser administration for Panerelay.
Recurring browser-authenticated Fetch, site-adapter, and browser-selection commands for Panerelay.
Install it when you want a persistent `panerelay` command:
Base Setup installs or updates the global `panerelay` command. Install site adapters separately when needed:
```bash
npm install --global @panerelay/cli
npx --yes @panerelay/setup
npx --yes @panerelay/setup add bilibili
panerelay browsers
panerelay browser use edge
panerelay browser clear
```
For occasional use, no global installation is required:
```bash
npx --yes @panerelay/cli browsers
```
## Browser-backed fetch

@@ -58,3 +52,3 @@

Setup-managed connection adapters can also expose engine-neutral mode operations. The examples below use a globally installed `panerelay` command. Browser Use itself is invoked through the official `browser-use` command; setup manages its Browser Harness environment default:
Setup-managed connection adapters can also expose engine-neutral mode preferences. Browser Use itself is invoked through the official `browser-use` command; Setup manages its Browser Harness environment default:

@@ -61,0 +55,0 @@ ```bash

import { spawn } from 'node:child_process';
import { resolveCliConnection, type CliConnectionResolverOptions, type ResolveCliConnectionInput } from './adapter-dispatcher.js';
import { acquireCliConcurrencyLock, type CliConcurrencyLockOptions } from './concurrency-lock.js';
export interface RunCliConnectionInput extends ResolveCliConnectionInput {
childCommand: string[];
}
export interface CliCommandRunnerDependencies {
acquireLock?: typeof acquireCliConcurrencyLock;
resolveConnection?: typeof resolveCliConnection;
spawnChild?: typeof spawn;
}
export interface CliCommandRunnerOptions extends CliConnectionResolverOptions {
concurrencyLock?: CliConcurrencyLockOptions;
runnerDependencies?: CliCommandRunnerDependencies;
}
export declare function runCliConnectionCommand(input: RunCliConnectionInput, options?: CliCommandRunnerOptions): Promise<number>;
//# sourceMappingURL=command-runner.d.ts.map
{"version":3,"file":"command-runner.d.ts","sourceRoot":"","sources":["../src/command-runner.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EACL,oBAAoB,EACpB,KAAK,4BAA4B,EACjC,KAAK,yBAAyB,EAC/B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,yBAAyB,EAAE,KAAK,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAIlG,MAAM,WAAW,qBAAsB,SAAQ,yBAAyB;IACtE,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,4BAA4B;IAC3C,WAAW,CAAC,EAAE,OAAO,yBAAyB,CAAC;IAC/C,iBAAiB,CAAC,EAAE,OAAO,oBAAoB,CAAC;IAChD,UAAU,CAAC,EAAE,OAAO,KAAK,CAAC;CAC3B;AAED,MAAM,WAAW,uBAAwB,SAAQ,4BAA4B;IAC3E,eAAe,CAAC,EAAE,yBAAyB,CAAC;IAC5C,kBAAkB,CAAC,EAAE,4BAA4B,CAAC;CACnD;AAsCD,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,qBAAqB,EAC5B,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,MAAM,CAAC,CAgCjB"}
import { constants } from 'node:os';
import { spawn } from 'node:child_process';
import { resolveCliConnection, } from './adapter-dispatcher.js';
import { acquireCliConcurrencyLock } from './concurrency-lock.js';
const FORWARDED_SIGNALS = ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGQUIT'];
function signalExitCode(signal) {
return 128 + (constants.signals[signal] ?? 0);
}
async function waitForChild(child) {
const listeners = new Map();
for (const signal of FORWARDED_SIGNALS) {
const listener = () => {
if (child.exitCode === null && child.signalCode === null)
child.kill(signal);
};
try {
process.on(signal, listener);
listeners.set(signal, listener);
}
catch {
// The platform may not expose every POSIX signal.
}
}
try {
return await new Promise((resolve, reject) => {
let settled = false;
child.once('error', () => {
if (settled)
return;
settled = true;
reject(new Error('Child command could not be started'));
});
child.once('exit', (code, signal) => {
if (settled)
return;
settled = true;
resolve(code ?? (signal ? signalExitCode(signal) : 1));
});
});
}
finally {
for (const [signal, listener] of listeners)
process.off(signal, listener);
}
}
export async function runCliConnectionCommand(input, options = {}) {
if (input.childCommand.length === 0 || !input.childCommand[0]) {
throw new Error('A child command is required');
}
const resolved = await (options.runnerDependencies?.resolveConnection ?? resolveCliConnection)(input, options);
const environment = {
...(options.environment ?? process.env),
...resolved.environment,
};
const lock = resolved.concurrencyKey
? await (options.runnerDependencies?.acquireLock ?? acquireCliConcurrencyLock)(resolved.concurrencyKey, options.concurrencyLock)
: null;
try {
const child = (options.runnerDependencies?.spawnChild ?? spawn)(input.childCommand[0], input.childCommand.slice(1), {
env: environment,
stdio: 'inherit',
windowsHide: true,
});
return await waitForChild(child);
}
finally {
await lock?.release();
}
}
export declare const PANERELAY_CLI_ADAPTER_LOCK_DIRECTORY_ENV: "PANERELAY_CLI_ADAPTER_LOCK_DIRECTORY";
export interface CliConcurrencyLockOptions {
environment?: NodeJS.ProcessEnv;
homeDirectory?: string;
isProcessAlive?: (pid: number) => boolean;
lockDirectory?: string;
waitMs?: number;
}
export interface CliConcurrencyLock {
release(): Promise<void>;
}
export declare function acquireCliConcurrencyLock(key: string, options?: CliConcurrencyLockOptions): Promise<CliConcurrencyLock>;
//# sourceMappingURL=concurrency-lock.d.ts.map
{"version":3,"file":"concurrency-lock.d.ts","sourceRoot":"","sources":["../src/concurrency-lock.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,wCAAwC,EACnD,sCAA+C,CAAC;AAelD,MAAM,WAAW,yBAAyB;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAoED,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,kBAAkB,CAAC,CA8E7B"}
import { createHash, randomBytes } from 'node:crypto';
import { chmod, lstat, mkdir, open, readFile, unlink } from 'node:fs/promises';
import { homedir } from 'node:os';
import { join } from 'node:path';
import { CliAdapterDispatchError } from './adapter-dispatcher.js';
export const PANERELAY_CLI_ADAPTER_LOCK_DIRECTORY_ENV = 'PANERELAY_CLI_ADAPTER_LOCK_DIRECTORY';
const LOCK_PROTOCOL = 'panerelay.cli-adapter-lock.v1';
const DEFAULT_WAIT_MS = 750;
const POLL_MS = 50;
const MAX_STALE_RECLAIMS = 8;
class StaleLockRemovedError extends Error {
}
function lockDirectory(options) {
return (options.lockDirectory ??
options.environment?.[PANERELAY_CLI_ADAPTER_LOCK_DIRECTORY_ENV] ??
join(options.homeDirectory ?? homedir(), '.panerelay', 'locks', 'cli-adapters'));
}
function lockPath(key, options) {
const digest = createHash('sha256').update(key).digest('hex');
return join(lockDirectory(options), `${digest}.lock`);
}
function isLockRecord(value) {
if (!value || typeof value !== 'object' || Array.isArray(value))
return false;
const candidate = value;
return (Object.keys(candidate).length === 4 &&
candidate.protocol === LOCK_PROTOCOL &&
typeof candidate.pid === 'number' &&
Number.isSafeInteger(candidate.pid) &&
candidate.pid > 0 &&
typeof candidate.token === 'string' &&
/^[a-f0-9]{32}$/.test(candidate.token) &&
typeof candidate.createdAt === 'string' &&
Number.isFinite(Date.parse(candidate.createdAt)));
}
function processAlive(pid, options) {
if (options.isProcessAlive)
return options.isProcessAlive(pid);
try {
process.kill(pid, 0);
return true;
}
catch {
return false;
}
}
async function readProtectedLock(filePath, options) {
const metadata = await lstat(filePath);
if (!metadata.isFile() ||
metadata.isSymbolicLink() ||
(process.platform !== 'win32' && (metadata.mode & 0o077) !== 0)) {
throw new CliAdapterDispatchError('busy', 'Connection lane lock is unsafe');
}
const value = JSON.parse(await readFile(filePath, 'utf8'));
if (!isLockRecord(value)) {
throw new CliAdapterDispatchError('busy', 'Connection lane lock is invalid');
}
if (!processAlive(value.pid, options)) {
await unlink(filePath).catch(() => undefined);
throw new StaleLockRemovedError();
}
return value;
}
function delay(milliseconds) {
return new Promise(resolve => setTimeout(resolve, milliseconds));
}
export async function acquireCliConcurrencyLock(key, options = {}) {
if (!/^[A-Za-z0-9._:-]{1,128}$/.test(key)) {
throw new CliAdapterDispatchError('busy', 'Connection lane lock key is invalid');
}
const directory = lockDirectory(options);
await mkdir(directory, { recursive: true, mode: 0o700 });
if (process.platform !== 'win32')
await chmod(directory, 0o700);
const filePath = lockPath(key, options);
const token = randomBytes(16).toString('hex');
const record = {
protocol: LOCK_PROTOCOL,
pid: process.pid,
token,
createdAt: new Date().toISOString(),
};
const waitMs = Math.max(0, Math.min(options.waitMs ?? DEFAULT_WAIT_MS, 5_000));
const deadline = Date.now() + waitMs;
let attempts = 0;
let staleReclaims = 0;
let retryAfterReclaim = false;
while (true) {
if (attempts > 0 && Date.now() >= deadline && !retryAfterReclaim) {
throw new CliAdapterDispatchError('busy', 'Another command is using this connection lane');
}
attempts += 1;
retryAfterReclaim = false;
try {
const handle = await open(filePath, 'wx', 0o600);
try {
await handle.writeFile(`${JSON.stringify(record)}\n`);
}
finally {
await handle.close();
}
return {
release: async () => {
try {
const current = await readProtectedLock(filePath, {
...options,
isProcessAlive: pid => pid === process.pid || processAlive(pid, options),
});
if (current.token === token)
await unlink(filePath);
}
catch (error) {
if (error.code !== 'ENOENT') {
// A missing, replaced, or damaged lock is never removed by this owner.
}
}
},
};
}
catch (error) {
if (error.code !== 'EEXIST') {
if (error instanceof CliAdapterDispatchError)
throw error;
throw new CliAdapterDispatchError('busy', 'Connection lane lock is unavailable');
}
try {
await readProtectedLock(filePath, options);
}
catch (readError) {
const reclaimable = readError instanceof StaleLockRemovedError ||
readError.code === 'ENOENT';
if (!reclaimable)
throw readError;
staleReclaims += 1;
if (staleReclaims > MAX_STALE_RECLAIMS || (Date.now() >= deadline && staleReclaims > 1)) {
throw new CliAdapterDispatchError('busy', 'Another command is using this connection lane');
}
retryAfterReclaim = true;
if (Date.now() < deadline)
await delay(1);
continue;
}
if (Date.now() >= deadline) {
throw new CliAdapterDispatchError('busy', 'Another command is using this connection lane');
}
await delay(Math.min(POLL_MS, Math.max(1, deadline - Date.now())));
}
}
}