Sign In

@panerelay/setup

Package Overview
Dependencies
Maintainers
2
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@panerelay/setup - npm Package Compare versions

Comparing version
0.8.0
to
0.9.0
+22
dist/agent-fetch-integration.d.ts
export type AgentFetchIntegration = 'codex' | 'claude';
export interface AgentFetchIntegrationOptions {
homeDirectory?: string;
statePath?: string;
}
export interface AgentFetchIntegrationStatus {
configured: boolean;
detail: string;
integration: AgentFetchIntegration;
}
export declare function installCodexFetchIntegration(launchPath: string, options?: AgentFetchIntegrationOptions): Promise<string>;
export declare function uninstallCodexFetchIntegration(options?: AgentFetchIntegrationOptions): Promise<string | undefined>;
export declare function installClaudeFetchIntegration(launchPath: string, options?: AgentFetchIntegrationOptions): Promise<{
configPath: string;
settingsPath: string;
}>;
export declare function uninstallClaudeFetchIntegration(options?: AgentFetchIntegrationOptions): Promise<{
configPath: string;
settingsPath: string;
} | undefined>;
export declare function readAgentFetchIntegrationStatus(integration: AgentFetchIntegration, options?: AgentFetchIntegrationOptions): Promise<AgentFetchIntegrationStatus>;
//# sourceMappingURL=agent-fetch-integration.d.ts.map
{"version":3,"file":"agent-fetch-integration.d.ts","sourceRoot":"","sources":["../src/agent-fetch-integration.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,QAAQ,CAAC;AA0BvD,MAAM,WAAW,4BAA4B;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,2BAA2B;IAC1C,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,qBAAqB,CAAC;CACpC;AA2SD,wBAAsB,4BAA4B,CAChD,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,MAAM,CAAC,CAiBjB;AAED,wBAAsB,8BAA8B,CAClD,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAmB7B;AAMD,wBAAsB,6BAA6B,CACjD,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC,CAuDvD;AAED,wBAAsB,+BAA+B,CACnD,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC,CAgDnE;AAED,wBAAsB,+BAA+B,CACnD,WAAW,EAAE,qBAAqB,EAClC,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,2BAA2B,CAAC,CAoCtC"}
import { randomBytes } from 'node:crypto';
import { chmod, mkdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
import { homedir } from 'node:os';
import { dirname, join } from 'node:path';
const STATE_PROTOCOL = 'panerelay.agent-fetch-integrations.v1';
const CODEX_MCP_START = '# >>> Panerelay browser fetch MCP >>>';
const CODEX_MCP_END = '# <<< Panerelay browser fetch MCP <<<';
const CODEX_TOOLS_START = '# >>> Panerelay browser fetch tools >>>';
const CODEX_TOOLS_END = '# <<< Panerelay browser fetch tools <<<';
const CODEX_WEB_SEARCH_MARKER = '# Panerelay browser fetch managed';
const MAX_CONFIG_BYTES = 2 * 1024 * 1024;
function home(options) {
return options.homeDirectory ?? homedir();
}
function statePath(options) {
return options.statePath ?? join(home(options), '.panerelay', 'agent-fetch-integrations.json');
}
function codexConfigPath(options) {
return join(home(options), '.codex', 'config.toml');
}
function claudeConfigPath(options) {
return join(home(options), '.claude.json');
}
function claudeSettingsPath(options) {
return join(home(options), '.claude', 'settings.json');
}
async function readBounded(path) {
try {
const value = await readFile(path, 'utf8');
if (Buffer.byteLength(value) > MAX_CONFIG_BYTES) {
throw new Error(`Agent configuration is too large to manage safely: ${path}`);
}
return value;
}
catch (error) {
if (error.code === 'ENOENT')
return undefined;
throw error;
}
}
async function existingMode(path) {
try {
return (await stat(path)).mode & 0o777;
}
catch (error) {
if (error.code === 'ENOENT')
return undefined;
throw error;
}
}
async function writeProtected(path, value, options = {}) {
const directory = dirname(path);
const temporary = `${path}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;
const mode = (await existingMode(path)) ?? 0o600;
await mkdir(directory, { recursive: true, mode: 0o700 });
if (options.protectDirectory)
await chmod(directory, 0o700);
try {
await writeFile(temporary, value, { mode });
await rename(temporary, path);
await chmod(path, mode);
}
finally {
await rm(temporary, { force: true }).catch(() => undefined);
}
}
function asObject(value) {
return value && typeof value === 'object' && !Array.isArray(value)
? value
: {};
}
function hasOnlyKeys(value, keys) {
return Object.keys(value).every(key => keys.includes(key));
}
function boundedString(value, maximum = MAX_CONFIG_BYTES) {
return typeof value === 'string' && Buffer.byteLength(value) <= maximum;
}
async function readJsonObject(path) {
const source = await readBounded(path);
if (source === undefined)
return {};
try {
const value = JSON.parse(source);
if (!value || typeof value !== 'object' || Array.isArray(value))
throw new Error();
return value;
}
catch {
throw new Error(`Agent configuration is not a JSON object: ${path}`);
}
}
async function readJsonObjectWithExistence(path) {
const source = await readBounded(path);
if (source === undefined)
return { exists: false, value: {} };
try {
const value = JSON.parse(source);
if (!value || typeof value !== 'object' || Array.isArray(value))
throw new Error();
return { exists: true, value: value };
}
catch {
throw new Error(`Agent configuration is not a JSON object: ${path}`);
}
}
function validState(value, options) {
const state = asObject(value);
if (state.protocol !== STATE_PROTOCOL || !hasOnlyKeys(state, ['protocol', 'codex', 'claude'])) {
return false;
}
if (state.codex !== undefined) {
const codex = asObject(state.codex);
const webSearch = asObject(codex.webSearch);
if (!hasOnlyKeys(codex, ['configExisted', 'configPath', 'mcpBlock', 'webSearch']) ||
typeof codex.configExisted !== 'boolean' ||
codex.configPath !== codexConfigPath(options) ||
!boundedString(codex.mcpBlock) ||
!hasOnlyKeys(webSearch, ['mode', 'previousLine']) ||
!['replaced', 'inserted', 'created-table'].includes(String(webSearch.mode)) ||
(webSearch.mode === 'replaced' && !boundedString(webSearch.previousLine, 8 * 1024)) ||
(webSearch.mode !== 'replaced' && webSearch.previousLine !== undefined)) {
return false;
}
}
if (state.claude !== undefined) {
const claude = asObject(state.claude);
const mcpServer = asObject(claude.mcpServer);
if (!hasOnlyKeys(claude, [
'addedWebFetchDeny',
'configExisted',
'configPath',
'denyExisted',
'mcpServer',
'mcpServersExisted',
'permissionsExisted',
'settingsExisted',
'settingsPath',
]) ||
typeof claude.addedWebFetchDeny !== 'boolean' ||
typeof claude.configExisted !== 'boolean' ||
claude.configPath !== claudeConfigPath(options) ||
typeof claude.denyExisted !== 'boolean' ||
typeof claude.mcpServersExisted !== 'boolean' ||
typeof claude.permissionsExisted !== 'boolean' ||
typeof claude.settingsExisted !== 'boolean' ||
claude.settingsPath !== claudeSettingsPath(options) ||
!hasOnlyKeys(mcpServer, ['type', 'command', 'args']) ||
mcpServer.type !== 'stdio' ||
!boundedString(mcpServer.command, 8 * 1024) ||
!Array.isArray(mcpServer.args) ||
mcpServer.args.length !== 1 ||
mcpServer.args[0] !== '--fetch-mcp') {
return false;
}
}
return true;
}
async function readState(options) {
const source = await readBounded(statePath(options));
if (source === undefined)
return { protocol: STATE_PROTOCOL };
try {
const value = JSON.parse(source);
if (!validState(value, options))
throw new Error();
return value;
}
catch {
throw new Error('Panerelay Agent fetch integration state is invalid');
}
}
async function saveState(options, state) {
if (!state.codex && !state.claude) {
await rm(statePath(options), { force: true });
return;
}
await writeProtected(statePath(options), `${JSON.stringify(state, null, 2)}\n`, {
protectDirectory: true,
});
}
function codexMcpBlock(launchPath) {
return [
CODEX_MCP_START,
'[mcp_servers.panerelay_fetch]',
`command = ${JSON.stringify(launchPath)}`,
`args = [${JSON.stringify('--fetch-mcp')}]`,
CODEX_MCP_END,
].join('\n');
}
function blockRange(source, start, end) {
const first = source.indexOf(start);
if (first < 0)
return null;
const last = source.indexOf(end, first + start.length);
if (last < 0 || source.indexOf(start, first + start.length) >= 0) {
throw new Error('Panerelay-managed Codex configuration markers are invalid');
}
return [first, last + end.length];
}
function replaceManagedBlock(source, previous, next) {
const range = blockRange(source, CODEX_MCP_START, CODEX_MCP_END);
if (!range) {
if (previous)
throw new Error('Panerelay-managed Codex MCP configuration was removed');
if (/^\s*\[mcp_servers\.panerelay_fetch\]\s*$/m.test(source)) {
throw new Error('Codex already has an unmanaged panerelay_fetch MCP server');
}
return `${source.trimEnd()}${source.trim() ? '\n\n' : ''}${next}\n`;
}
const current = source.slice(range[0], range[1]);
if (!previous || current !== previous) {
throw new Error('Panerelay-managed Codex MCP configuration was modified');
}
return `${source.slice(0, range[0])}${next}${source.slice(range[1])}`;
}
function installCodexWebSearch(source, previous) {
const managedLine = `web_search = false ${CODEX_WEB_SEARCH_MARKER}`;
if (previous) {
if (previous.mode === 'created-table') {
const range = blockRange(source, CODEX_TOOLS_START, CODEX_TOOLS_END);
if (!range ||
source.slice(range[0], range[1]) !==
[CODEX_TOOLS_START, '[tools]', managedLine, CODEX_TOOLS_END].join('\n')) {
throw new Error('Panerelay-managed Codex WebSearch configuration was modified');
}
return { source, state: previous };
}
if (!source.includes(managedLine)) {
throw new Error('Panerelay-managed Codex WebSearch configuration was modified');
}
return { source, state: previous };
}
const section = /^\s*\[tools\]\s*$/m.exec(source);
if (!section || section.index === undefined) {
const block = [CODEX_TOOLS_START, '[tools]', managedLine, CODEX_TOOLS_END].join('\n');
return {
source: `${source.trimEnd()}${source.trim() ? '\n\n' : ''}${block}\n`,
state: { mode: 'created-table' },
};
}
const sectionStart = section.index + section[0].length;
const nextSection = /^\s*\[[^\]]+\]\s*$/gm;
nextSection.lastIndex = sectionStart;
const next = nextSection.exec(source);
const sectionEnd = next?.index ?? source.length;
const sectionBody = source.slice(sectionStart, sectionEnd);
const existing = /^([ \t]*web_search[ \t]*=.*)$/m.exec(sectionBody);
if (existing?.index !== undefined) {
const absolute = sectionStart + existing.index;
return {
source: `${source.slice(0, absolute)}${managedLine}${source.slice(absolute + existing[1].length)}`,
state: { mode: 'replaced', previousLine: existing[1] },
};
}
const insertion = `\n${CODEX_WEB_SEARCH_MARKER}\n${managedLine}`;
return {
source: `${source.slice(0, sectionEnd).trimEnd()}${insertion}\n${source.slice(sectionEnd).replace(/^\n/, '')}`,
state: { mode: 'inserted' },
};
}
function uninstallCodexWebSearch(source, state) {
const managedLine = `web_search = false ${CODEX_WEB_SEARCH_MARKER}`;
if (state.mode === 'created-table') {
const range = blockRange(source, CODEX_TOOLS_START, CODEX_TOOLS_END);
if (!range)
throw new Error('Panerelay-managed Codex WebSearch configuration is missing');
return `${source.slice(0, range[0])}${source.slice(range[1])}`.replace(/\n{3,}/g, '\n\n');
}
const index = source.indexOf(managedLine);
if (index < 0 || source.indexOf(managedLine, index + 1) >= 0) {
throw new Error('Panerelay-managed Codex WebSearch configuration was modified');
}
if (state.mode === 'replaced') {
return `${source.slice(0, index)}${state.previousLine}${source.slice(index + managedLine.length)}`;
}
const marker = `${CODEX_WEB_SEARCH_MARKER}\n${managedLine}`;
const markerIndex = source.indexOf(marker);
if (markerIndex < 0)
throw new Error('Panerelay-managed Codex WebSearch marker is missing');
return `${source.slice(0, markerIndex)}${source.slice(markerIndex + marker.length)}`.replace(/\n{3,}/g, '\n\n');
}
export async function installCodexFetchIntegration(launchPath, options = {}) {
const state = await readState(options);
const path = codexConfigPath(options);
if (state.codex && state.codex.configPath !== path) {
throw new Error('Panerelay Codex fetch integration belongs to another configuration path');
}
const existingSource = await readBounded(path);
const configExisted = state.codex?.configExisted ?? existingSource !== undefined;
let source = existingSource ?? '';
const webSearch = installCodexWebSearch(source, state.codex?.webSearch);
source = webSearch.source;
const mcpBlock = codexMcpBlock(launchPath);
source = replaceManagedBlock(source, state.codex?.mcpBlock, mcpBlock);
await writeProtected(path, `${source.trimEnd()}\n`);
state.codex = { configExisted, configPath: path, mcpBlock, webSearch: webSearch.state };
await saveState(options, state);
return path;
}
export async function uninstallCodexFetchIntegration(options = {}) {
const state = await readState(options);
if (!state.codex)
return undefined;
let source = (await readBounded(state.codex.configPath)) ?? '';
const range = blockRange(source, CODEX_MCP_START, CODEX_MCP_END);
if (!range || source.slice(range[0], range[1]) !== state.codex.mcpBlock) {
throw new Error('Panerelay-managed Codex MCP configuration was modified');
}
source = `${source.slice(0, range[0])}${source.slice(range[1])}`.replace(/\n{3,}/g, '\n\n');
source = uninstallCodexWebSearch(source, state.codex.webSearch);
if (!state.codex.configExisted && !source.trim()) {
await rm(state.codex.configPath, { force: true });
}
else {
await writeProtected(state.codex.configPath, `${source.trimEnd()}${source.trim() ? '\n' : ''}`);
}
const path = state.codex.configPath;
delete state.codex;
await saveState(options, state);
return path;
}
function sameJson(left, right) {
return JSON.stringify(left) === JSON.stringify(right);
}
export async function installClaudeFetchIntegration(launchPath, options = {}) {
const state = await readState(options);
const configPath = claudeConfigPath(options);
const settingsPath = claudeSettingsPath(options);
if (state.claude &&
(state.claude.configPath !== configPath || state.claude.settingsPath !== settingsPath)) {
throw new Error('Panerelay Claude fetch integration belongs to another configuration path');
}
const configFile = await readJsonObjectWithExistence(configPath);
const config = configFile.value;
const existingServers = config.mcpServers;
const mcpServersExisted = state.claude?.mcpServersExisted ?? existingServers !== undefined;
const mcpServers = asObject(existingServers);
const server = { type: 'stdio', command: launchPath, args: ['--fetch-mcp'] };
const current = mcpServers.panerelay_fetch;
if (state.claude) {
if (!sameJson(current, state.claude.mcpServer)) {
throw new Error('Panerelay-managed Claude MCP configuration was modified');
}
}
else if (current !== undefined) {
throw new Error('Claude already has an unmanaged panerelay_fetch MCP server');
}
mcpServers.panerelay_fetch = server;
config.mcpServers = mcpServers;
const settingsFile = await readJsonObjectWithExistence(settingsPath);
const settings = settingsFile.value;
const permissionsExisted = state.claude?.permissionsExisted ?? settings.permissions !== undefined;
const permissions = asObject(settings.permissions);
const denyExisted = state.claude?.denyExisted ?? permissions.deny !== undefined;
const deny = Array.isArray(permissions.deny)
? permissions.deny.filter((value) => typeof value === 'string')
: [];
const addedWebFetchDeny = state.claude?.addedWebFetchDeny ?? !deny.includes('WebFetch');
if (!deny.includes('WebFetch'))
deny.push('WebFetch');
permissions.deny = deny;
settings.permissions = permissions;
await writeProtected(configPath, `${JSON.stringify(config, null, 2)}\n`);
await writeProtected(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
state.claude = {
addedWebFetchDeny,
configExisted: state.claude?.configExisted ?? configFile.exists,
configPath,
denyExisted,
mcpServer: server,
mcpServersExisted,
permissionsExisted,
settingsExisted: state.claude?.settingsExisted ?? settingsFile.exists,
settingsPath,
};
await saveState(options, state);
return { configPath, settingsPath };
}
export async function uninstallClaudeFetchIntegration(options = {}) {
const state = await readState(options);
if (!state.claude)
return undefined;
const config = await readJsonObject(state.claude.configPath);
const mcpServers = asObject(config.mcpServers);
if (!sameJson(mcpServers.panerelay_fetch, state.claude.mcpServer)) {
throw new Error('Panerelay-managed Claude MCP configuration was modified');
}
delete mcpServers.panerelay_fetch;
if (!state.claude.mcpServersExisted && Object.keys(mcpServers).length === 0) {
delete config.mcpServers;
}
else {
config.mcpServers = mcpServers;
}
const settings = await readJsonObject(state.claude.settingsPath);
if (state.claude.addedWebFetchDeny) {
const permissions = asObject(settings.permissions);
const deny = Array.isArray(permissions.deny) ? permissions.deny : [];
if (deny.filter(value => value === 'WebFetch').length !== 1) {
throw new Error('Panerelay-managed Claude WebFetch policy was modified');
}
const restoredDeny = deny.filter(value => value !== 'WebFetch');
if (state.claude.denyExisted) {
permissions.deny = restoredDeny;
}
else {
delete permissions.deny;
}
if (!state.claude.permissionsExisted && Object.keys(permissions).length === 0) {
delete settings.permissions;
}
else {
settings.permissions = permissions;
}
}
if (!state.claude.configExisted && Object.keys(config).length === 0) {
await rm(state.claude.configPath, { force: true });
}
else {
await writeProtected(state.claude.configPath, `${JSON.stringify(config, null, 2)}\n`);
}
if (!state.claude.settingsExisted && Object.keys(settings).length === 0) {
await rm(state.claude.settingsPath, { force: true });
}
else {
await writeProtected(state.claude.settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
}
const result = { configPath: state.claude.configPath, settingsPath: state.claude.settingsPath };
delete state.claude;
await saveState(options, state);
return result;
}
export async function readAgentFetchIntegrationStatus(integration, options = {}) {
try {
const state = await readState(options);
if (integration === 'codex') {
if (!state.codex)
return { integration, configured: false, detail: 'Not configured' };
const source = (await readBounded(state.codex.configPath)) ?? '';
const configured = source.includes(state.codex.mcpBlock) &&
source.includes(`web_search = false ${CODEX_WEB_SEARCH_MARKER}`);
return {
integration,
configured,
detail: configured ? state.codex.configPath : 'Managed Codex configuration is incomplete',
};
}
if (!state.claude)
return { integration, configured: false, detail: 'Not configured' };
const config = await readJsonObject(state.claude.configPath);
const settings = await readJsonObject(state.claude.settingsPath);
const configured = sameJson(asObject(config.mcpServers).panerelay_fetch, state.claude.mcpServer) &&
Array.isArray(asObject(settings.permissions).deny) &&
asObject(settings.permissions).deny.includes('WebFetch');
return {
integration,
configured,
detail: configured
? `${state.claude.configPath}; ${state.claude.settingsPath}`
: 'Managed Claude configuration is incomplete',
};
}
catch (error) {
return {
integration,
configured: false,
detail: error instanceof Error ? error.message : String(error),
};
}
}
import { type FetchAdapterRegistration } from '@panerelay/protocol';
import { type FetchAdapterRegistryOptions } from '@panerelay/cli';
import { type GitHubResolutionOptions } from './github-source.js';
export interface FetchAdapterInstallOptions extends FetchAdapterRegistryOptions, GitHubResolutionOptions {
builtinSources?: Record<string, string>;
}
export type FetchAdapterRemoveOptions = FetchAdapterRegistryOptions;
export declare function builtinFetchAdapterIds(): string[];
export declare function installFetchAdapters(sources: string[], options?: FetchAdapterInstallOptions): Promise<FetchAdapterRegistration[]>;
export declare function listFetchAdapters(options?: FetchAdapterRegistryOptions): Promise<FetchAdapterRegistration[]>;
export declare function removeFetchAdapters(ids: string[] | 'all', options?: FetchAdapterRemoveOptions): Promise<string[]>;
//# sourceMappingURL=fetch-adapters.d.ts.map
{"version":3,"file":"fetch-adapters.d.ts","sourceRoot":"","sources":["../src/fetch-adapters.ts"],"names":[],"mappings":"AAgBA,OAAO,EAKL,KAAK,wBAAwB,EAG9B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAIL,KAAK,2BAA2B,EACjC,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,0BACf,SAAQ,2BAA2B,EAAE,uBAAuB;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED,MAAM,MAAM,yBAAyB,GAAG,2BAA2B,CAAC;AAyBpE,wBAAgB,sBAAsB,IAAI,MAAM,EAAE,CAEjD;AAgUD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAQrC;AAED,wBAAsB,iBAAiB,CACrC,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAErC;AAED,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,MAAM,EAAE,GAAG,KAAK,EACrB,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,MAAM,EAAE,CAAC,CA6BnB"}
import { createHash, randomBytes } from 'node:crypto';
import { chmod, copyFile, lstat, mkdir, mkdtemp, readFile, readdir, rename, rm, stat, writeFile, } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { dirname, isAbsolute, join, resolve, sep } from 'node:path';
import { PANERELAY_FETCH_ADAPTER_MAX_ARTIFACT_BYTES, PANERELAY_FETCH_ADAPTER_REGISTRY_PROTOCOL, isFetchAdapterManifest, } from '@panerelay/protocol';
import { fetchAdapterDataDirectory, fetchAdapterRegistryPath, readFetchAdapterRegistry, } from '@panerelay/cli';
import { builtinSiteSources } from '@panerelay/sites';
import { buildSite } from '@panerelay/site-kit';
import { parseGitHubSource, resolveGitHubSource, } from './github-source.js';
function packagedBuiltinSources() {
return builtinSiteSources();
}
export function builtinFetchAdapterIds() {
return Object.keys(packagedBuiltinSources()).sort();
}
function isPosix() {
return process.platform !== 'win32';
}
async function protectedDirectory(path) {
await mkdir(path, { recursive: true, mode: 0o700 });
if (isPosix())
await chmod(path, 0o700);
}
async function protectedFile(path) {
if (isPosix())
await chmod(path, 0o600);
}
async function regularSourceFile(path, label) {
const metadata = await lstat(path);
if (!metadata.isFile() || metadata.isSymbolicLink())
throw new Error(`${label} is not a regular file`);
if (metadata.size > PANERELAY_FETCH_ADAPTER_MAX_ARTIFACT_BYTES) {
throw new Error(`${label} exceeds the adapter artifact limit`);
}
}
async function existingDirectory(value) {
const path = resolve(value);
try {
if ((await stat(path)).isDirectory())
return path;
}
catch {
return undefined;
}
return undefined;
}
async function resolveSource(value, builtins, options, cleanups) {
if (value === 'all') {
return Object.entries(builtins).map(([id, directory]) => ({
directory,
provenance: { kind: 'builtin', id },
}));
}
if (builtins[value]) {
return [{ directory: builtins[value], provenance: { kind: 'builtin', id: value } }];
}
const local = await existingDirectory(value);
if (local)
return [{ directory: local, provenance: { kind: 'local', path: local } }];
if (isAbsolute(value) ||
value.startsWith('./') ||
value.startsWith('../') ||
value.includes('\\')) {
const path = resolve(value);
return [{ directory: path, provenance: { kind: 'local', path } }];
}
const github = parseGitHubSource(value);
if (github) {
const resolvedSource = await resolveGitHubSource(github, options);
cleanups.push(resolvedSource.cleanup);
return [{ directory: resolvedSource.directory, provenance: resolvedSource.provenance }];
}
const pathLike = value.includes('/') || value.includes('\\') || isAbsolute(value);
if (pathLike) {
const path = resolve(value);
return [{ directory: path, provenance: { kind: 'local', path } }];
}
throw new Error(`Unknown fetch adapter source: ${value}`);
}
async function validateSource(sourceDirectory) {
const directoryMetadata = await lstat(sourceDirectory);
if (!directoryMetadata.isDirectory() || directoryMetadata.isSymbolicLink()) {
throw new Error(`Fetch adapter source is not a regular directory: ${sourceDirectory}`);
}
const manifestPath = join(sourceDirectory, 'panerelay-fetch-adapter.json');
await regularSourceFile(manifestPath, 'Fetch adapter manifest');
let manifest;
try {
manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
}
catch {
throw new Error(`Fetch adapter manifest is not valid JSON: ${manifestPath}`);
}
if (!isFetchAdapterManifest(manifest)) {
throw new Error(`Fetch adapter manifest is invalid: ${manifestPath}`);
}
const entryPath = resolve(sourceDirectory, manifest.entry);
if (dirname(entryPath) !== resolve(sourceDirectory)) {
throw new Error(`Fetch adapter ${manifest.id} entry must be directly inside its source directory`);
}
const sourceEntries = (await readdir(sourceDirectory)).sort();
const expectedEntries = ['panerelay-fetch-adapter.json', manifest.entry].sort();
if (sourceEntries.length !== expectedEntries.length ||
!sourceEntries.every((entry, index) => entry === expectedEntries[index])) {
throw new Error(`Fetch adapter ${manifest.id} source must contain exactly its manifest and entry`);
}
await regularSourceFile(entryPath, `Fetch adapter ${manifest.id} entry`);
return {
entryPath,
manifest,
provenance: { kind: 'local', path: resolve(sourceDirectory) },
};
}
async function prepareSource(candidate, cleanups) {
let entries;
try {
entries = await readdir(candidate.directory);
}
catch {
throw new Error(`Fetch adapter source directory is unavailable: ${candidate.directory}`);
}
let directory = candidate.directory;
const strictTwoFile = entries.includes('panerelay-fetch-adapter.json');
if (!strictTwoFile) {
if (!entries.includes('panerelay.site.ts')) {
throw new Error(`Fetch adapter source must contain panerelay.site.ts or a strict two-file adapter: ${candidate.directory}`);
}
const temporaryRoot = await mkdtemp(join(tmpdir(), 'panerelay-site-build-'));
cleanups.push(() => rm(temporaryRoot, { force: true, recursive: true }));
directory = join(temporaryRoot, 'output');
await buildSite(candidate.directory, { outDirectory: directory });
}
const validated = await validateSource(directory);
const provenance = candidate.provenance.kind === 'builtin'
? {
kind: 'builtin',
id: candidate.provenance.id,
version: validated.manifest.version,
}
: candidate.provenance;
return { ...validated, provenance };
}
async function prepareBatch(sources, options) {
const cleanups = [];
try {
const builtins = options.builtinSources ?? packagedBuiltinSources();
const candidates = [];
for (const source of sources) {
candidates.push(...(await resolveSource(source, builtins, options, cleanups)));
}
const unique = candidates.filter((candidate, index) => candidates.findIndex(value => value.directory === candidate.directory &&
JSON.stringify(value.provenance) === JSON.stringify(candidate.provenance)) === index);
const validated = [];
for (const candidate of unique)
validated.push(await prepareSource(candidate, cleanups));
return {
validated,
cleanup: async () => {
for (const cleanup of cleanups.reverse())
await cleanup();
},
};
}
catch (error) {
for (const cleanup of cleanups.reverse())
await cleanup().catch(() => undefined);
throw error;
}
}
async function writeProtectedRegistry(registry, options) {
const path = fetchAdapterRegistryPath(options);
const temporary = `${path}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;
await writeFile(temporary, `${JSON.stringify(registry, null, 2)}\n`, { mode: 0o600 });
await protectedFile(temporary);
await rename(temporary, path);
}
function expectedVersionDirectory(registration, options) {
return join(fetchAdapterDataDirectory(options), registration.manifest.id, registration.manifest.version);
}
async function removeOtherVersions(registration, options) {
const idDirectory = join(fetchAdapterDataDirectory(options), registration.manifest.id);
let entries;
try {
entries = await readdir(idDirectory);
}
catch {
return;
}
for (const entry of entries) {
if (entry === registration.manifest.version)
continue;
const target = join(idDirectory, entry);
if (dirname(target) !== idDirectory)
continue;
await rm(target, { recursive: true, force: true });
}
}
async function installValidatedFetchAdapters(validated, options = {}) {
const ids = validated.map(source => source.manifest.id);
if (new Set(ids).size !== ids.length)
throw new Error('A fetch adapter batch contains duplicate IDs');
const registryDirectory = fetchAdapterDataDirectory(options);
await protectedDirectory(registryDirectory);
const current = await readFetchAdapterRegistry(options);
const stagingRoot = join(registryDirectory, `.install-${process.pid}-${randomBytes(6).toString('hex')}`);
await protectedDirectory(stagingRoot);
const staged = new Map();
try {
for (const source of validated) {
const directory = join(stagingRoot, source.manifest.id, source.manifest.version);
await protectedDirectory(directory);
const entry = join(directory, source.manifest.entry);
await copyFile(source.entryPath, entry);
await protectedFile(entry);
await writeFile(join(directory, 'panerelay-fetch-adapter.json'), `${JSON.stringify(source.manifest, null, 2)}\n`, { mode: 0o600 });
const sha256 = createHash('sha256')
.update(await readFile(entry))
.digest('hex');
staged.set(source.manifest.id, {
directory,
registration: {
manifest: source.manifest,
executablePath: join(registryDirectory, source.manifest.id, source.manifest.version, source.manifest.entry),
sha256,
source: source.provenance,
},
});
}
const backups = new Map();
const installedTargets = [];
try {
for (const source of validated) {
const target = join(registryDirectory, source.manifest.id, source.manifest.version);
await protectedDirectory(dirname(target));
try {
await lstat(target);
const backup = `${target}.backup-${randomBytes(6).toString('hex')}`;
await rename(target, backup);
backups.set(target, backup);
}
catch (error) {
if (error.code !== 'ENOENT')
throw error;
}
await rename(staged.get(source.manifest.id).directory, target);
installedTargets.push(target);
}
const replacements = new Map([...staged.values()].map(value => [value.registration.manifest.id, value.registration]));
const registry = {
protocol: PANERELAY_FETCH_ADAPTER_REGISTRY_PROTOCOL,
adapters: [
...current.adapters.filter(adapter => !replacements.has(adapter.manifest.id)),
...replacements.values(),
].sort((left, right) => left.manifest.id.localeCompare(right.manifest.id)),
};
await writeProtectedRegistry(registry, options);
await Promise.all([...backups.values()].map(path => rm(path, { recursive: true, force: true })));
await Promise.all([...replacements.values()].map(value => removeOtherVersions(value, options)));
return [...replacements.values()];
}
catch (error) {
for (const target of installedTargets.reverse())
await rm(target, { recursive: true, force: true });
for (const [target, backup] of [...backups.entries()].reverse()) {
await rename(backup, target).catch(() => undefined);
}
throw error;
}
}
finally {
await rm(stagingRoot, { recursive: true, force: true });
}
}
export async function installFetchAdapters(sources, options = {}) {
if (sources.length === 0)
throw new Error('At least one fetch adapter source is required');
const prepared = await prepareBatch(sources, options);
try {
return await installValidatedFetchAdapters(prepared.validated, options);
}
finally {
await prepared.cleanup();
}
}
export async function listFetchAdapters(options = {}) {
return (await readFetchAdapterRegistry(options)).adapters;
}
export async function removeFetchAdapters(ids, options = {}) {
const current = await readFetchAdapterRegistry(options);
const selected = ids === 'all' ? current.adapters.map(adapter => adapter.manifest.id) : [...new Set(ids)];
if (selected.length === 0) {
if (ids === 'all')
return [];
throw new Error('At least one fetch adapter ID is required');
}
const selectedSet = new Set(selected);
const removed = current.adapters.filter(adapter => selectedSet.has(adapter.manifest.id));
const missing = selected.filter(id => !removed.some(adapter => adapter.manifest.id === id));
if (missing.length > 0)
throw new Error(`Fetch adapter is not installed: ${missing.join(', ')}`);
const registry = {
protocol: PANERELAY_FETCH_ADAPTER_REGISTRY_PROTOCOL,
adapters: current.adapters.filter(adapter => !selectedSet.has(adapter.manifest.id)),
};
await writeProtectedRegistry(registry, options);
for (const registration of removed) {
const target = expectedVersionDirectory(registration, options);
const expectedPrefix = `${join(fetchAdapterDataDirectory(options), registration.manifest.id)}${sep}`;
if (!`${target}${sep}`.startsWith(expectedPrefix)) {
throw new Error(`Refusing to remove fetch adapter outside protected storage: ${registration.manifest.id}`);
}
await rm(target, { recursive: true, force: true });
await rm(dirname(target), { recursive: false }).catch(() => undefined);
}
return removed.map(adapter => adapter.manifest.id);
}
import type { FetchAdapterSourceProvenance } from '@panerelay/protocol';
export type GitHubFetch = (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>;
export interface GitHubSource {
repository: string;
ref?: string;
subdirectory?: string;
}
export interface GitHubResolutionOptions {
fetch?: GitHubFetch;
apiBaseUrl?: string;
codeloadBaseUrl?: string;
}
export interface ResolvedGitHubSource {
cleanup(): Promise<void>;
directory: string;
provenance: FetchAdapterSourceProvenance & {
kind: 'github';
};
}
export declare function parseGitHubSource(value: string): GitHubSource | undefined;
export declare function resolveGitHubSource(source: GitHubSource, options?: GitHubResolutionOptions): Promise<ResolvedGitHubSource>;
//# sourceMappingURL=github-source.d.ts.map
{"version":3,"file":"github-source.d.ts","sourceRoot":"","sources":["../src/github-source.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAYxE,MAAM,MAAM,WAAW,GAAG,CACxB,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,UAAU,CAAC,OAAO,EACxC,IAAI,CAAC,EAAE,WAAW,KACf,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEvB,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,4BAA4B,GAAG;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC;CAC/D;AA+DD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAqCzE;AA8LD,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,oBAAoB,CAAC,CAiE/B"}
import { createGunzip } from 'node:zlib';
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { dirname, join, resolve, sep } from 'node:path';
import { Readable } from 'node:stream';
const MAX_REDIRECTS = 3;
const REQUEST_TIMEOUT_MS = 30_000;
const MAX_COMPRESSED_BYTES = 16 * 1024 * 1024;
const MAX_EXPANDED_BYTES = 64 * 1024 * 1024;
const MAX_ARCHIVE_ENTRIES = 2_048;
const MAX_ARCHIVE_FILE_BYTES = 8 * 1024 * 1024;
const MAX_ARCHIVE_PATH_DEPTH = 32;
const MAX_REF_BYTES = 256;
const MAX_SUBDIRECTORY_BYTES = 4 * 1024;
function bounded(value, maximum) {
return value.length > 0 && Buffer.byteLength(value) <= maximum && !/\p{Cc}/u.test(value);
}
function validRepository(value) {
const match = /^([0-9A-Za-z](?:[0-9A-Za-z-]{0,37}[0-9A-Za-z])?)\/([0-9A-Za-z._-]{1,100})$/.exec(value);
return !!match && match[2] !== '.' && match[2] !== '..' && !match[2]?.endsWith('.git');
}
function validRef(value) {
return (bounded(value, MAX_REF_BYTES) &&
/^[0-9A-Za-z][0-9A-Za-z._/-]*$/.test(value) &&
!value.includes('..') &&
!value.includes('//') &&
!value.endsWith('/') &&
!value.endsWith('.') &&
value.split('/').every(segment => segment !== '.' && segment !== '..'));
}
function validSubdirectory(value) {
if (!bounded(value, MAX_SUBDIRECTORY_BYTES) || value.startsWith('/') || value.endsWith('/')) {
return false;
}
if (value.includes('\\'))
return false;
const segments = value.split('/');
return (segments.length <= MAX_ARCHIVE_PATH_DEPTH &&
segments.every(segment => segment !== '' && segment !== '.' && segment !== '..' && Buffer.byteLength(segment) <= 255));
}
function parseSelection(repository, suffix, subdirectory) {
let selectedRepository = repository;
let ref;
const at = suffix.indexOf('@');
if (at >= 0) {
if (suffix.slice(0, at))
throw new Error('GitHub source suffix is malformed');
ref = suffix.slice(at + 1);
}
else if (suffix) {
throw new Error('GitHub source suffix is malformed');
}
selectedRepository = selectedRepository.replace(/\.git$/i, '');
if (!validRepository(selectedRepository))
throw new Error('GitHub repository is invalid');
if (ref !== undefined && !validRef(ref))
throw new Error('GitHub ref is invalid');
if (subdirectory !== undefined && !validSubdirectory(subdirectory)) {
throw new Error('GitHub source subdirectory is invalid');
}
return {
repository: selectedRepository,
...(ref ? { ref } : {}),
...(subdirectory ? { subdirectory } : {}),
};
}
export function parseGitHubSource(value) {
const explicit = value.startsWith('github:');
const input = explicit ? value.slice('github:'.length) : value;
if (/^https?:\/\//i.test(input)) {
let url;
try {
url = new URL(input);
}
catch {
throw new Error('GitHub source URL is invalid');
}
if (url.protocol !== 'https:' || url.hostname.toLowerCase() !== 'github.com') {
if (explicit)
throw new Error('GitHub source must use https://github.com');
return undefined;
}
if (url.username || url.password || url.hash)
throw new Error('GitHub source URL is unsafe');
const segments = url.pathname.split('/').filter(Boolean);
if (segments.length !== 2)
throw new Error('GitHub source URL must identify one repository');
const allowedQueries = new Set(['ref', 'path']);
if ([...url.searchParams.keys()].some(key => !allowedQueries.has(key))) {
throw new Error('GitHub source URL contains an unsupported query');
}
const refs = url.searchParams.getAll('ref');
const paths = url.searchParams.getAll('path');
if (refs.length > 1 || paths.length > 1)
throw new Error('GitHub source URL repeats a query');
const suffix = refs[0] ? `@${refs[0]}` : '';
return parseSelection(`${segments[0]}/${segments[1]}`, suffix, paths[0] || undefined);
}
const hash = input.indexOf('#');
const beforePath = hash >= 0 ? input.slice(0, hash) : input;
const subdirectory = hash >= 0 ? input.slice(hash + 1) : undefined;
const match = /^([^/@]+\/[^/@]+)(.*)$/.exec(beforePath);
if (!match) {
if (explicit)
throw new Error('GitHub source shorthand is invalid');
return undefined;
}
return parseSelection(match[1], match[2] ?? '', subdirectory);
}
function safeArchivePath(value) {
if (!value || value.startsWith('/') || value.includes('\\') || /\p{Cc}/u.test(value)) {
throw new Error('GitHub archive contains an unsafe path');
}
const segments = value.split('/').filter((segment, index, values) => {
return !(index === values.length - 1 && segment === '');
});
if (segments.length < 1 ||
segments.length > MAX_ARCHIVE_PATH_DEPTH + 1 ||
segments.some(segment => segment === '' || segment === '.' || segment === '..' || Buffer.byteLength(segment) > 255)) {
throw new Error('GitHub archive contains an unsafe path');
}
return segments;
}
function tarString(block, start, length) {
const end = block.indexOf(0, start);
return block
.subarray(start, end >= start && end < start + length ? end : start + length)
.toString('utf8');
}
function tarNumber(block, start, length) {
const value = tarString(block, start, length).trim();
if (!/^[0-7]+$/.test(value))
throw new Error('GitHub archive has invalid numeric metadata');
const parsed = Number.parseInt(value, 8);
if (!Number.isSafeInteger(parsed) || parsed < 0)
throw new Error('GitHub archive size is invalid');
return parsed;
}
function verifyTarChecksum(block) {
const expected = tarNumber(block, 148, 8);
let actual = 0;
for (let index = 0; index < block.length; index += 1) {
actual += index >= 148 && index < 156 ? 32 : block[index];
}
if (actual !== expected)
throw new Error('GitHub archive checksum is invalid');
}
async function gunzipBounded(compressed) {
const gunzip = createGunzip();
const chunks = [];
let length = 0;
Readable.from(compressed).pipe(gunzip);
for await (const chunk of gunzip) {
const value = Buffer.from(chunk);
length += value.length;
if (length > MAX_EXPANDED_BYTES) {
gunzip.destroy();
throw new Error('GitHub archive exceeds the expanded byte limit');
}
chunks.push(value);
}
return Buffer.concat(chunks);
}
async function extractArchive(compressed, output) {
const archive = await gunzipBounded(compressed);
let offset = 0;
let entries = 0;
let rootSegment;
const written = new Set();
while (offset + 512 <= archive.length) {
const header = archive.subarray(offset, offset + 512);
offset += 512;
if (header.every(byte => byte === 0))
break;
entries += 1;
if (entries > MAX_ARCHIVE_ENTRIES)
throw new Error('GitHub archive has too many entries');
verifyTarChecksum(header);
const prefix = tarString(header, 345, 155);
const name = tarString(header, 0, 100);
const archivePath = prefix ? `${prefix}/${name}` : name;
const segments = safeArchivePath(archivePath);
rootSegment ??= segments[0];
if (segments[0] !== rootSegment)
throw new Error('GitHub archive has multiple roots');
const relativeSegments = segments.slice(1);
const size = tarNumber(header, 124, 12);
if (size > MAX_ARCHIVE_FILE_BYTES)
throw new Error('GitHub archive contains an oversized file');
const paddedSize = Math.ceil(size / 512) * 512;
if (offset + paddedSize > archive.length)
throw new Error('GitHub archive is truncated');
const type = String.fromCharCode(header[156] ?? 0);
if (type !== '\0' && type !== '0' && type !== '5') {
throw new Error('GitHub archive contains a link or unsupported file type');
}
if (relativeSegments.length > 0) {
const target = resolve(output, ...relativeSegments);
const expectedPrefix = `${resolve(output)}${sep}`;
if (!`${target}${type === '5' ? sep : ''}`.startsWith(expectedPrefix)) {
throw new Error('GitHub archive path escapes extraction');
}
if (written.has(target))
throw new Error('GitHub archive contains duplicate paths');
written.add(target);
if (type === '5') {
await mkdir(target, { recursive: true, mode: 0o700 });
}
else {
await mkdir(dirname(target), { recursive: true, mode: 0o700 });
await writeFile(target, archive.subarray(offset, offset + size), {
mode: 0o600,
flag: 'wx',
});
}
}
offset += paddedSize;
}
if (entries === 0 || !rootSegment)
throw new Error('GitHub archive is empty');
}
async function responseBytes(response) {
if (!response.body)
throw new Error('GitHub archive response has no body');
const chunks = [];
let length = 0;
for await (const chunk of response.body) {
const value = Buffer.from(chunk);
length += value.length;
if (length > MAX_COMPRESSED_BYTES)
throw new Error('GitHub archive exceeds the download limit');
chunks.push(value);
}
return Buffer.concat(chunks);
}
async function request(url, fetchImplementation, accept) {
let current = new URL(url);
for (let redirects = 0; redirects <= MAX_REDIRECTS; redirects += 1) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
timer.unref();
let response;
try {
response = await fetchImplementation(current, {
headers: { accept, 'user-agent': 'panerelay-setup' },
redirect: 'manual',
signal: controller.signal,
});
}
catch (error) {
throw new Error(`GitHub request failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
}
finally {
clearTimeout(timer);
}
if (![301, 302, 303, 307, 308].includes(response.status))
return response;
if (redirects === MAX_REDIRECTS)
throw new Error('GitHub request exceeded the redirect limit');
const location = response.headers.get('location');
if (!location)
throw new Error('GitHub redirect is missing its location');
const next = new URL(location, current);
if (next.protocol !== 'https:' || next.username || next.password) {
throw new Error('GitHub redirect is unsafe');
}
current = next;
}
throw new Error('GitHub request failed');
}
async function githubJson(url, fetchImplementation, repository) {
const response = await request(url, fetchImplementation, 'application/vnd.github+json');
if (!response.ok) {
if (response.status === 404) {
throw new Error(`GitHub repository or ref is unavailable; private repositories are unsupported: ${repository}`);
}
const remaining = response.headers.get('x-ratelimit-remaining');
const reset = response.headers.get('x-ratelimit-reset');
const rate = remaining === '0' ? ` (rate limit resets at ${reset ?? 'unknown'})` : '';
throw new Error(`GitHub API returned HTTP ${response.status}${rate}: ${repository}`);
}
const value = (await response.json());
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`GitHub API returned malformed metadata: ${repository}`);
}
return value;
}
export async function resolveGitHubSource(source, options = {}) {
const fetchImplementation = options.fetch ?? fetch;
const apiBase = (options.apiBaseUrl ?? 'https://api.github.com').replace(/\/$/, '');
const codeloadBase = (options.codeloadBaseUrl ?? 'https://codeload.github.com').replace(/\/$/, '');
const [owner, repositoryName] = source.repository.split('/');
let ref = source.ref;
if (!ref) {
const repository = await githubJson(`${apiBase}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repositoryName)}`, fetchImplementation, source.repository);
if (typeof repository.default_branch !== 'string' || !validRef(repository.default_branch)) {
throw new Error(`GitHub repository default branch is invalid: ${source.repository}`);
}
ref = repository.default_branch;
}
const commit = await githubJson(`${apiBase}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repositoryName)}/commits/${encodeURIComponent(ref)}`, fetchImplementation, source.repository);
if (typeof commit.sha !== 'string' || !/^[0-9a-f]{40}$/.test(commit.sha)) {
throw new Error(`GitHub commit metadata is invalid: ${source.repository}`);
}
const archiveResponse = await request(`${codeloadBase}/${encodeURIComponent(owner)}/${encodeURIComponent(repositoryName)}/tar.gz/${commit.sha}`, fetchImplementation, 'application/octet-stream');
if (!archiveResponse.ok) {
throw new Error(`GitHub archive returned HTTP ${archiveResponse.status}: ${source.repository}`);
}
const temporaryRoot = await mkdtemp(join(tmpdir(), 'panerelay-github-adapter-'));
try {
const repositoryRoot = join(temporaryRoot, 'repository');
await mkdir(repositoryRoot, { mode: 0o700 });
await extractArchive(await responseBytes(archiveResponse), repositoryRoot);
const directory = source.subdirectory
? resolve(repositoryRoot, ...source.subdirectory.split('/'))
: repositoryRoot;
if (!`${directory}${sep}`.startsWith(`${repositoryRoot}${sep}`) &&
directory !== repositoryRoot) {
throw new Error('GitHub source subdirectory escapes the repository');
}
return {
directory,
provenance: {
kind: 'github',
repository: source.repository,
commit: commit.sha,
...(source.ref ? { ref: source.ref } : {}),
...(source.subdirectory ? { subdirectory: source.subdirectory } : {}),
},
cleanup: () => rm(temporaryRoot, { force: true, recursive: true }),
};
}
catch (error) {
await rm(temporaryRoot, { force: true, recursive: true });
throw error;
}
}
+11
-1

@@ -6,6 +6,11 @@ #!/usr/bin/env node

import { setupPanerelay, uninstallPanerelay } from './lifecycle.js';
export type SetupOperation = 'setup' | 'doctor' | 'uninstall';
import { installFetchAdapters, listFetchAdapters, removeFetchAdapters } from './fetch-adapters.js';
export type SetupOperation = 'setup' | 'doctor' | 'uninstall' | 'add' | 'remove' | 'adapters';
export interface ParsedSetupArgs {
agentBrowser: boolean;
browserUse: boolean;
claudeFetch?: boolean;
codexFetch?: boolean;
removeClaudeFetch?: boolean;
removeCodexFetch?: boolean;
playwright: boolean;

@@ -19,2 +24,4 @@ extensionId?: string;

yes: boolean;
adapterItems?: string[];
adapterAll?: boolean;
}

@@ -51,4 +58,7 @@ interface SetupIntegrationPrompt {

interactive?: () => boolean;
installFetchAdapters?: typeof installFetchAdapters;
listFetchAdapters?: typeof listFetchAdapters;
readInteractiveState?: typeof readInteractiveSetupState;
selectIntegrations?: SetupSelectIntegrations;
removeFetchAdapters?: typeof removeFetchAdapters;
setup?: typeof setupPanerelay;

@@ -55,0 +65,0 @@ systemLocale?: string;

+1
-1

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

{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAmBA,OAAO,EAAE,eAAe,EAAqB,MAAM,aAAa,CAAC;AACjE,OAAO,EAA6C,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5F,OAAO,EACL,yBAAyB,EAEzB,KAAK,gBAAgB,EACtB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAA8B,MAAM,gBAAgB,CAAC;AAKhG,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE9D,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,SAAS,EAAE,cAAc,CAAC;IAC1B,GAAG,EAAE,OAAO,CAAC;CACd;AAmBD,UAAU,sBAAsB;IAC9B,aAAa,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,aAAa,CAAC;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,gBAAgB,CAAC;KACzB,CAAC,CAAC;CACJ;AAED,UAAU,uBAAuB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,KAAK,uBAAuB,GAAG,CAC7B,MAAM,EAAE,sBAAsB,KAC3B,OAAO,CAAC,SAAS,gBAAgB,EAAE,GAAG,SAAS,CAAC,CAAC;AACtD,KAAK,YAAY,GAAG,CAAC,MAAM,EAAE,uBAAuB,KAAK,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;AAEtF,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAiCD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,eAAe,CAyF9D;AAsPD,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IAC7C,cAAc,CAAC,EAAE,YAAY,CAAC;IAC9B,mBAAmB,CAAC,EAAE,MAAM,aAAa,CAAC;IAC1C,MAAM,CAAC,EAAE,OAAO,eAAe,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;IAC5B,oBAAoB,CAAC,EAAE,OAAO,yBAAyB,CAAC;IACxD,kBAAkB,CAAC,EAAE,uBAAuB,CAAC;IAC7C,KAAK,CAAC,EAAE,OAAO,cAAc,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,kBAAkB,CAAC;CACvC;AAoID,wBAAsB,IAAI,CACxB,IAAI,GAAE,MAAM,EAA0B,EACtC,YAAY,GAAE,eAAoB,GACjC,OAAO,CAAC,MAAM,CAAC,CAwOjB"}
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}

@@ -15,2 +15,3 @@ #!/usr/bin/env node

import { setupPanerelay, uninstallPanerelay } from './lifecycle.js';
import { installFetchAdapters, listFetchAdapters, removeFetchAdapters } from './fetch-adapters.js';
const PANERELAY_CHROME_WEB_STORE_URL = 'https://chromewebstore.google.com/detail/panerelay/panplnkjlkoceaonlmpdekjphgmbggmi';

@@ -86,9 +87,57 @@ function versionRequested(argv) {

? 'uninstall'
: undefined;
: command === 'add' || command === 'remove' || command === 'adapters'
? command
: undefined;
if (!operation)
throw new Error(`Unknown command: ${command}`);
const optionStart = command === localized.argv[0] ? 1 : 0;
if (operation === 'add' || operation === 'remove' || operation === 'adapters') {
const adapterItems = [];
let adapterAll = false;
for (let index = optionStart; index < localized.argv.length; index += 1) {
const argument = localized.argv[index];
if (argument === '--all') {
if (adapterAll)
throw new Error('--all can only be provided once');
adapterAll = true;
}
else if (argument.startsWith('-')) {
throw new Error(`Unknown option: ${argument}`);
}
else {
adapterItems.push(argument);
}
}
if (operation === 'adapters' && (adapterItems.length > 0 || adapterAll)) {
throw new Error('adapters does not accept arguments');
}
if ((operation === 'add' || operation === 'remove') &&
adapterItems.length === 0 &&
!adapterAll) {
throw new Error(`${operation} requires at least one adapter or --all`);
}
if (adapterAll && adapterItems.length > 0) {
throw new Error('--all cannot be combined with adapter names or paths');
}
return {
agentBrowser: false,
browserUse: false,
playwright: false,
globalDefault: false,
help: false,
json: false,
language: localized.language,
operation,
yes: false,
...(adapterItems.length > 0 ? { adapterItems } : {}),
...(adapterAll ? { adapterAll: true } : {}),
};
}
let globalDefault = false;
let agentBrowser = false;
let browserUse = false;
let claudeFetch = false;
let codexFetch = false;
let removeClaudeFetch = false;
let removeCodexFetch = false;
let playwright = false;

@@ -106,2 +155,10 @@ let json = false;

browserUse = true;
else if (argument === '--claude-fetch')
claudeFetch = true;
else if (argument === '--codex-fetch')
codexFetch = true;
else if (argument === '--remove-claude-fetch')
removeClaudeFetch = true;
else if (argument === '--remove-codex-fetch')
removeCodexFetch = true;
else if (argument === '--playwright')

@@ -143,2 +200,14 @@ playwright = true;

}
if ((claudeFetch || codexFetch) && operation === 'uninstall') {
throw new Error('--codex-fetch and --claude-fetch are not needed with uninstall');
}
if ((removeClaudeFetch || removeCodexFetch) && operation !== 'setup') {
throw new Error('--remove-*-fetch is only available with setup');
}
if (claudeFetch && removeClaudeFetch) {
throw new Error('--claude-fetch cannot be combined with --remove-claude-fetch');
}
if (codexFetch && removeCodexFetch) {
throw new Error('--codex-fetch cannot be combined with --remove-codex-fetch');
}
if (extensionId && operation === 'uninstall') {

@@ -153,2 +222,6 @@ throw new Error('--extension-id is not available with uninstall');

browserUse,
...(claudeFetch ? { claudeFetch: true } : {}),
...(codexFetch ? { codexFetch: true } : {}),
...(removeClaudeFetch ? { removeClaudeFetch: true } : {}),
...(removeCodexFetch ? { removeCodexFetch: true } : {}),
playwright,

@@ -500,2 +573,52 @@ ...(extensionId ? { extensionId } : {}),

}
function describeAdapterSource(registration, locale) {
const source = registration.source;
if (!source)
return translate(locale, 'adapterSourceUnknown');
if (source.kind === 'builtin') {
return translate(locale, 'adapterSourceBuiltin', { id: source.id, version: source.version });
}
if (source.kind === 'local') {
return translate(locale, 'adapterSourceLocal', { path: source.path });
}
const selection = [
source.ref ? `ref=${source.ref}` : '',
source.subdirectory ? `path=${source.subdirectory}` : '',
]
.filter(Boolean)
.join(', ');
return translate(locale, 'adapterSourceGitHub', {
repository: source.repository,
commit: source.commit.slice(0, 12),
selection: selection ? ` (${selection})` : '',
});
}
function localizedAdapterError(error, locale) {
const message = error instanceof Error ? error.message : String(error);
if (locale === 'en')
return translate(locale, 'adapterError', { message });
const replacements = [
[/^Unknown fetch adapter source:/, '未知 Fetch 适配器来源:'],
[/^Fetch adapter source directory is unavailable:/, 'Fetch 适配器来源目录不可用:'],
[/^Fetch adapter source must contain /, 'Fetch 适配器来源必须包含 '],
[
/^GitHub repository or ref is unavailable; private repositories are unsupported:/,
'GitHub 仓库或引用不可用;当前不支持私有仓库:',
],
[/^GitHub repository is invalid/, 'GitHub 仓库标识无效'],
[/^GitHub ref is invalid/, 'GitHub 引用无效'],
[/^GitHub source subdirectory is invalid/, 'GitHub 来源子目录无效'],
[/^GitHub source URL is unsafe/, 'GitHub 来源 URL 不安全'],
[/^GitHub archive contains an unsafe path/, 'GitHub 压缩包包含不安全路径'],
[
/^GitHub archive contains a link or unsupported file type/,
'GitHub 压缩包包含链接或不支持的文件类型',
],
[/^GitHub archive contains an oversized file/, 'GitHub 压缩包包含超大文件'],
[/^GitHub archive /, 'GitHub 压缩包'],
[/^GitHub request /, 'GitHub 请求'],
];
const localized = replacements.reduce((value, [pattern, replacement]) => value.replace(pattern, replacement), message);
return translate(locale, 'adapterError', { message: localized });
}
export async function main(argv = process.argv.slice(2), dependencies = {}) {

@@ -527,2 +650,34 @@ if (versionRequested(argv)) {

try {
if (parsed.operation === 'adapters') {
const adapters = await (dependencies.listFetchAdapters ?? listFetchAdapters)({
environment: dependencies.environment,
});
console.log(translate(locale, 'adapterListTitle'));
if (adapters.length === 0)
console.log(translate(locale, 'adapterNone'));
else {
for (const adapter of adapters) {
console.log(` ${adapter.manifest.id}@${adapter.manifest.version} — ${adapter.manifest.description} — ${describeAdapterSource(adapter, locale)}`);
}
}
return 0;
}
if (parsed.operation === 'add') {
const sources = parsed.adapterAll ? ['all'] : (parsed.adapterItems ?? []);
console.log(translate(locale, 'adapterTrust'));
console.log(translate(locale, 'adapterAddProgress'));
const installed = await (dependencies.installFetchAdapters ?? installFetchAdapters)(sources, {
environment: dependencies.environment,
});
console.log(translate(locale, 'adapterInstalledTitle'));
for (const adapter of installed) {
console.log(` ${adapter.manifest.id}@${adapter.manifest.version} — ${describeAdapterSource(adapter, locale)}`);
}
return 0;
}
if (parsed.operation === 'remove') {
const removed = await (dependencies.removeFetchAdapters ?? removeFetchAdapters)(parsed.adapterAll ? 'all' : (parsed.adapterItems ?? []), { environment: dependencies.environment });
console.log(translate(locale, 'adapterRemoved', { adapters: removed.join(', ') }));
return 0;
}
if (parsed.operation === 'doctor') {

@@ -532,2 +687,4 @@ const report = await (dependencies.doctor ?? doctorPanerelay)({

browserUse: parsed.browserUse,
...(parsed.claudeFetch ? { claudeFetch: true } : {}),
...(parsed.codexFetch ? { codexFetch: true } : {}),
playwright: parsed.playwright,

@@ -562,2 +719,6 @@ environment: dependencies.environment,

browserUse: parsed.browserUse,
...(parsed.claudeFetch ? { claudeFetch: true } : {}),
...(parsed.codexFetch ? { codexFetch: true } : {}),
...(parsed.removeClaudeFetch ? { removeClaudeFetch: true } : {}),
...(parsed.removeCodexFetch ? { removeCodexFetch: true } : {}),
playwright: parsed.playwright,

@@ -575,2 +736,6 @@ environment: dependencies.environment,

!parsed.browserUse &&
!parsed.claudeFetch &&
!parsed.codexFetch &&
!parsed.removeClaudeFetch &&
!parsed.removeCodexFetch &&
!parsed.playwright &&

@@ -592,2 +757,6 @@ !parsed.yes &&

const selectedBrowserUse = setupOptions.browserUse === true;
const selectedClaudeFetch = setupOptions.claudeFetch === true;
const selectedCodexFetch = setupOptions.codexFetch === true;
const selectedRemoveClaudeFetch = setupOptions.removeClaudeFetch === true;
const selectedRemoveCodexFetch = setupOptions.removeCodexFetch === true;
const selectedPlaywright = setupOptions.playwright === true;

@@ -612,3 +781,9 @@ const selectedGlobalDefault = setupOptions.globalDefault === true;

: translate(locale, 'extensionCustomNextStep', { id: result.host.extensionId }));
if (selectedAgentBrowser || selectedBrowserUse || selectedPlaywright) {
if (selectedAgentBrowser ||
selectedBrowserUse ||
selectedPlaywright ||
selectedCodexFetch ||
selectedClaudeFetch ||
selectedRemoveCodexFetch ||
selectedRemoveClaudeFetch) {
console.log('');

@@ -620,2 +795,18 @@ console.log(translate(locale, 'setupGroupAutomation'));

}
if (selectedCodexFetch) {
printSetupCheck(result.codexFetchConfigPath ? 'pass' : 'fail', translate(locale, 'setupCodexFetch'), result.codexFetchConfigPath ?? translate(locale, 'setupNotFound'));
}
if (selectedClaudeFetch) {
printSetupCheck(result.claudeFetchConfigPaths ? 'pass' : 'fail', translate(locale, 'setupClaudeFetch'), result.claudeFetchConfigPaths
? `${result.claudeFetchConfigPaths.configPath}; ${result.claudeFetchConfigPaths.settingsPath}`
: translate(locale, 'setupNotFound'));
}
if (selectedRemoveCodexFetch) {
printSetupCheck('pass', translate(locale, 'setupCodexFetchRemoved'), result.removedCodexFetchConfigPath ?? translate(locale, 'setupNotConfigured'));
}
if (selectedRemoveClaudeFetch) {
printSetupCheck('pass', translate(locale, 'setupClaudeFetchRemoved'), result.removedClaudeFetchConfigPaths
? `${result.removedClaudeFetchConfigPaths.configPath}; ${result.removedClaudeFetchConfigPaths.settingsPath}`
: translate(locale, 'setupNotConfigured'));
}
const agentBrowserReady = result.agentBrowserInstallation?.supported === true;

@@ -682,3 +873,7 @@ if (selectedAgentBrowser) {

setupProgress?.error(translate(locale, 'setupProgressFailed'));
console.error(error instanceof Error ? error.message : String(error));
console.error(parsed.operation === 'add' || parsed.operation === 'remove' || parsed.operation === 'adapters'
? localizedAdapterError(error, locale)
: error instanceof Error
? error.message
: String(error));
return 1;

@@ -685,0 +880,0 @@ }

@@ -21,2 +21,4 @@ import { type CommandRunner } from '@panerelay/bridge/platform';

browserUse?: boolean;
claudeFetch?: boolean;
codexFetch?: boolean;
playwright?: boolean;

@@ -23,0 +25,0 @@ playwrightProbe?: typeof probePlaywrightInstallation;

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

{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAYA,OAAO,EAAgC,KAAK,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAgB9F,OAAO,EAKL,uBAAuB,EACxB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,6BAA6B,EAAE,MAAM,gCAAgC,CAAC;AAC/E,OAAO,EAA8B,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAIhG,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAEpD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,EAAE,EAAE,OAAO,CAAC;CACb;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,6BAA6B,CAAC;IACzD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,2BAA2B,CAAC;IACrD,eAAe,CAAC,EAAE,OAAO,uBAAuB,CAAC;IACjD,sBAAsB,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,cAAc,CAAC,EAAE,aAAa,CAAC;CAChC;AAyHD,wBAAsB,eAAe,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAsbxF"}
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAYA,OAAO,EAAgC,KAAK,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAgB9F,OAAO,EAKL,uBAAuB,EACxB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,6BAA6B,EAAE,MAAM,gCAAgC,CAAC;AAC/E,OAAO,EAA8B,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AAKhG,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAEpD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,EAAE,EAAE,OAAO,CAAC;CACb;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,6BAA6B,CAAC;IACzD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,2BAA2B,CAAC;IACrD,eAAe,CAAC,EAAE,OAAO,uBAAuB,CAAC;IACjD,sBAAsB,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAChD,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,cAAc,CAAC,EAAE,aAAa,CAAC;CAChC;AAyHD,wBAAsB,eAAe,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAmcxF"}

@@ -15,2 +15,3 @@ import { constants } from 'node:fs';

import { PLAYWRIGHT_MINIMUM_VERSION, probePlaywrightInstallation } from '@panerelay/playwright';
import { readAgentFetchIntegrationStatus } from './agent-fetch-integration.js';
const SETUP_COMMAND = 'npx --yes @panerelay/setup';

@@ -216,2 +217,15 @@ async function probeBrowserUseGateway() {

}
for (const integration of [
...(options.codexFetch ? ['codex'] : []),
...(options.claudeFetch ? ['claude'] : []),
]) {
const status = await readAgentFetchIntegrationStatus(integration, { homeDirectory: home });
checks.push({
id: `${integration}-fetch`,
label: `${integration === 'codex' ? 'Codex' : 'Claude Code'} browser fetch routing`,
status: status.configured ? 'pass' : 'fail',
detail: status.detail,
...(status.configured ? {} : { hint: `Run: ${SETUP_COMMAND} --${integration}-fetch` }),
});
}
let runtimeConfig = {};

@@ -218,0 +232,0 @@ try {

export type SupportedLocale = 'en' | 'zh-CN';
declare const englishMessages: {
readonly adapterAddProgress: "Resolving, validating, and installing fetch adapters...";
readonly adapterError: "Fetch adapter operation failed: {message}";
readonly adapterInstalledTitle: "Installed fetch adapters";
readonly adapterListTitle: "Installed fetch adapters";
readonly adapterNone: " (none)";
readonly adapterRemoved: "Removed fetch adapters: {adapters}";
readonly adapterSourceBuiltin: "built-in {id}@{version}";
readonly adapterSourceGitHub: "GitHub {repository} at {commit}{selection}";
readonly adapterSourceLocal: "local {path}";
readonly adapterSourceUnknown: "legacy installation (source not recorded)";
readonly adapterTrust: "Trust: local and GitHub adapters run as third-party code when invoked. Inspect their source before installing.";
readonly agentBrowserMissing: "Warning: agent-browser was not found.";

@@ -48,2 +59,7 @@ readonly agentBrowserUnsupported: "Warning: agent-browser {version} is unsupported. Panerelay requires 0.33.0 or newer.";

readonly setupBrowserUse: "Browser Use";
readonly setupClaudeFetch: "Claude Code browser fetch routing";
readonly setupCodexFetch: "Codex browser fetch routing";
readonly setupClaudeFetchRemoved: "Claude Code browser fetch routing removed";
readonly setupCodexFetchRemoved: "Codex browser fetch routing removed";
readonly setupNotConfigured: "Not configured";
readonly setupBrowserUseCommand: "Browser Use command:";

@@ -71,3 +87,3 @@ readonly setupPlaywright: "Playwright CLI";

readonly integrationSelectPrompt: "Select integrations (checked: install/update; unchecked: remove Panerelay integration)";
readonly help: "Panerelay Setup\n\nUsage:\n npx --yes @panerelay/setup [--agent-browser] [--browser-use] [--playwright] [--global-default] [--extension-id <id>] [--lang <language>]\n npx --yes @panerelay/setup doctor [--agent-browser] [--browser-use] [--playwright] [--global-default] [--extension-id <id>] [--json] [--lang <language>]\n npx --yes @panerelay/setup uninstall [--yes] [--lang <language>]\n\nCommands:\n setup Install the Native Host for the Extension and side panel (default)\n doctor Diagnose the local Panerelay integration\n uninstall Remove Panerelay-managed local integration files\n\nOptions:\n --agent-browser Also install or diagnose the Panerelay agent-browser integration\n --browser-use Also install or diagnose the Panerelay Browser Use integration\n --playwright Also install or diagnose the Panerelay Playwright CLI integration\n --global-default\n Set selected automation integrations as user-level defaults\n --extension-id\n Use a custom 32-character Chrome Extension ID for this installation\n --json Print a machine-readable doctor report\n --lang Use en or zh-CN instead of the system language\n --yes, -y Confirm uninstall without a prompt\n --version, -v\n Show the version\n --help, -h Show this help\n\nOptional automation integrations:\n npx --yes @panerelay/setup --agent-browser\n npx --yes @panerelay/setup --browser-use\n npx --yes @panerelay/setup --playwright";
readonly help: "Panerelay Setup\n\nUsage:\n npx --yes @panerelay/setup [--agent-browser] [--browser-use] [--playwright] [--codex-fetch|--remove-codex-fetch] [--claude-fetch|--remove-claude-fetch] [--global-default] [--extension-id <id>] [--lang <language>]\n npx --yes @panerelay/setup doctor [--agent-browser] [--browser-use] [--playwright] [--codex-fetch] [--claude-fetch] [--global-default] [--extension-id <id>] [--json] [--lang <language>]\n npx --yes @panerelay/setup uninstall [--yes] [--lang <language>]\n npx --yes @panerelay/setup add <adapter|path|github-source>... | --all\n npx --yes @panerelay/setup remove <adapter>... | --all\n npx --yes @panerelay/setup adapters\n\nCommands:\n doctor Diagnose the local Panerelay integration\n uninstall Remove Panerelay-managed local integration files\n add Install built-in, local two-file/source-form, or public GitHub fetch adapters\n remove Remove one or more installed fetch adapters\n adapters List installed fetch adapters\n\nOptions:\n --agent-browser Also install or diagnose the Panerelay agent-browser integration\n --browser-use Also install or diagnose the Panerelay Browser Use integration\n --playwright Also install or diagnose the Panerelay Playwright CLI integration\n --codex-fetch Route external Codex web access through Panerelay Fetch MCP\n --claude-fetch Route external Claude Code WebFetch through Panerelay Fetch MCP\n --remove-codex-fetch Remove Panerelay-owned external Codex fetch routing\n --remove-claude-fetch\n Remove Panerelay-owned external Claude Code fetch routing\n --global-default\n Set selected automation integrations as user-level defaults\n --extension-id\n Use a custom 32-character Chrome Extension ID for this installation\n --json Print a machine-readable doctor report\n --lang Use en or zh-CN instead of the system language\n --yes, -y Confirm uninstall without a prompt\n --version, -v\n Show the version\n --help, -h Show this help\n\nOptional automation integrations:\n npx --yes @panerelay/setup --agent-browser\n npx --yes @panerelay/setup --browser-use\n npx --yes @panerelay/setup --playwright\n npx --yes @panerelay/setup --codex-fetch\n npx --yes @panerelay/setup --claude-fetch\n npx --yes @panerelay/setup --remove-codex-fetch\n npx --yes @panerelay/setup --remove-claude-fetch\n\nOptional fetch adapters:\n npx --yes @panerelay/setup add bilibili\n npx --yes @panerelay/setup add --all\n npx --yes @panerelay/setup add ./my-site\n npx --yes @panerelay/setup add owner/repository\n npx --yes @panerelay/setup add github:owner/repository@v1.0.0#sites/example\n npx --yes @panerelay/setup add 'https://github.com/owner/repository?ref=v1.0.0&path=sites/example'\n npx --yes @panerelay/setup remove bilibili";
readonly nativeHost: "Native Host: {path}";

@@ -74,0 +90,0 @@ readonly nonInteractiveUninstall: "Non-interactive input detected. Re-run with --yes.";

@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgHX,CAAC;AAEX,KAAK,UAAU,GAAG,MAAM,OAAO,eAAe,CAAC;AAmH/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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwJX,CAAC;AAEX,KAAK,UAAU,GAAG,MAAM,OAAO,eAAe,CAAC;AA0J/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 = {
adapterAddProgress: 'Resolving, validating, and installing fetch adapters...',
adapterError: 'Fetch adapter operation failed: {message}',
adapterInstalledTitle: 'Installed fetch adapters',
adapterListTitle: 'Installed fetch adapters',
adapterNone: ' (none)',
adapterRemoved: 'Removed fetch adapters: {adapters}',
adapterSourceBuiltin: 'built-in {id}@{version}',
adapterSourceGitHub: 'GitHub {repository} at {commit}{selection}',
adapterSourceLocal: 'local {path}',
adapterSourceUnknown: 'legacy installation (source not recorded)',
adapterTrust: 'Trust: local and GitHub adapters run as third-party code when invoked. Inspect their source before installing.',
agentBrowserMissing: 'Warning: agent-browser was not found.',

@@ -48,2 +59,7 @@ agentBrowserUnsupported: 'Warning: agent-browser {version} is unsupported. Panerelay requires 0.33.0 or newer.',

setupBrowserUse: 'Browser Use',
setupClaudeFetch: 'Claude Code browser fetch routing',
setupCodexFetch: 'Codex browser fetch routing',
setupClaudeFetchRemoved: 'Claude Code browser fetch routing removed',
setupCodexFetchRemoved: 'Codex browser fetch routing removed',
setupNotConfigured: 'Not configured',
setupBrowserUseCommand: 'Browser Use command:',

@@ -74,10 +90,15 @@ setupPlaywright: 'Playwright CLI',

Usage:
npx --yes @panerelay/setup [--agent-browser] [--browser-use] [--playwright] [--global-default] [--extension-id <id>] [--lang <language>]
npx --yes @panerelay/setup doctor [--agent-browser] [--browser-use] [--playwright] [--global-default] [--extension-id <id>] [--json] [--lang <language>]
npx --yes @panerelay/setup [--agent-browser] [--browser-use] [--playwright] [--codex-fetch|--remove-codex-fetch] [--claude-fetch|--remove-claude-fetch] [--global-default] [--extension-id <id>] [--lang <language>]
npx --yes @panerelay/setup doctor [--agent-browser] [--browser-use] [--playwright] [--codex-fetch] [--claude-fetch] [--global-default] [--extension-id <id>] [--json] [--lang <language>]
npx --yes @panerelay/setup uninstall [--yes] [--lang <language>]
npx --yes @panerelay/setup add <adapter|path|github-source>... | --all
npx --yes @panerelay/setup remove <adapter>... | --all
npx --yes @panerelay/setup adapters
Commands:
setup Install the Native Host for the Extension and side panel (default)
doctor Diagnose the local Panerelay integration
uninstall Remove Panerelay-managed local integration files
add Install built-in, local two-file/source-form, or public GitHub fetch adapters
remove Remove one or more installed fetch adapters
adapters List installed fetch adapters

@@ -88,2 +109,7 @@ Options:

--playwright Also install or diagnose the Panerelay Playwright CLI integration
--codex-fetch Route external Codex web access through Panerelay Fetch MCP
--claude-fetch Route external Claude Code WebFetch through Panerelay Fetch MCP
--remove-codex-fetch Remove Panerelay-owned external Codex fetch routing
--remove-claude-fetch
Remove Panerelay-owned external Claude Code fetch routing
--global-default

@@ -103,3 +129,16 @@ Set selected automation integrations as user-level defaults

npx --yes @panerelay/setup --browser-use
npx --yes @panerelay/setup --playwright`,
npx --yes @panerelay/setup --playwright
npx --yes @panerelay/setup --codex-fetch
npx --yes @panerelay/setup --claude-fetch
npx --yes @panerelay/setup --remove-codex-fetch
npx --yes @panerelay/setup --remove-claude-fetch
Optional fetch adapters:
npx --yes @panerelay/setup add bilibili
npx --yes @panerelay/setup add --all
npx --yes @panerelay/setup add ./my-site
npx --yes @panerelay/setup add owner/repository
npx --yes @panerelay/setup add github:owner/repository@v1.0.0#sites/example
npx --yes @panerelay/setup add 'https://github.com/owner/repository?ref=v1.0.0&path=sites/example'
npx --yes @panerelay/setup remove bilibili`,
nativeHost: 'Native Host: {path}',

@@ -114,2 +153,13 @@ nonInteractiveUninstall: 'Non-interactive input detected. Re-run with --yes.',

const chineseMessages = {
adapterAddProgress: '正在解析、验证并安装 Fetch 适配器……',
adapterError: 'Fetch 适配器操作失败:{message}',
adapterInstalledTitle: '已安装 Fetch 适配器',
adapterListTitle: '已安装的 Fetch 适配器',
adapterNone: ' (无)',
adapterRemoved: '已移除 Fetch 适配器:{adapters}',
adapterSourceBuiltin: '内置 {id}@{version}',
adapterSourceGitHub: 'GitHub {repository},提交 {commit}{selection}',
adapterSourceLocal: '本地 {path}',
adapterSourceUnknown: '旧版安装(未记录来源)',
adapterTrust: '信任提示:本地和 GitHub 适配器在调用时会作为第三方代码运行,请在安装前检查源码。',
agentBrowserMissing: '警告:未找到 agent-browser。',

@@ -159,2 +209,7 @@ agentBrowserUnsupported: '警告:agent-browser {version} 不受支持。Panerelay 需要 0.33.0 或更高版本。',

setupBrowserUse: 'Browser Use',
setupClaudeFetch: 'Claude Code 浏览器 Fetch 路由',
setupCodexFetch: 'Codex 浏览器 Fetch 路由',
setupClaudeFetchRemoved: '已移除 Claude Code 浏览器 Fetch 路由',
setupCodexFetchRemoved: '已移除 Codex 浏览器 Fetch 路由',
setupNotConfigured: '未配置',
setupBrowserUseCommand: 'Browser Use 命令:',

@@ -186,10 +241,15 @@ playwrightMissing: '警告:未找到 Playwright CLI 0.1.17 或更高版本。请安装或升级上游 CLI 后,使用 --playwright 重新运行 setup。',

用法:
npx --yes @panerelay/setup [--agent-browser] [--browser-use] [--playwright] [--global-default] [--extension-id <id>] [--lang <语言>]
npx --yes @panerelay/setup doctor [--agent-browser] [--browser-use] [--playwright] [--global-default] [--extension-id <id>] [--json] [--lang <语言>]
npx --yes @panerelay/setup [--agent-browser] [--browser-use] [--playwright] [--codex-fetch|--remove-codex-fetch] [--claude-fetch|--remove-claude-fetch] [--global-default] [--extension-id <id>] [--lang <语言>]
npx --yes @panerelay/setup doctor [--agent-browser] [--browser-use] [--playwright] [--codex-fetch] [--claude-fetch] [--global-default] [--extension-id <id>] [--json] [--lang <语言>]
npx --yes @panerelay/setup uninstall [--yes] [--lang <语言>]
npx --yes @panerelay/setup add <适配器|路径|GitHub 来源>... | --all
npx --yes @panerelay/setup remove <适配器>... | --all
npx --yes @panerelay/setup adapters
命令:
setup 为 Extension 和侧边栏安装 Native Host(默认)
doctor 诊断本地 Panerelay 集成
uninstall 移除由 Panerelay 管理的本地集成文件
add 安装内置、本地两文件/源码格式或公开 GitHub Fetch 适配器
remove 移除一个或多个已安装的 Fetch 适配器
adapters 列出已安装的 Fetch 适配器

@@ -200,2 +260,7 @@ 选项:

--playwright 同时安装或诊断 Panerelay Playwright CLI 集成
--codex-fetch 将外部 Codex 网络访问路由到 Panerelay Fetch MCP
--claude-fetch 将外部 Claude Code WebFetch 路由到 Panerelay Fetch MCP
--remove-codex-fetch 移除 Panerelay 管理的外部 Codex Fetch 路由
--remove-claude-fetch
移除 Panerelay 管理的外部 Claude Code Fetch 路由
--global-default

@@ -215,3 +280,16 @@ 将选中的自动化集成设为用户级默认

npx --yes @panerelay/setup --browser-use
npx --yes @panerelay/setup --playwright`,
npx --yes @panerelay/setup --playwright
npx --yes @panerelay/setup --codex-fetch
npx --yes @panerelay/setup --claude-fetch
npx --yes @panerelay/setup --remove-codex-fetch
npx --yes @panerelay/setup --remove-claude-fetch
可选 Fetch 适配器:
npx --yes @panerelay/setup add bilibili
npx --yes @panerelay/setup add --all
npx --yes @panerelay/setup add ./my-site
npx --yes @panerelay/setup add owner/repository
npx --yes @panerelay/setup add github:owner/repository@v1.0.0#sites/example
npx --yes @panerelay/setup add 'https://github.com/owner/repository?ref=v1.0.0&path=sites/example'
npx --yes @panerelay/setup remove bilibili`,
nativeHost: 'Native Host:{path}',

@@ -218,0 +296,0 @@ nonInteractiveUninstall: '检测到非交互式输入,请添加 --yes 后重试。',

@@ -9,3 +9,7 @@ export { installBrowserUseIntegrationArtifacts, PANERELAY_BROWSER_USE_CONFIG_PROTOCOL, PANERELAY_BROWSER_USE_INTEGRATION_VERSION, posixNodeLauncherContent, resolveBrowserUseIntegrationPaths, windowsNodeLauncherContent, uninstallBrowserUseIntegrationArtifacts, } from './browser-use-integration.js';

export { installPlaywrightIntegration, PANERELAY_PLAYWRIGHT_INTEGRATION_VERSION, resolvePlaywrightIntegrationPaths, uninstallPlaywrightIntegration, } from './playwright-integration.js';
export { installClaudeFetchIntegration, installCodexFetchIntegration, readAgentFetchIntegrationStatus, uninstallClaudeFetchIntegration, uninstallCodexFetchIntegration, } from './agent-fetch-integration.js';
export type { AgentFetchIntegration, AgentFetchIntegrationOptions, AgentFetchIntegrationStatus, } from './agent-fetch-integration.js';
export { builtinFetchAdapterIds, installFetchAdapters, listFetchAdapters, removeFetchAdapters, } from './fetch-adapters.js';
export type { FetchAdapterInstallOptions, FetchAdapterRemoveOptions } from './fetch-adapters.js';
export type { PlaywrightIntegrationInstallation, PlaywrightIntegrationOptions, PlaywrightIntegrationPaths, } from './playwright-integration.js';
//# sourceMappingURL=index.d.ts.map

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

{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qCAAqC,EACrC,qCAAqC,EACrC,yCAAyC,EACzC,wBAAwB,EACxB,iCAAiC,EACjC,0BAA0B,EAC1B,uCAAuC,GACxC,MAAM,8BAA8B,CAAC;AACtC,YAAY,EACV,2BAA2B,EAC3B,iCAAiC,EACjC,gCAAgC,EAChC,0BAA0B,EAC1B,mCAAmC,EACnC,oCAAoC,EACpC,qCAAqC,GACtC,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,wBAAwB,EACxB,6BAA6B,EAC7B,yBAAyB,EACzB,qBAAqB,EACrB,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpE,YAAY,EACV,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,4BAA4B,EAC5B,wCAAwC,EACxC,iCAAiC,EACjC,8BAA8B,GAC/B,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,iCAAiC,EACjC,4BAA4B,EAC5B,0BAA0B,GAC3B,MAAM,6BAA6B,CAAC"}
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qCAAqC,EACrC,qCAAqC,EACrC,yCAAyC,EACzC,wBAAwB,EACxB,iCAAiC,EACjC,0BAA0B,EAC1B,uCAAuC,GACxC,MAAM,8BAA8B,CAAC;AACtC,YAAY,EACV,2BAA2B,EAC3B,iCAAiC,EACjC,gCAAgC,EAChC,0BAA0B,EAC1B,mCAAmC,EACnC,oCAAoC,EACpC,qCAAqC,GACtC,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,wBAAwB,EACxB,6BAA6B,EAC7B,yBAAyB,EACzB,qBAAqB,EACrB,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpE,YAAY,EACV,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,4BAA4B,EAC5B,wCAAwC,EACxC,iCAAiC,EACjC,8BAA8B,GAC/B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,6BAA6B,EAC7B,4BAA4B,EAC5B,+BAA+B,EAC/B,+BAA+B,EAC/B,8BAA8B,GAC/B,MAAM,8BAA8B,CAAC;AACtC,YAAY,EACV,qBAAqB,EACrB,4BAA4B,EAC5B,2BAA2B,GAC5B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,0BAA0B,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AACjG,YAAY,EACV,iCAAiC,EACjC,4BAA4B,EAC5B,0BAA0B,GAC3B,MAAM,6BAA6B,CAAC"}

@@ -6,1 +6,3 @@ export { installBrowserUseIntegrationArtifacts, PANERELAY_BROWSER_USE_CONFIG_PROTOCOL, PANERELAY_BROWSER_USE_INTEGRATION_VERSION, posixNodeLauncherContent, resolveBrowserUseIntegrationPaths, windowsNodeLauncherContent, uninstallBrowserUseIntegrationArtifacts, } from './browser-use-integration.js';

export { installPlaywrightIntegration, PANERELAY_PLAYWRIGHT_INTEGRATION_VERSION, resolvePlaywrightIntegrationPaths, uninstallPlaywrightIntegration, } from './playwright-integration.js';
export { installClaudeFetchIntegration, installCodexFetchIntegration, readAgentFetchIntegrationStatus, uninstallClaudeFetchIntegration, uninstallCodexFetchIntegration, } from './agent-fetch-integration.js';
export { builtinFetchAdapterIds, installFetchAdapters, listFetchAdapters, removeFetchAdapters, } from './fetch-adapters.js';

@@ -8,5 +8,10 @@ import { installNativeHost, uninstallNativeHost, type NativeHostInstallationResult } from '@panerelay/bridge/install';

import { probePlaywrightInstallation, type PlaywrightInstallation } from '@panerelay/playwright';
import { installClaudeFetchIntegration, installCodexFetchIntegration, uninstallClaudeFetchIntegration, uninstallCodexFetchIntegration } from './agent-fetch-integration.js';
export interface PanerelaySetupOptions {
agentBrowser?: boolean;
browserUse?: boolean;
claudeFetch?: boolean;
codexFetch?: boolean;
removeClaudeFetch?: boolean;
removeCodexFetch?: boolean;
playwright?: boolean;

@@ -32,2 +37,4 @@ browserUseDefault?: 'direct' | 'extension';

browserUseVersions?: BrowserUseVersions;
claudeFetchConfigPaths?: Awaited<ReturnType<typeof installClaudeFetchIntegration>>;
codexFetchConfigPath?: string;
playwrightInstallation?: PlaywrightInstallation;

@@ -37,2 +44,4 @@ playwrightIntegration?: PlaywrightIntegrationInstallation;

removedBrowserUseIntegration?: BrowserUseIntegrationUninstallResult;
removedClaudeFetchConfigPaths?: Awaited<ReturnType<typeof uninstallClaudeFetchIntegration>>;
removedCodexFetchConfigPath?: string;
removedPlaywrightIntegration?: Awaited<ReturnType<typeof uninstallPlaywrightIntegration>>;

@@ -44,2 +53,4 @@ projectConfigPath?: string;

browserUseIntegration: BrowserUseIntegrationUninstallResult;
claudeFetchConfigPaths?: Awaited<ReturnType<typeof uninstallClaudeFetchIntegration>>;
codexFetchConfigPath?: string;
playwrightIntegration: Awaited<ReturnType<typeof uninstallPlaywrightIntegration>>;

@@ -54,2 +65,4 @@ projectConfigPath?: string;

installBrowserUse?: typeof installBrowserUseIntegrationArtifacts;
installClaudeFetch?: typeof installClaudeFetchIntegration;
installCodexFetch?: typeof installCodexFetchIntegration;
probeBrowserUse?: typeof probeBrowserUseVersions;

@@ -61,2 +74,4 @@ probeAgentBrowser?: typeof probeAgentBrowserInstallation;

uninstallBrowserUse?: typeof uninstallBrowserUseIntegrationArtifacts;
uninstallClaudeFetch?: typeof uninstallClaudeFetchIntegration;
uninstallCodexFetch?: typeof uninstallCodexFetchIntegration;
installPlaywright?: typeof installPlaywrightIntegration;

@@ -63,0 +78,0 @@ probePlaywright?: typeof probePlaywrightInstallation;

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

{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,4BAA4B,EAClC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,wBAAwB,EACxB,yBAAyB,EACzB,qBAAqB,EACrB,2BAA2B,EAC5B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,qCAAqC,EACrC,uCAAuC,EACvC,KAAK,iCAAiC,EACtC,KAAK,oCAAoC,EAC1C,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAEL,uBAAuB,EACvB,KAAK,kBAAkB,EACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,6BAA6B,EAC7B,KAAK,wBAAwB,EAC9B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,4BAA4B,EAC5B,KAAK,iCAAiC,EACtC,8BAA8B,EAC/B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,2BAA2B,EAAE,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAEjG,MAAM,WAAW,qBAAqB;IACpC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iBAAiB,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,oBAAoB;IACnC,wBAAwB,CAAC,EAAE,wBAAwB,CAAC;IACpD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,aAAa,EAAE,OAAO,CAAC;IACvB,IAAI,EAAE,4BAA4B,CAAC;IACnC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,qBAAqB,CAAC,EAAE,iCAAiC,CAAC;IAC1D,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;IAChD,qBAAqB,CAAC,EAAE,iCAAiC,CAAC;IAC1D,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC,4BAA4B,CAAC,EAAE,oCAAoC,CAAC;IACpE,4BAA4B,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,8BAA8B,CAAC,CAAC,CAAC;IAC1F,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,wBAAwB;IACvC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,qBAAqB,EAAE,oCAAoC,CAAC;IAC5D,qBAAqB,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,8BAA8B,CAAC,CAAC,CAAC;IAClF,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,OAAO,mBAAmB,CAAC;IACzC,eAAe,CAAC,EAAE,OAAO,uBAAuB,CAAC;IACjD,gBAAgB,CAAC,EAAE,OAAO,wBAAwB,CAAC;IACnD,WAAW,CAAC,EAAE,OAAO,iBAAiB,CAAC;IACvC,iBAAiB,CAAC,EAAE,OAAO,qCAAqC,CAAC;IACjE,eAAe,CAAC,EAAE,OAAO,uBAAuB,CAAC;IACjD,iBAAiB,CAAC,EAAE,OAAO,6BAA6B,CAAC;IACzD,gBAAgB,CAAC,EAAE,OAAO,yBAAyB,CAAC;IACpD,aAAa,CAAC,EAAE,OAAO,qBAAqB,CAAC;IAC7C,aAAa,CAAC,EAAE,OAAO,mBAAmB,CAAC;IAC3C,mBAAmB,CAAC,EAAE,OAAO,uCAAuC,CAAC;IACrE,iBAAiB,CAAC,EAAE,OAAO,4BAA4B,CAAC;IACxD,eAAe,CAAC,EAAE,OAAO,2BAA2B,CAAC;IACrD,mBAAmB,CAAC,EAAE,OAAO,8BAA8B,CAAC;IAC5D,kBAAkB,CAAC,EAAE,OAAO,2BAA2B,CAAC;CACzD;AAED,wBAAsB,cAAc,CAClC,OAAO,GAAE,qBAA0B,EACnC,YAAY,GAAE,qBAA0B,GACvC,OAAO,CAAC,oBAAoB,CAAC,CA6I/B;AAED,wBAAsB,kBAAkB,CACtC,OAAO,GAAE,qBAA0B,EACnC,YAAY,GAAE,qBAA0B,GACvC,OAAO,CAAC,wBAAwB,CAAC,CAyCnC"}
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"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"}

@@ -8,2 +8,3 @@ import { installNativeHost, uninstallNativeHost, } from '@panerelay/bridge/install';

import { probePlaywrightInstallation } from '@panerelay/playwright';
import { installClaudeFetchIntegration, installCodexFetchIntegration, uninstallClaudeFetchIntegration, uninstallCodexFetchIntegration, } from './agent-fetch-integration.js';
export async function setupPanerelay(options = {}, dependencies = {}) {

@@ -24,2 +25,8 @@ const installHost = dependencies.installHost ?? installNativeHost;

}
if (options.claudeFetch && options.removeClaudeFetch) {
throw new Error('claudeFetch and removeClaudeFetch are mutually exclusive');
}
if (options.codexFetch && options.removeCodexFetch) {
throw new Error('codexFetch and removeCodexFetch are mutually exclusive');
}
const agentBrowserInstallation = options.agentBrowser

@@ -53,2 +60,22 @@ ? await (dependencies.probeAgentBrowser ?? probeAgentBrowserInstallation)({

: undefined;
const codexFetchConfigPath = options.codexFetch
? await (dependencies.installCodexFetch ?? installCodexFetchIntegration)(host.launchPath, {
homeDirectory: options.homeDirectory,
})
: undefined;
const claudeFetchConfigPaths = options.claudeFetch
? await (dependencies.installClaudeFetch ?? installClaudeFetchIntegration)(host.launchPath, {
homeDirectory: options.homeDirectory,
})
: undefined;
const removedCodexFetchConfigPath = options.removeCodexFetch
? await (dependencies.uninstallCodexFetch ?? uninstallCodexFetchIntegration)({
homeDirectory: options.homeDirectory,
})
: undefined;
const removedClaudeFetchConfigPaths = options.removeClaudeFetch
? await (dependencies.uninstallClaudeFetch ?? uninstallClaudeFetchIntegration)({
homeDirectory: options.homeDirectory,
})
: undefined;
const browserUseVersions = options.browserUse

@@ -104,2 +131,6 @@ ? await (dependencies.probeBrowserUse ?? probeBrowserUseVersions)(options.environment, options.platform)

...(browserUseIntegration ? { browserUseIntegration } : {}),
...(codexFetchConfigPath ? { codexFetchConfigPath } : {}),
...(claudeFetchConfigPaths ? { claudeFetchConfigPaths } : {}),
...(removedCodexFetchConfigPath ? { removedCodexFetchConfigPath } : {}),
...(removedClaudeFetchConfigPaths ? { removedClaudeFetchConfigPaths } : {}),
...(browserUseVersions

@@ -128,2 +159,6 @@ ? {

...(browserUseIntegration ? { browserUseIntegration } : {}),
...(codexFetchConfigPath ? { codexFetchConfigPath } : {}),
...(claudeFetchConfigPaths ? { claudeFetchConfigPaths } : {}),
...(removedCodexFetchConfigPath ? { removedCodexFetchConfigPath } : {}),
...(removedClaudeFetchConfigPaths ? { removedClaudeFetchConfigPaths } : {}),
...(browserUseVersions

@@ -149,2 +184,4 @@ ? {

const removeProject = dependencies.removeProject ?? removeProjectProvider;
const codexFetchConfigPath = await (dependencies.uninstallCodexFetch ?? uninstallCodexFetchIntegration)({ homeDirectory: options.homeDirectory });
const claudeFetchConfigPaths = await (dependencies.uninstallClaudeFetch ?? uninstallClaudeFetchIntegration)({ homeDirectory: options.homeDirectory });
await uninstallHost({

@@ -171,2 +208,4 @@ homeDirectory: options.homeDirectory,

browserUseIntegration,
...(codexFetchConfigPath ? { codexFetchConfigPath } : {}),
...(claudeFetchConfigPaths ? { claudeFetchConfigPaths } : {}),
playwrightIntegration,

@@ -181,2 +220,4 @@ };

browserUseIntegration,
...(codexFetchConfigPath ? { codexFetchConfigPath } : {}),
...(claudeFetchConfigPaths ? { claudeFetchConfigPaths } : {}),
playwrightIntegration,

@@ -183,0 +224,0 @@ projectConfigPath,

@@ -6,2 +6,16 @@ #!/usr/bin/env node

// ../protocol/dist/browser-fetch.js
var PANERELAY_FETCH_MAX_URL_BYTES = 8 * 1024;
var PANERELAY_FETCH_MAX_HEADER_BYTES = 64 * 1024;
var PANERELAY_FETCH_MAX_BODY_BYTES = 16 * 1024 * 1024;
var PANERELAY_FETCH_MAX_RESPONSE_BODY_BYTES = 32 * 1024 * 1024;
var PANERELAY_FETCH_MAX_HTTP_REQUEST_BYTES = 24 * 1024 * 1024;
var PANERELAY_FETCH_MAX_SESSION_REQUEST_BYTES = 96 * 1024;
var PANERELAY_FETCH_ADAPTER_MAX_ARTIFACT_BYTES = 8 * 1024 * 1024;
var PANERELAY_FETCH_ADAPTER_MAX_OUTPUT_BYTES = 8 * 1024 * 1024;
var PANERELAY_FETCH_ADAPTER_MAX_STDERR_BYTES = 64 * 1024;
var PANERELAY_FETCH_ADAPTER_MAX_SOURCE_PATH_BYTES = 4 * 1024;
var PANERELAY_FETCH_ADAPTER_MAX_FILE_BYTES = 12 * 1024 * 1024;
var PANERELAY_FETCH_ADAPTER_MAX_INPUT_BYTES = 18 * 1024 * 1024;
// ../protocol/dist/cli-adapter.js

@@ -8,0 +22,0 @@ var PANERELAY_CLI_ADAPTER_PROTOCOL_VERSION = "panerelay.cli-adapter.v1";

@@ -11,2 +11,16 @@ #!/usr/bin/env node

// ../protocol/dist/browser-fetch.js
var PANERELAY_FETCH_MAX_URL_BYTES = 8 * 1024;
var PANERELAY_FETCH_MAX_HEADER_BYTES = 64 * 1024;
var PANERELAY_FETCH_MAX_BODY_BYTES = 16 * 1024 * 1024;
var PANERELAY_FETCH_MAX_RESPONSE_BODY_BYTES = 32 * 1024 * 1024;
var PANERELAY_FETCH_MAX_HTTP_REQUEST_BYTES = 24 * 1024 * 1024;
var PANERELAY_FETCH_MAX_SESSION_REQUEST_BYTES = 96 * 1024;
var PANERELAY_FETCH_ADAPTER_MAX_ARTIFACT_BYTES = 8 * 1024 * 1024;
var PANERELAY_FETCH_ADAPTER_MAX_OUTPUT_BYTES = 8 * 1024 * 1024;
var PANERELAY_FETCH_ADAPTER_MAX_STDERR_BYTES = 64 * 1024;
var PANERELAY_FETCH_ADAPTER_MAX_SOURCE_PATH_BYTES = 4 * 1024;
var PANERELAY_FETCH_ADAPTER_MAX_FILE_BYTES = 12 * 1024 * 1024;
var PANERELAY_FETCH_ADAPTER_MAX_INPUT_BYTES = 18 * 1024 * 1024;
// ../protocol/dist/cli-adapter.js

@@ -13,0 +27,0 @@ var PANERELAY_CLI_ADAPTER_PROTOCOL_VERSION = "panerelay.cli-adapter.v1";

{
"name": "@panerelay/setup",
"version": "0.8.0",
"version": "0.9.0",
"description": "Install and diagnose Panerelay's local integration and optional automation adapters.",

@@ -39,8 +39,10 @@ "type": "module",

"@clack/prompts": "1.2.0",
"@panerelay/browser-registry": "0.8.0",
"@panerelay/browser-use": "0.8.0",
"@panerelay/bridge": "0.8.0",
"@panerelay/cli": "0.8.0",
"@panerelay/playwright": "0.8.0",
"@panerelay/protocol": "0.8.0"
"@panerelay/bridge": "0.9.0",
"@panerelay/browser-use": "0.9.0",
"@panerelay/browser-registry": "0.9.0",
"@panerelay/playwright": "0.9.0",
"@panerelay/cli": "0.9.0",
"@panerelay/protocol": "0.9.0",
"@panerelay/site-kit": "0.9.0",
"@panerelay/sites": "0.9.0"
},

@@ -53,4 +55,5 @@ "devDependencies": {

"typecheck": "tsc -p tsconfig.json --noEmit",
"test": "pnpm run build && node ../../scripts/run-compiled-tests.mjs dist"
"test": "pnpm --filter @panerelay/setup... run build && pnpm run test:compiled",
"test:compiled": "node ../../scripts/run-compiled-tests.mjs dist"
}
}

@@ -20,4 +20,20 @@ # @panerelay/setup

Agents in the side panel keep the selected project as their working directory and receive only bounded current-tab URL and title context. Browser MCP servers and Skills continue to come from the Agent's own configuration.
Agents in the side panel keep the selected project as their working directory and receive only bounded current-tab URL and title context. Panerelay-owned Codex and Claude Code processes also receive the bounded Panerelay Fetch MCP for browser-authenticated HTTP(S) requests. Automation MCP servers and Skills continue to come from the Agent's own configuration.
### Optional browser-authenticated fetch for external Agents
Codex and Claude Code have native hosted fetch/search surfaces that cannot be replaced by hooks. Panerelay can explicitly configure their supported MCP/settings surfaces so requests to known URLs use `panerelay_fetch` with the browser's login state:
```bash
npx --yes @panerelay/setup --codex-fetch
npx --yes @panerelay/setup --claude-fetch
npx --yes @panerelay/setup doctor --codex-fetch --claude-fetch
npx --yes @panerelay/setup --remove-codex-fetch
npx --yes @panerelay/setup --remove-claude-fetch
```
`--codex-fetch` registers the MCP and disables Codex hosted web search. `--claude-fetch` registers the MCP and denies Claude `WebFetch` while leaving `WebSearch` available. The matching `--remove-*-fetch` option removes only that integration. These are explicit global Agent configuration changes: base setup and the interactive automation selector do not enable them. Setup uses marked/structured Panerelay-owned entries, rejects an unmanaged `panerelay_fetch` conflict, and removal or full uninstall removes only unchanged owned entries while restoring the previous Codex web-search value. It does not install either Agent, patch a vendor runtime, accept API keys, or guarantee model tool selection.
The MCP is a generic HTTP(S) request path with exact-origin session authority and explicit Extension domain approval. It attaches applicable browser Cookies by default, rejects redirects, and never returns Cookie or storage values. Arbitrary `localStorage` access is not exposed; a built-in/site adapter may use only a protected exact-origin storage binding declared in its manifest.
### Automation tool integrations — let your Agent configure them

@@ -28,6 +44,6 @@

```bash
npx skills add F-loat/panerelay --skill panerelay-browser
npx skills add F-loat/panerelay --skill panerelay
```
Then ask the Agent to use `$panerelay-browser` with the engine you want. The Skill covers environment inspection, official upstream installation only when needed, selected Panerelay setup and doctor commands, a stop for user-controlled tab authorization, engine-specific verification, and troubleshooting.
Then ask the Agent to use `$panerelay`. The Skill chooses browser-authenticated Fetch or the requested automation engine and covers environment inspection, official upstream installation only when needed, selected Panerelay setup and doctor commands, a stop for user-controlled authorization, engine-specific verification, and troubleshooting.

@@ -54,2 +70,40 @@ Skill installation, scope, updates, and removal are owned by `npx skills`. Setup does not inspect Agent Skill directories or remove independently installed Skills.

### Fetch adapter lifecycle
Fetch adapters are independent from base setup and automation-engine integrations. They are installed only by an explicit adapter command:
```bash
npx --yes @panerelay/setup add bilibili
npx --yes @panerelay/setup add bilibili /absolute/path/to/local-adapter /absolute/path/to/source-site
npx --yes @panerelay/setup add owner/repository
npx --yes @panerelay/setup add github:owner/repository@v1.0.0#sites/example
npx --yes @panerelay/setup add 'https://github.com/owner/repository?ref=v1.0.0&path=sites/example'
npx --yes @panerelay/setup add --all
npx --yes @panerelay/setup adapters
npx --yes @panerelay/setup remove bilibili
npx --yes @panerelay/setup remove --all
```
`add` validates every source before making a batch visible. Built-in names resolve only within the lockstep `@panerelay/sites` catalog dependency. Existing local paths win over GitHub shorthand, and an unknown bare ID fails without network access. Only an explicit `owner/repository`, `github:` shorthand, or canonical `https://github.com/owner/repository` URL enables public GitHub access. Setup resolves the selected/default ref once to a full commit through the unauthenticated GitHub API, downloads its bounded HTTPS codeload archive, and records credential-free provenance. Private repositories, tokens, Git credential helpers, `git clone`, submodules, dependency installation, and repository scripts are unsupported.
A local source may be either the strict installed two-file form or an editable site-kit directory containing `panerelay.site.ts` and direct `commands/*.ts` files. Source-form adapters are built in protected temporary storage through `@panerelay/site-kit`; setup never writes generated files into the author directory and never runs colocated tests. Active files and the atomic registry are stored under `~/.panerelay/fetch-adapters` with user-only permissions. `adapters` shows recorded built-in, absolute local, or GitHub commit provenance. Re-running `add` explicitly replaces that site; `remove` changes only selected fetch-adapter records and owned version directories, not the Native Host, automation integrations, browser defaults, or conversations.
The installed form contains exactly `panerelay-fetch-adapter.json` and the self-contained `.mjs` entry named by its `entry` field. The manifest protocol is `panerelay.fetch-adapter.v3` and declares a bounded ID, name, version, description, commands, typed arguments, output fields, and examples. Installed code runs as a one-shot Node child with a minimal environment and a short-lived fetch-only Bridge credential. Local and GitHub installation are explicit trust decisions: static build, process isolation, and digest verification do not sandbox later command execution from the user's filesystem.
Create, check, test, and build a source adapter without a nested npm package:
```bash
npx --yes @panerelay/site-kit init ./example-site --id example
npx --yes @panerelay/site-kit check ./example-site
npx --yes @panerelay/site-kit test ./example-site
npx --yes @panerelay/site-kit build ./example-site --out ./example-adapter
npx --yes @panerelay/setup add ./example-site
```
Each command file exports one `defineCommand(...)` definition with literal help metadata and its handler. Relative TypeScript helpers and `node:` built-ins are supported; arbitrary package imports are rejected. Existing strict two-file adapters remain installable. When GitHub is unavailable or rate-limited, build locally and pass either source form or the two-file output as the offline fallback.
The built-in Bilibili source lives directly under `packages/sites/src/bilibili`, and `@panerelay/sites` generates and packages its two-file install artifact. It exposes 16 reads (`whoami`, `me`, `video`, `search`, `hot`, `ranking`, `dynamic`, `feed`, `feed-detail`, `favorite`, `history`, `following`, `user-videos`, `comments`, `subtitle`, and `summary`) and three writes (`comment`, `follow`, and `unfollow`). Each public command and its help metadata live in one matching file under `commands`; shared WBI/API, profile, video, dynamic, and relation helpers remain separate.
The adapter requires a logged-in browser session and Chrome site access for Bilibili. Its write requests declare a generic binding from the `bili_jct` Cookie name to the `csrf` form field. Only the Extension resolves the value; neither setup, the registry, the adapter child, Native Messaging, nor normal errors receive it. Comment requires explicit `--execute`, while follow/unfollow are idempotent and verify the resulting relation. Interactive `login` and downloader/filesystem-oriented `download` are not shipped. The CLI renders OpenCLI-style tables by default and accepts `--json` for structured output.
An unflagged interactive setup initializes its integration selector from the current protected Panerelay Provider and adapter configuration. Checked integrations are installed or updated, while unchecked integrations have only their Panerelay-owned Provider, adapter, configuration, and default artifacts removed. The upstream agent-browser, browser-use, Browser Harness, and Playwright CLI installations are never removed. The shared default answer is also initialized from current Panerelay defaults, so a later setup run reflects the state produced by the previous successful run without a separate selection cache. After the final answer, a localized timer shows that reconciliation is still running. Explicit integration flags retain additive behavior and do not remove omitted integrations.

@@ -87,3 +141,3 @@

The supported surfaces are the official `browser-use` CLI, `browser-use --cli-mcp`, and the Browser Use workflow in the independently installed `panerelay-browser` Skill. Panerelay does not transparently intercept arbitrary browser-use Python SDK construction. The exact verified baseline is browser-use 0.13.7 with Browser Harness 0.1.8; newer supported versions meet the minimum without automatically inheriting `Verified` status. See the [compatibility record](../../docs/compatibility/browser-use-0.13.7.md).
The supported surfaces are the official `browser-use` CLI, `browser-use --cli-mcp`, and the Browser Use workflow in the independently installed `panerelay` Skill. Panerelay does not transparently intercept arbitrary browser-use Python SDK construction. The exact verified baseline is browser-use 0.13.7 with Browser Harness 0.1.8; newer supported versions meet the minimum without automatically inheriting `Verified` status. See the [compatibility record](../../docs/compatibility/browser-use-0.13.7.md).

@@ -122,7 +176,7 @@ The base CLI controls the durable Browser Use mode:

The independently installed `panerelay-browser` Skill contains the Playwright workflow. See the [Playwright integration guide](../adapters/playwright/README.md) and [compatibility record](../../docs/compatibility/playwright-cli-0.1.17.md).
The independently installed `panerelay` Skill contains the Playwright workflow. See the [Playwright integration guide](../adapters/playwright/README.md) and [compatibility record](../../docs/compatibility/playwright-cli-0.1.17.md).
This connection reuses authorized tabs and does not provide isolated BrowserContexts, launch-time executable or proxy options, or browser-wide close. The fixed endpoint is loopback discovery, not a reusable browser credential.
Omitting an action runs `setup`. In an interactive terminal, the unflagged command presents the desired-state selector described above. In non-interactive use it installs only the Native Messaging host and side-panel prerequisites. Add `--agent-browser`, `--browser-use`, and/or `--playwright` to install integrations explicitly without removing omitted integrations.
Omitting an action runs `setup`. In an interactive terminal, the unflagged command presents the desired-state selector described above. In non-interactive use it installs only the Native Messaging host and side-panel prerequisites. Add `--agent-browser`, `--browser-use`, and/or `--playwright` to install automation integrations explicitly without removing omitted integrations. Add `--codex-fetch` or `--claude-fetch` only when the user explicitly wants the corresponding external-Agent configuration.

@@ -129,0 +183,0 @@ ```bash