@panerelay/cli
Advanced tools
+3
-10
| #!/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"} |
+10
-142
| #!/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 @@ } |
+1
-3
| 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"} |
+34
-48
| 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) { |
+0
-4
@@ -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"} |
+0
-2
| 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'; |
+4
-4
| { | ||
| "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": { |
+5
-11
| # `@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()))); | ||
| } | ||
| } | ||
| } |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
21
-8.7%9
-18.18%137414
-12.82%39
-13.33%2346
-14.75%66
-8.33%+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
Updated