chore(node-sdk): audit providers kirocli kiroide opencode
Kiro and OpenCode files use common.ts, shell-command.ts, and managedScriptReference; exactKeys shared from kiroide-contract. Claude-Session: https://claude.ai/code/session_01Agoc9bRVE9KL7mmWtey9Vq
This commit is contained in:
1 parent
3b5cbd6e17
commit
25bfa39fcf
9 files changed
+57
-228
No files matched your search
@@ -1,14 +1,14 @@
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { managedScriptReference, sameAgentId, samePath } from './common.js';
|
||||
import {
|
||||
buildKiroIdeCommand,
|
||||
kiroIdeRuntimeReference,
|
||||
sameKiroIdeAgentId,
|
||||
sameKiroIdePath,
|
||||
type KiroIdeRuntimeReference,
|
||||
} from './kiroide-command.js';
|
||||
import {
|
||||
buildKiroIdeHook,
|
||||
exactKeys,
|
||||
kiroIdeRuntimeContracts,
|
||||
renderKiroIdeDocument,
|
||||
type KiroIdeDocument,
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
type RenderedKiroIdeDocument,
|
||||
type KiroIdeRuntimeContract,
|
||||
} from './kiroide-contract.js';
|
||||
import { isNodeExecutable, parseLegacyWindowsCommand } from './shell-command.js';
|
||||
import { isObject, parseStrictJsonObject, type JsonObject } from './strict-json.js';
|
||||
|
||||
export const AGENT_KEY = 'kirocli';
|
||||
@@ -162,27 +163,14 @@ function eventHooks(document: KiroCliV2Document, event: string): readonly KiroCl
|
||||
return (document.hooks[event] as readonly KiroCliV2Hook[] | undefined) ?? [];
|
||||
}
|
||||
|
||||
function exactKeys(value: JsonObject, keys: readonly string[]): boolean {
|
||||
return Object.keys(value).sort().join('|') === [...keys].sort().join('|');
|
||||
}
|
||||
|
||||
function legacyWindowsReference(
|
||||
command: unknown,
|
||||
scriptName: string,
|
||||
): KiroIdeRuntimeReference | undefined {
|
||||
if (typeof command !== 'string') return undefined;
|
||||
const match = /^"([^"\r\n]+)" "([^"\r\n]+)"$/.exec(command);
|
||||
if (!match
|
||||
|| !path.isAbsolute(match[1])
|
||||
|| !['node', 'node.exe'].includes(path.basename(match[1]).toLowerCase())
|
||||
|| !path.isAbsolute(match[2])
|
||||
|| path.basename(match[2]) !== scriptName) return undefined;
|
||||
const agentDirectory = path.dirname(match[2]);
|
||||
if (!sameKiroIdePath(path.dirname(agentDirectory), path.join(os.homedir(), '.elydora'))) {
|
||||
return undefined;
|
||||
}
|
||||
const agentId = path.basename(agentDirectory);
|
||||
return agentId ? { agentId, scriptPath: match[2] } : undefined;
|
||||
const parsed = parseLegacyWindowsCommand(command);
|
||||
if (!parsed || !path.isAbsolute(parsed[0]) || !isNodeExecutable(parsed[0])) return undefined;
|
||||
return managedScriptReference(parsed[1], scriptName);
|
||||
}
|
||||
|
||||
function ownedReference(command: unknown, scriptName: string): KiroIdeRuntimeReference | undefined {
|
||||
@@ -219,7 +207,7 @@ function hasOwnedV2Hooks(document: KiroCliV2Document, agentId?: string): boolean
|
||||
return ['preToolUse', 'postToolUse'].some((event) => eventHooks(document, event).some((hook) => {
|
||||
const reference = v2OwnedReference(hook, event);
|
||||
return reference !== undefined
|
||||
&& (agentId === undefined || sameKiroIdeAgentId(reference.agentId, agentId));
|
||||
&& (agentId === undefined || sameAgentId(reference.agentId, agentId));
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -237,7 +225,7 @@ export function withoutManagedKiroCliV2Hooks(
|
||||
for (const event of ['preToolUse', 'postToolUse']) {
|
||||
const remaining = eventHooks(document, event).filter((hook) => {
|
||||
const reference = v2OwnedReference(hook, event);
|
||||
return !reference || (agentId !== undefined && !sameKiroIdeAgentId(reference.agentId, agentId));
|
||||
return !reference || (agentId !== undefined && !sameAgentId(reference.agentId, agentId));
|
||||
});
|
||||
if (remaining.length > 0) next[event] = remaining;
|
||||
else delete next[event];
|
||||
@@ -288,7 +276,7 @@ function entirelyManagedV2Document(document: KiroCliV2Document, agentId?: string
|
||||
const hooks = eventHooks(document, event);
|
||||
if (hooks.length !== 1) return false;
|
||||
const reference = v2OwnedReference(hooks[0], event);
|
||||
if (!reference || (agentId !== undefined && !sameKiroIdeAgentId(reference.agentId, agentId))) {
|
||||
if (!reference || (agentId !== undefined && !sameAgentId(reference.agentId, agentId))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -324,7 +312,7 @@ export function kiroCliV2RuntimeContracts(
|
||||
.map((hook) => v2ManagedReference(hook, 'postToolUse'))
|
||||
.filter((value): value is KiroIdeRuntimeReference => value !== undefined);
|
||||
if (guards.length !== 1 || audits.length !== 1
|
||||
|| !sameKiroIdeAgentId(guards[0].agentId, audits[0].agentId)) return [];
|
||||
|| !sameAgentId(guards[0].agentId, audits[0].agentId)) return [];
|
||||
return [{ agentId: guards[0].agentId, guardPath: guards[0].scriptPath, auditPath: audits[0].scriptPath }];
|
||||
}
|
||||
|
||||
@@ -355,7 +343,7 @@ export function withoutManagedKiroCliV3Hooks(
|
||||
): KiroIdeHook[] {
|
||||
return hooks.filter((hook) => {
|
||||
const reference = v3OwnedReference(hook);
|
||||
return !reference || (agentId !== undefined && !sameKiroIdeAgentId(reference.agentId, agentId));
|
||||
return !reference || (agentId !== undefined && !sameAgentId(reference.agentId, agentId));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -363,7 +351,7 @@ function hasOwnedV3Hooks(hooks: readonly KiroIdeHook[], agentId?: string): boole
|
||||
return hooks.some((hook) => {
|
||||
const reference = v3OwnedReference(hook);
|
||||
return reference !== undefined
|
||||
&& (agentId === undefined || sameKiroIdeAgentId(reference.agentId, agentId));
|
||||
&& (agentId === undefined || sameAgentId(reference.agentId, agentId));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -374,7 +362,7 @@ function entirelyManagedV3Document(document: KiroIdeDocument, agentId?: string):
|
||||
&& document.hooks.every((hook) => {
|
||||
const reference = v3OwnedReference(hook);
|
||||
return reference !== undefined
|
||||
&& (agentId === undefined || sameKiroIdeAgentId(reference.agentId, agentId));
|
||||
&& (agentId === undefined || sameAgentId(reference.agentId, agentId));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -409,8 +397,8 @@ export function commonKiroCliRuntimeContract(
|
||||
const v2 = kiroCliV2RuntimeContracts(v2Document);
|
||||
const v3 = kiroIdeRuntimeContracts(v3Hooks);
|
||||
if (v2.length !== 1 || v3.length !== 1
|
||||
|| !sameKiroIdeAgentId(v2[0].agentId, v3[0].agentId)
|
||||
|| !sameKiroIdePath(v2[0].guardPath, v3[0].guardPath)
|
||||
|| !sameKiroIdePath(v2[0].auditPath, v3[0].auditPath)) return undefined;
|
||||
|| !sameAgentId(v2[0].agentId, v3[0].agentId)
|
||||
|| !samePath(v2[0].guardPath, v3[0].guardPath)
|
||||
|| !samePath(v2[0].auditPath, v3[0].auditPath)) return undefined;
|
||||
return v2[0];
|
||||
}
|
||||
@@ -27,10 +27,6 @@ const DISPLAY_NAME = 'Kiro CLI';
|
||||
const V2_DIRECTORY_LABEL = 'Kiro CLI agents directory';
|
||||
const V3_DIRECTORY_LABEL = 'Kiro CLI global hooks directory';
|
||||
|
||||
export type KiroCliRuntimePaths = ManagedRuntimePaths;
|
||||
export type PreparedKiroCliInstallation = PreparedManagedInstallation;
|
||||
export type { RenameFile };
|
||||
|
||||
function hookLocations(sources: KiroCliSources) {
|
||||
return [
|
||||
{ directoryLabel: V2_DIRECTORY_LABEL, filePath: sources.paths.v2Path },
|
||||
@@ -41,7 +37,7 @@ function hookLocations(sources: KiroCliSources) {
|
||||
export async function preflightKiroCliInstallation(
|
||||
config: InstallConfig,
|
||||
sources: KiroCliSources,
|
||||
): Promise<KiroCliRuntimePaths> {
|
||||
): Promise<ManagedRuntimePaths> {
|
||||
return preflightManagedInstallation({
|
||||
agentKey: AGENT_KEY,
|
||||
hookLocations: hookLocations(sources),
|
||||
@@ -54,7 +50,7 @@ export async function prepareKiroCliInstallation(
|
||||
sources: KiroCliSources,
|
||||
v2: RenderedKiroCliV2Document,
|
||||
v3: RenderedKiroIdeDocument,
|
||||
): Promise<PreparedKiroCliInstallation> {
|
||||
): Promise<PreparedManagedInstallation> {
|
||||
if (v2.next === undefined || v3.next === undefined) {
|
||||
throw new Error('Kiro CLI installation requires both v2 and v3 hook documents');
|
||||
}
|
||||
@@ -83,7 +79,7 @@ export async function prepareKiroCliInstallation(
|
||||
}
|
||||
|
||||
export async function commitKiroCliInstallation(
|
||||
prepared: PreparedKiroCliInstallation,
|
||||
prepared: PreparedManagedInstallation,
|
||||
renameFile?: RenameFile,
|
||||
): Promise<void> {
|
||||
await commitManagedInstallation(prepared, renameFile);
|
||||
|
||||
@@ -12,11 +12,10 @@ import {
|
||||
type KiroIdeDocument,
|
||||
type KiroIdeRuntimeContract,
|
||||
} from './kiroide-contract.js';
|
||||
import { MAX_CONFIG_BYTES } from './common.js';
|
||||
import { inspectPhysicalDirectory, readPhysicalFile } from './managed-files.js';
|
||||
import { managedRuntimeFilesExist } from './managed-runtime-status.js';
|
||||
|
||||
const MAX_HOOK_BYTES = 512 * 1024;
|
||||
|
||||
export interface KiroCliSources {
|
||||
readonly paths: KiroCliPaths;
|
||||
readonly v2: KiroCliV2Document;
|
||||
@@ -36,7 +35,7 @@ async function readV2Document(paths: KiroCliPaths): Promise<KiroCliV2Document> {
|
||||
const snapshot = await readPhysicalFile(
|
||||
paths.v2Path,
|
||||
'Kiro CLI v2 agent config',
|
||||
MAX_HOOK_BYTES,
|
||||
MAX_CONFIG_BYTES,
|
||||
);
|
||||
return snapshot
|
||||
? parseKiroCliV2Document(paths.v2Path, snapshot.contents)
|
||||
@@ -47,7 +46,7 @@ async function readV3Document(paths: KiroCliPaths): Promise<KiroIdeDocument> {
|
||||
const snapshot = await readPhysicalFile(
|
||||
paths.v3Path,
|
||||
'Kiro CLI v3 global hooks',
|
||||
MAX_HOOK_BYTES,
|
||||
MAX_CONFIG_BYTES,
|
||||
);
|
||||
return snapshot
|
||||
? parseKiroIdeDocument(paths.v3Path, snapshot.contents, 'Kiro CLI v3 global hooks')
|
||||
|
||||
+13
-136
@@ -1,132 +1,20 @@
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { managedScriptReference, type ManagedScriptReference } from './common.js';
|
||||
import {
|
||||
encodedWindowsCommand,
|
||||
isNodeExecutable,
|
||||
parseEncodedWindowsCommand,
|
||||
parsePosixCommand,
|
||||
posixSource,
|
||||
} from './shell-command.js';
|
||||
|
||||
interface ParsedArgument {
|
||||
readonly value: string;
|
||||
readonly next: number;
|
||||
}
|
||||
|
||||
export interface KiroIdeRuntimeReference {
|
||||
readonly agentId: string;
|
||||
readonly scriptPath: string;
|
||||
}
|
||||
|
||||
export function sameKiroIdePath(left: string, right: string): boolean {
|
||||
const normalizedLeft = path.resolve(left);
|
||||
const normalizedRight = path.resolve(right);
|
||||
return process.platform === 'win32'
|
||||
? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase()
|
||||
: normalizedLeft === normalizedRight;
|
||||
}
|
||||
|
||||
export function sameKiroIdeAgentId(left: string, right: string): boolean {
|
||||
return process.platform === 'win32' ? left.toLowerCase() === right.toLowerCase() : left === right;
|
||||
}
|
||||
|
||||
function quotePosix(value: string): string {
|
||||
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
||||
}
|
||||
|
||||
function quotePowerShell(value: string): string {
|
||||
return `'${value.replaceAll("'", "''")}'`;
|
||||
}
|
||||
|
||||
function windowsPowerShellPath(): string {
|
||||
const configuredRoot = process.env.SystemRoot;
|
||||
const systemRoot = configuredRoot
|
||||
&& path.win32.isAbsolute(configuredRoot)
|
||||
&& !/["%\r\n]/.test(configuredRoot)
|
||||
? configuredRoot
|
||||
: 'C:\\Windows';
|
||||
return path.win32.join(
|
||||
systemRoot,
|
||||
'System32',
|
||||
'WindowsPowerShell',
|
||||
'v1.0',
|
||||
'powershell.exe',
|
||||
);
|
||||
}
|
||||
|
||||
function windowsCommand(scriptPath: string): string {
|
||||
const source = `& ${quotePowerShell(process.execPath)} ${quotePowerShell(scriptPath)}; exit $LASTEXITCODE`;
|
||||
const encoded = Buffer.from(source, 'utf16le').toString('base64');
|
||||
return `"${windowsPowerShellPath()}" -NoLogo -NoProfile -NonInteractive -EncodedCommand ${encoded}`;
|
||||
}
|
||||
export type KiroIdeRuntimeReference = ManagedScriptReference;
|
||||
|
||||
export function buildKiroIdeCommand(scriptPath: string): string {
|
||||
if (!path.isAbsolute(process.execPath) || !path.isAbsolute(scriptPath)) {
|
||||
throw new Error('Kiro IDE hook commands require absolute executable and script paths');
|
||||
}
|
||||
return process.platform === 'win32'
|
||||
? windowsCommand(scriptPath)
|
||||
: `${quotePosix(process.execPath)} ${quotePosix(scriptPath)}`;
|
||||
}
|
||||
|
||||
function readPosixArgument(command: string, start: number): ParsedArgument | undefined {
|
||||
if (command[start] !== "'") return undefined;
|
||||
const apostrophe = `'"'"'`;
|
||||
let value = '';
|
||||
for (let index = start + 1; index < command.length;) {
|
||||
if (command.startsWith(apostrophe, index)) {
|
||||
value += "'";
|
||||
index += apostrophe.length;
|
||||
continue;
|
||||
}
|
||||
if (command[index] === "'") return { value, next: index + 1 };
|
||||
value += command[index];
|
||||
index += 1;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function parsePosixCommand(command: string): readonly [string, string] | undefined {
|
||||
const executable = readPosixArgument(command, 0);
|
||||
if (!executable || command[executable.next] !== ' ') return undefined;
|
||||
const script = readPosixArgument(command, executable.next + 1);
|
||||
return script && script.next === command.length ? [executable.value, script.value] : undefined;
|
||||
}
|
||||
|
||||
function readPowerShellArgument(command: string, start: number): ParsedArgument | undefined {
|
||||
if (command[start] !== "'") return undefined;
|
||||
let value = '';
|
||||
for (let index = start + 1; index < command.length; index += 1) {
|
||||
if (command[index] !== "'") {
|
||||
value += command[index];
|
||||
continue;
|
||||
}
|
||||
if (command[index + 1] === "'") {
|
||||
value += "'";
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
return { value, next: index + 1 };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function parsePowerShellSource(source: string): readonly [string, string] | undefined {
|
||||
if (!source.startsWith('& ')) return undefined;
|
||||
const executable = readPowerShellArgument(source, 2);
|
||||
if (!executable || source[executable.next] !== ' ') return undefined;
|
||||
const script = readPowerShellArgument(source, executable.next + 1);
|
||||
return script && source.slice(script.next) === '; exit $LASTEXITCODE'
|
||||
? [executable.value, script.value]
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function parseWindowsCommand(command: string): readonly [string, string] | undefined {
|
||||
const match = /^"([^"\r\n]+)" -NoLogo -NoProfile -NonInteractive -EncodedCommand ([A-Za-z0-9+/]+={0,2})$/.exec(command);
|
||||
if (!match
|
||||
|| !path.win32.isAbsolute(match[1])
|
||||
|| path.win32.basename(match[1]).toLowerCase() !== 'powershell.exe') return undefined;
|
||||
const buffer = Buffer.from(match[2], 'base64');
|
||||
if (buffer.length % 2 !== 0 || buffer.toString('base64') !== match[2]) return undefined;
|
||||
return parsePowerShellSource(buffer.toString('utf16le'));
|
||||
}
|
||||
|
||||
function isNodeExecutable(filePath: string): boolean {
|
||||
const basename = path.basename(filePath).toLowerCase();
|
||||
return basename === 'node' || basename === 'node.exe';
|
||||
return process.platform === 'win32' ? encodedWindowsCommand(scriptPath) : posixSource(scriptPath);
|
||||
}
|
||||
|
||||
export function kiroIdeRuntimeReference(
|
||||
@@ -134,19 +22,8 @@ export function kiroIdeRuntimeReference(
|
||||
scriptName: string,
|
||||
): KiroIdeRuntimeReference | undefined {
|
||||
const parsed = process.platform === 'win32'
|
||||
? parseWindowsCommand(command)
|
||||
? parseEncodedWindowsCommand(command)
|
||||
: parsePosixCommand(command);
|
||||
if (!parsed
|
||||
|| !path.isAbsolute(parsed[0])
|
||||
|| !isNodeExecutable(parsed[0])
|
||||
|| !path.isAbsolute(parsed[1])
|
||||
|| path.basename(parsed[1]) !== scriptName) return undefined;
|
||||
const agentDirectory = path.dirname(parsed[1]);
|
||||
if (!sameKiroIdePath(path.dirname(agentDirectory), path.join(os.homedir(), '.elydora'))) {
|
||||
return undefined;
|
||||
}
|
||||
const agentId = path.basename(agentDirectory);
|
||||
return agentId && agentId !== '.' && agentId !== '..'
|
||||
? { agentId, scriptPath: parsed[1] }
|
||||
: undefined;
|
||||
if (!parsed || !path.isAbsolute(parsed[0]) || !isNodeExecutable(parsed[0])) return undefined;
|
||||
return managedScriptReference(parsed[1], scriptName);
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { managedScriptReference, sameAgentId } from './common.js';
|
||||
import {
|
||||
buildKiroIdeCommand,
|
||||
kiroIdeRuntimeReference,
|
||||
sameKiroIdeAgentId,
|
||||
sameKiroIdePath,
|
||||
type KiroIdeRuntimeReference,
|
||||
} from './kiroide-command.js';
|
||||
import { isObject, parseStrictJsonObject, type JsonObject } from './strict-json.js';
|
||||
@@ -154,7 +153,7 @@ export function createKiroIdeDocument(filePath: string): KiroIdeDocument {
|
||||
return { exists: false, filePath, root: {}, hooks: [] };
|
||||
}
|
||||
|
||||
function exactKeys(value: JsonObject, keys: readonly string[]): boolean {
|
||||
export function exactKeys(value: JsonObject, keys: readonly string[]): boolean {
|
||||
return Object.keys(value).sort().join('|') === [...keys].sort().join('|');
|
||||
}
|
||||
|
||||
@@ -228,7 +227,7 @@ export function withoutManagedKiroIdeHooks(
|
||||
): KiroIdeHook[] {
|
||||
return hooks.filter((hook) => {
|
||||
const reference = ownedReference(hook);
|
||||
return !reference || (agentId !== undefined && !sameKiroIdeAgentId(reference.agentId, agentId));
|
||||
return !reference || (agentId !== undefined && !sameAgentId(reference.agentId, agentId));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -285,12 +284,7 @@ export function kiroIdeRuntimeContracts(
|
||||
function legacyReference(command: unknown, scriptName: string) {
|
||||
if (typeof command !== 'string') return undefined;
|
||||
const match = /^node "([^"\r\n]+)"$/.exec(command);
|
||||
if (!match || !path.isAbsolute(match[1]) || path.basename(match[1]) !== scriptName) return undefined;
|
||||
const agentDirectory = path.dirname(match[1]);
|
||||
if (!sameKiroIdePath(path.dirname(agentDirectory), path.join(os.homedir(), '.elydora'))) {
|
||||
return undefined;
|
||||
}
|
||||
return { agentId: path.basename(agentDirectory), scriptPath: match[1] };
|
||||
return match ? managedScriptReference(match[1], scriptName) : undefined;
|
||||
}
|
||||
|
||||
export function legacyKiroIdeRuntimeContract(raw: string, filePath: string): KiroIdeRuntimeContract | undefined {
|
||||
@@ -311,6 +305,6 @@ export function legacyKiroIdeRuntimeContract(raw: string, filePath: string): Kir
|
||||
|| auditHook.timeout_ms !== 5000) return undefined;
|
||||
const guard = legacyReference(guardHook.command, GUARD_SCRIPT);
|
||||
const audit = legacyReference(auditHook.command, AUDIT_SCRIPT);
|
||||
if (!guard || !audit || !sameKiroIdeAgentId(guard.agentId, audit.agentId)) return undefined;
|
||||
if (!guard || !audit || !sameAgentId(guard.agentId, audit.agentId)) return undefined;
|
||||
return { agentId: guard.agentId, guardPath: guard.scriptPath, auditPath: audit.scriptPath };
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from 'node:path';
|
||||
import type { InstallConfig } from './base.js';
|
||||
import { sameKiroIdeAgentId } from './kiroide-command.js';
|
||||
import { sameAgentId } from './common.js';
|
||||
import {
|
||||
AGENT_KEY,
|
||||
AUDIT_SCRIPT,
|
||||
@@ -31,17 +31,13 @@ import {
|
||||
const DISPLAY_NAME = 'Kiro IDE';
|
||||
const DIRECTORY_LABEL = 'Kiro IDE hooks directory';
|
||||
|
||||
export type KiroIdeRuntimePaths = ManagedRuntimePaths;
|
||||
export type PreparedKiroIdeInstallation = PreparedManagedInstallation;
|
||||
export type { RenameFile };
|
||||
|
||||
function hookLocation(paths: KiroIdePaths) {
|
||||
return [{ directoryLabel: DIRECTORY_LABEL, filePath: paths.configPath }];
|
||||
}
|
||||
|
||||
function removesLegacy(legacy: LegacyKiroIdeDocument, agentId?: string): boolean {
|
||||
return legacy.contract !== undefined
|
||||
&& (agentId === undefined || sameKiroIdeAgentId(legacy.contract.agentId, agentId));
|
||||
&& (agentId === undefined || sameAgentId(legacy.contract.agentId, agentId));
|
||||
}
|
||||
|
||||
async function prepareLegacyRemoval(
|
||||
@@ -64,7 +60,7 @@ async function prepareLegacyRemoval(
|
||||
export async function preflightKiroIdeInstallation(
|
||||
config: InstallConfig,
|
||||
paths: KiroIdePaths,
|
||||
): Promise<KiroIdeRuntimePaths> {
|
||||
): Promise<ManagedRuntimePaths> {
|
||||
return preflightManagedInstallation({
|
||||
agentKey: AGENT_KEY,
|
||||
hookLocations: hookLocation(paths),
|
||||
@@ -76,7 +72,7 @@ export async function prepareKiroIdeInstallation(
|
||||
config: InstallConfig,
|
||||
sources: KiroIdeSources,
|
||||
rendered: RenderedKiroIdeDocument,
|
||||
): Promise<PreparedKiroIdeInstallation> {
|
||||
): Promise<PreparedManagedInstallation> {
|
||||
if (rendered.next === undefined) throw new Error('Kiro IDE installation requires a hook document');
|
||||
const prepared = await prepareManagedInstallation({
|
||||
agentKey: AGENT_KEY,
|
||||
@@ -107,7 +103,7 @@ export async function prepareKiroIdeInstallation(
|
||||
}
|
||||
|
||||
export async function commitKiroIdeInstallation(
|
||||
prepared: PreparedKiroIdeInstallation,
|
||||
prepared: PreparedManagedInstallation,
|
||||
renameFile?: RenameFile,
|
||||
): Promise<void> {
|
||||
await commitManagedInstallation(prepared, renameFile);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import type { HookScriptOptions } from './hook-template.js';
|
||||
import { samePath } from './common.js';
|
||||
import { errorMessage, samePath } from './common.js';
|
||||
import { isNodeExecutable } from './shell-command.js';
|
||||
import { parseStrictJsonObject } from './strict-json.js';
|
||||
|
||||
export const AGENT_KEY = 'opencode';
|
||||
@@ -51,10 +52,6 @@ interface OpenCodePluginMetadata {
|
||||
readonly auditPath: string;
|
||||
}
|
||||
|
||||
export function sameOpenCodeAgentId(left: string, right: string): boolean {
|
||||
return process.platform === 'win32' ? left.toLowerCase() === right.toLowerCase() : left === right;
|
||||
}
|
||||
|
||||
export function resolveOpenCodePaths(
|
||||
homeDirectory = os.homedir(),
|
||||
xdgConfigHome = process.env.XDG_CONFIG_HOME,
|
||||
@@ -83,11 +80,6 @@ function validAgentSegment(agentId: string): boolean {
|
||||
&& path.basename(agentId) === agentId;
|
||||
}
|
||||
|
||||
function isNodeExecutable(filePath: string): boolean {
|
||||
const basename = path.basename(filePath).toLowerCase();
|
||||
return basename === 'node' || basename === 'node.exe';
|
||||
}
|
||||
|
||||
function validateRuntimePaths(metadata: OpenCodePluginMetadata): void {
|
||||
if (!validAgentSegment(metadata.agentId)) {
|
||||
throw new Error('OpenCode plugin metadata contains an invalid agentId');
|
||||
@@ -287,10 +279,6 @@ export const ElydoraAuditPlugin = async () => ({
|
||||
`);
|
||||
}
|
||||
|
||||
function errorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
|
||||
export function parseOpenCodePlugin(
|
||||
filePath: string,
|
||||
source: string,
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
OPENCODE_AUDIT_OPTIONS,
|
||||
buildOpenCodeMetadata,
|
||||
buildOpenCodePlugin,
|
||||
sameOpenCodeAgentId,
|
||||
} from './opencode-contract.js';
|
||||
import { sameAgentId } from './common.js';
|
||||
import type { OpenCodePluginFile, OpenCodeSources } from './opencode-io.js';
|
||||
import {
|
||||
commitManagedInstallation,
|
||||
@@ -29,14 +29,10 @@ import {
|
||||
const DISPLAY_NAME = 'OpenCode';
|
||||
const DIRECTORY_LABEL = 'OpenCode plugins directory';
|
||||
|
||||
export type OpenCodeRuntimePaths = ManagedRuntimePaths;
|
||||
export type PreparedOpenCodeInstallation = PreparedManagedInstallation;
|
||||
export type { RenameFile };
|
||||
|
||||
export async function preflightOpenCodeInstallation(
|
||||
config: InstallConfig,
|
||||
sources: OpenCodeSources,
|
||||
): Promise<OpenCodeRuntimePaths> {
|
||||
): Promise<ManagedRuntimePaths> {
|
||||
return preflightManagedInstallation({
|
||||
agentKey: AGENT_KEY,
|
||||
hookLocations: [{
|
||||
@@ -50,7 +46,7 @@ export async function preflightOpenCodeInstallation(
|
||||
export async function prepareOpenCodeInstallation(
|
||||
config: InstallConfig,
|
||||
sources: OpenCodeSources,
|
||||
): Promise<PreparedOpenCodeInstallation> {
|
||||
): Promise<PreparedManagedInstallation> {
|
||||
const paths = await preflightOpenCodeInstallation(config, sources);
|
||||
const metadata = buildOpenCodeMetadata(
|
||||
config.agentId,
|
||||
@@ -85,7 +81,7 @@ export async function prepareOpenCodeInstallation(
|
||||
}
|
||||
|
||||
export async function commitOpenCodeInstallation(
|
||||
prepared: PreparedOpenCodeInstallation,
|
||||
prepared: PreparedManagedInstallation,
|
||||
renameFile?: RenameFile,
|
||||
): Promise<void> {
|
||||
await commitManagedInstallation(prepared, renameFile);
|
||||
@@ -96,7 +92,7 @@ function ownedByAgent<TContract extends { readonly agentId: string }>(
|
||||
agentId?: string,
|
||||
): boolean {
|
||||
return file.contract !== undefined
|
||||
&& (agentId === undefined || sameOpenCodeAgentId(file.contract.agentId, agentId));
|
||||
&& (agentId === undefined || sameAgentId(file.contract.agentId, agentId));
|
||||
}
|
||||
|
||||
export async function prepareOpenCodeUninstall(
|
||||
|
||||
@@ -9,11 +9,10 @@ import {
|
||||
type OpenCodePaths,
|
||||
type OpenCodeRuntimeContract,
|
||||
} from './opencode-contract.js';
|
||||
import { errorMessage, hasCode, MAX_CONFIG_BYTES } from './common.js';
|
||||
import { inspectPhysicalDirectory, readPhysicalFile, type FileSnapshot } from './managed-files.js';
|
||||
import { managedRuntimeFilesExist } from './managed-runtime-status.js';
|
||||
|
||||
const MAX_PLUGIN_BYTES = 512 * 1024;
|
||||
|
||||
export interface OpenCodePluginFile<TContract> {
|
||||
readonly exists: boolean;
|
||||
readonly filePath: string;
|
||||
@@ -40,7 +39,7 @@ async function readPluginFile<TContract>(
|
||||
label: string,
|
||||
parse: (filePath: string, source: string) => TContract | undefined,
|
||||
): Promise<OpenCodePluginFile<TContract>> {
|
||||
const snapshot = await readPhysicalFile(filePath, label, MAX_PLUGIN_BYTES);
|
||||
const snapshot = await readPhysicalFile(filePath, label, MAX_CONFIG_BYTES);
|
||||
if (!snapshot) return { exists: false, filePath };
|
||||
return {
|
||||
exists: true,
|
||||
@@ -73,18 +72,14 @@ export function requireAvailableOpenCodePlugin(
|
||||
}
|
||||
}
|
||||
|
||||
function hasCode(error: unknown, code: string): boolean {
|
||||
return typeof error === 'object' && error !== null && 'code' in error && error.code === code;
|
||||
}
|
||||
|
||||
async function executableExists(filePath: string): Promise<boolean> {
|
||||
try {
|
||||
return (await fsp.stat(filePath)).isFile();
|
||||
} catch (error) {
|
||||
if (hasCode(error, 'ENOENT') || hasCode(error, 'ENOTDIR')) return false;
|
||||
throw new Error(`Inspect OpenCode Node.js runtime at ${filePath}: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`, { cause: error instanceof Error ? error : new Error(String(error)) });
|
||||
throw new Error(`Inspect OpenCode Node.js runtime at ${filePath}: ${errorMessage(error)}`, {
|
||||
cause: error instanceof Error ? error : new Error(String(error)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user