import { type ScaffoldReplaceReporter } from "./scaffold-copy.js"; /** * Injectable seam for the tar extraction step so the Windows symlink-failure * path is unit-testable without a real `tar` binary. */ export interface ExtractDeps { /** * Extract `tarPath` into `dest`. May reject even when every real file * extracted (e.g. an entry the OS refuses, such as the `core/.agents/skills` * symlink on Windows without Developer Mode) — callers MUST validate the * extracted content rather than trust whether this resolved. The default * (node-tar) treats such per-entry problems as non-fatal warnings. */ runTar?: (tarPath: string, dest: string) => Promise; ensureDir?: (target: string) => Promise; copy?: (source: string, destination: string, options: { overwrite: boolean; }) => Promise; /** * Clear target paths where the scaffold ships a symlink before the copy, so * `copy({ overwrite: true })` can install them (fs-extra refuses to overwrite * a plain file with a symlink). Injectable so the collision path is unit * testable; defaults to the real reconciler. */ reconcile?: (sourceRoot: string, targetDir: string, onReplace?: ScaffoldReplaceReporter) => Promise; } /** A local target-directory failure that must never be attributed to GitHub. */ export declare class TargetDirectoryInstallError extends Error { /** * Structured errno `code` of every install attempt behind this failure, in * attempt order, with `undefined` where an attempt carried no code. Retained * so a reporting policy (see sentry-before-send) can tell a pure * local-environment failure from a mixed or product one, and used here to pick * the remedy sentence. Defaults to the single `cause` when `attemptCauses` is * omitted, so existing call sites keep their one real code. */ readonly causeCodes: readonly (string | undefined)[]; constructor(targetDir: string, cause: unknown, attemptCauses?: readonly unknown[]); } /** * The archive downloaded but did not yield a usable hq-core scaffold — nothing * extracted, or no core.yaml after extraction. This is a LOCAL post-download * failure, never a GitHub or network fault, so buildFetchFailure routes it away * from the "Failed to fetch hq-core scaffold from GitHub." headline * (CREATE-HQ-2). Its name is deliberately NOT one of the dedicated * local-environment drop classes and it carries no drop-set cause codes, so the * Sentry reporting policy never mistakes it for a pure local-environment * condition — a scaffold-extraction failure keeps reporting, so a future * recurrence stays visible. */ export declare class ScaffoldExtractionError extends Error { constructor(message: string); } export type ScaffoldFetchFailureClass = "terminated" | "http_status" | "gh_unauthenticated" | "other"; export interface ScaffoldFetchPathDiagnostic { failureClass: ScaffoldFetchFailureClass; attempts: number; } export interface ScaffoldFetchDiagnostics { api: ScaffoldFetchPathDiagnostic; archive: ScaffoldFetchPathDiagnostic; gh: ScaffoldFetchPathDiagnostic; } /** A final scaffold-fetch failure with finite, path-specific reporting data. */ export declare class ScaffoldFetchError extends Error { readonly diagnostics: ScaffoldFetchDiagnostics; constructor(message: string, diagnostics: ScaffoldFetchDiagnostics); } /** Build the exact account/host scope used by the gh CLI fallback preflight. */ export declare function ghAuthenticationStatusArgs(env?: NodeJS.ProcessEnv): string[]; /** * Resolve a GitHub token from the environment: `GH_TOKEN` first, then * `GITHUB_TOKEN`. The first non-empty (whitespace-trimmed) value wins; an empty * or whitespace-only value is treated as absent. Returns `undefined` when no * usable token is set, in which case the REST calls stay anonymous exactly as * before. The value is only ever used to build an `Authorization` request * header — it is never interpolated into any log line, progress string, error * message, or Sentry payload. */ export declare function resolveGitHubToken(env?: NodeJS.ProcessEnv): string | undefined; /** * Build the header set for a GitHub REST request, adding * `Authorization: Bearer ` only when a non-empty token is supplied. This * is the single place a token becomes a header; the header value is never * echoed anywhere else. With no token the headers are byte-identical to the * previous anonymous request (Accept + client headers). */ export declare function buildGitHubApiHeaders(token?: string): Record; export interface ReleaseInfo { tag_name: string; tarball_url: string; } /** Injectable network config for the REST (api.github.com) legs. */ export interface GitHubApiConfig { /** Defaults to the global `fetch`. */ fetchImpl?: typeof fetch; /** Defaults to https://api.github.com — a seam so tests never hit live GitHub. */ apiBaseUrl?: string; /** * Bearer token to authenticate with. When omitted, resolved from the * environment (GH_TOKEN → GITHUB_TOKEN). Pass "" to force an anonymous * request regardless of the environment. */ token?: string; } /** Injectable network config for the REST-free public-archive legs. */ export interface PublicArchiveConfig { /** Defaults to the global `fetch`. */ fetchImpl?: typeof fetch; /** Defaults to https://github.com — a seam for the release-asset + redirect legs. */ webBaseUrl?: string; /** Defaults to https://codeload.github.com — a seam for the codeload leg. */ codeloadBaseUrl?: string; } /** Injectable seam for the fetch strategy's error-boundary tests. */ export interface FetchTemplateDeps { getRelease?: (tag?: string) => Promise; downloadAndExtract?: (tarballUrl: string, targetDir: string, onProgress?: (phase: string) => void, onReplace?: ScaffoldReplaceReporter) => Promise; /** * REST-free fallback: fetch the scaffold from github.com's published release * asset (with a codeload-by-tag second try) instead of api.github.com, so a * box whose anonymous REST budget is exhausted still installs (CREATE-HQ-2 * reopen). Runs as attempt 2, between the REST attempt and the gh CLI attempt. */ fetchViaPublicArchive?: (tag: string | undefined, targetDir: string, onProgress?: (phase: string) => void, onReplace?: ScaffoldReplaceReporter) => Promise<{ version: string; }>; extractViaGhCli?: (tag: string, targetDir: string, onProgress?: (phase: string) => void, onReplace?: ScaffoldReplaceReporter) => Promise; /** Injectable authentication preflight. False skips the gh transfer entirely. */ checkGhAuth?: () => Promise; } export declare function getLatestRelease(cfg?: GitHubApiConfig): Promise; export declare function getTagRelease(tag: string, cfg?: GitHubApiConfig): Promise; export declare function downloadAndExtractViaApi(tarballUrl: string, targetDir: string, onProgress?: (phase: string) => void, onReplace?: ScaffoldReplaceReporter, cfg?: GitHubApiConfig): Promise; /** * REST-free scaffold fetch: download the published release asset * (github.com//releases/download//hq-core-.tar.gz), falling * through to codeload (codeload.github.com//tar.gz/refs/tags/) when * the asset leg fails, then extract with the unchanged in-process runner. * Anonymous and outside the REST rate-limit budget, so it installs on a box * whose api.github.com budget is exhausted without ever shelling out to gh. * * The asset leg falling through to codeload covers BOTH a non-200 asset (the * `hq-core-.tar.gz` name may not hold for a given tag) AND a 200 asset * whose payload will not extract (truncated / invalid / non-scaffold), since * codeload's generated tag archive is name-independent and may still be valid. * A TargetDirectoryInstallError is a local fault, not an archive problem, so it * is rethrown immediately rather than retried against codeload. */ export declare function fetchViaPublicArchive(tag: string | undefined, targetDir: string, onProgress?: (phase: string) => void, onReplace?: ScaffoldReplaceReporter, cfg?: PublicArchiveConfig): Promise<{ version: string; }>; export declare function extractTemplateDirFromTar(tarPath: string, targetDir: string, onProgress?: (phase: string) => void, deps?: ExtractDeps, onReplace?: ScaffoldReplaceReporter): Promise; /** * Fetch the hq-core scaffold from GitHub and extract it into targetDir. * * hq-core is the standalone scaffold seed (formerly the `template/` subdir of * `indigoai-us/hq`). Its entire repository tree is the fresh-install content. * Rich add-ons ship separately as `@indigoai-us/hq-pack-*` content packs * installed by the next phase of create-hq via `hq install`. * * Strategy (each attempt early-rethrows a TargetDirectoryInstallError so a local * filesystem fault is never retried or mislabelled as a GitHub problem): * 1. GitHub REST API → download tarball_url (authenticated when GH_TOKEN / * GITHUB_TOKEN is set, which lifts the 60 req/hr/IP anonymous ceiling). * 2. REST-free public archive (release asset → codeload) — anonymous and * outside the REST budget, so a rate-limited box still installs here without * shelling out to gh (CREATE-HQ-2 reopen). * 3. Fallback: gh CLI (`gh api repos/indigoai-us/hq-core/tarball/{ref}`) — kept * last because it is the only path that can reach a private/enterprise scaffold. * 4. If all fail: throw with the three attempt causes enumerated + manual clone * instructions. * * Returns the version tag that was fetched. */ export declare function fetchTemplate(targetDir: string, tag?: string, onProgress?: (phase: string) => void, deps?: FetchTemplateDeps, onReplace?: ScaffoldReplaceReporter): Promise<{ version: string; }>; export declare function shellQuote(value: string): string; /** * Builds the terminal fetch error without mislabelling local failures as GitHub * ones. A target-directory filesystem fault becomes a TargetDirectoryInstallError; * a post-download extraction fault (from ANY attempt, including the public * archive) becomes a ScaffoldExtractionError; only a genuine network/API failure * keeps the "Failed to fetch hq-core scaffold from GitHub." headline. * * `publicArchiveError` is the (optional, trailing) cause of the REST-free * attempt. It sits after `platform` so the many existing two-attempt callers — * `buildFetchFailure(target, apiError, ghError[, platform])` — keep their exact * behaviour and message; the live three-attempt path passes it and gets the * enumerated three-cause message plus a GH_TOKEN hint when the REST cause was a * rate limit. */ export declare function buildFetchFailure(targetDir: string, apiError: unknown, ghError: unknown, platform?: NodeJS.Platform, publicArchiveError?: unknown, diagnostics?: ScaffoldFetchDiagnostics): Error; //# sourceMappingURL=fetch-template.d.ts.map