/** * Harness install as an async job with a log. * * - npm harnesses: `npm install -g @latest`. When the global prefix is * not writable (EACCES / EPERM), the install is retried once under the * user-owned prefix `/npm-global` (`npm install -g --prefix …`); * its `bin` dir is on the PATH of everything Crewly spawns * (harness-exec.utils). No sudo, no shell profile edits. * - Script harnesses (Antigravity CLI, which has no npm package): the vendor's * official installer is downloaded over https from exactly the registry URL * (`https://antigravity.google/cli/install.sh`; redirects refused, size * capped, must be a `#!` script), written to a private temp dir and run * with bash — the same as the documented `curl -fsSL … | bash`. The * installer verifies the binary's SHA-512 itself and puts `agy` in * `~/.local/bin`, which is on the harness PATH. When the binary is already * installed the job runs its own updater (`agy update`) instead. * * One install runs per harness at a time: starting a second one while the * first is running returns the running job. * * @module services/harness/harness-install.service */ import { type ScriptInstallSpec } from './harness-registry.js'; import { type HarnessId, type HarnessLogger, type InstallJob, type RunCommand } from './harness.types.js'; /** Downloads an installer script; rejects on any failure. */ export type FetchScript = (url: string) => Promise; /** * Whether a URL is one Crewly may download an installer from: https, and * exactly the URL the registry names. * * @param url - Candidate URL * @param spec - The harness's script install spec * @returns True when allowed */ export declare function isAllowedInstallerUrl(url: string, spec: ScriptInstallSpec): boolean; /** * Default installer download: https only, no redirects, size-capped. * * @param url - Installer URL * @returns Script text * @throws Error on a non-2xx status, a redirect, a timeout or an oversized body */ export declare const defaultFetchScript: FetchScript; /** Injectable dependencies. */ export interface HarnessInstallDeps { run?: RunCommand; env?: NodeJS.ProcessEnv; /** Creates the user prefix dir */ mkdirp?: (dir: string) => void; now?: () => number; idFactory?: () => string; logger?: HarnessLogger; /** Called after a successful install (e.g. to refresh status caches) */ onInstalled?: (harnessId: HarnessId) => void; /** Downloads a vendor installer script (script harnesses) */ fetchScript?: FetchScript; /** PATH lookup of an installed harness binary (script harnesses update in place) */ resolveCommand?: (command: string) => string | null; } /** Error with a machine-readable code, for the REST layer. */ export declare class HarnessInstallError extends Error { readonly code: 'unknown_harness' | 'job_not_found'; /** * @param code - `unknown_harness` or `job_not_found` * @param message - Human-readable message */ constructor(code: 'unknown_harness' | 'job_not_found', message: string); } /** * Whether npm output says the global prefix is not writable. * * @param output - npm stdout + stderr * @returns True for EACCES / EPERM / permission denied */ export declare function isPermissionError(output: string): boolean; /** Runs and tracks harness install jobs. */ export declare class HarnessInstallService { private readonly run; private readonly env; private readonly mkdirp; private readonly now; private readonly idFactory; private readonly logger; private readonly onInstalled?; private readonly fetchScript; private readonly resolveCommand; private readonly jobs; /** * @param deps - Injectable dependencies (all optional) */ constructor(deps?: HarnessInstallDeps); /** * Start installing (or updating) a harness. * * @param harnessId - Harness id * @returns The new job, or the job already running for this harness * @throws HarnessInstallError `unknown_harness` */ startInstall(harnessId: string): InstallJob; /** * Look up a job. * * @param jobId - Job id * @returns The job * @throws HarnessInstallError `job_not_found` */ getJob(jobId: string): InstallJob; /** * Wait for a job to finish (CLI). * * @param jobId - Job id * @returns The finished job * @throws HarnessInstallError `job_not_found` */ waitForJob(jobId: string): Promise; /** * Run the install for a harness and record the outcome. * * @param record - Job record (mutated) * @param def - Harness definition * @returns The finished job */ private execute; /** * Install with the vendor's official script, or update in place when the * binary is already there. * * @param record - Job record (mutated) * @param def - Harness definition * @param spec - Script install spec * @returns True on success */ private installScript; /** * Run npm, falling back to the user prefix on a permission error. * * @param record - Job record (mutated) * @param npmPackage - Package to install * @returns True on success */ private installNpm; /** * Append to a job log, keeping only the tail. * * @param record - Job record * @param text - Text to append */ private append; /** Forget finished jobs older than the retention window. */ private pruneFinished; /** * Public view of a job. * * @param record - Job record * @returns Contract shape */ private toPublic; } //# sourceMappingURL=harness-install.service.d.ts.map