@oh-my-pi/pi-utils 15.1.0 → 15.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ export declare class AbortError extends Error {
2
+ constructor(signal: AbortSignal);
3
+ }
4
+ /**
5
+ * Creates an abortable stream from a given stream and signal.
6
+ *
7
+ * @param stream - The stream to make abortable
8
+ * @param signal - The signal to abort the stream
9
+ * @returns The abortable stream
10
+ */
11
+ export declare function createAbortableStream<T>(stream: ReadableStream<T>, signal?: AbortSignal): ReadableStream<T>;
12
+ /**
13
+ * Runs a promise-returning function (`pr`). If the given AbortSignal is aborted before or during
14
+ * execution, the promise is rejected with a standard error.
15
+ *
16
+ * @param signal - Optional AbortSignal to cancel the operation
17
+ * @param pr - Function returning a promise to run
18
+ * @returns Promise resolving as `pr` would, or rejecting on abort
19
+ */
20
+ export declare function untilAborted<T>(signal: AbortSignal | undefined | null, pr: Promise<T> | (() => Promise<T>)): Promise<T>;
21
+ /**
22
+ * Memoizes a function with no arguments, calling it once and caching the result.
23
+ *
24
+ * @param fn - Function to be called once
25
+ * @returns A function that returns the cached result of `fn`
26
+ */
27
+ export declare function once<T>(fn: () => T): () => T;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Wrap a promise with a timeout and optional abort signal.
3
+ * Rejects with the given message if the timeout fires first.
4
+ * Cleans up all listeners on settlement.
5
+ */
6
+ export declare function withTimeout<T>(promise: Promise<T>, ms: number, message: string, signal?: AbortSignal): Promise<T>;
@@ -0,0 +1,117 @@
1
+ export interface FlagDescriptor<K extends "string" | "boolean" | "integer" = "string" | "boolean" | "integer"> {
2
+ kind: K;
3
+ description?: string;
4
+ char?: string;
5
+ default?: unknown;
6
+ multiple?: boolean;
7
+ options?: readonly string[];
8
+ required?: boolean;
9
+ }
10
+ export interface ArgDescriptor {
11
+ kind: "string";
12
+ description?: string;
13
+ required?: boolean;
14
+ multiple?: boolean;
15
+ options?: readonly string[];
16
+ }
17
+ interface FlagInput {
18
+ description?: string;
19
+ char?: string;
20
+ default?: unknown;
21
+ multiple?: boolean;
22
+ options?: readonly string[];
23
+ required?: boolean;
24
+ }
25
+ interface ArgInput {
26
+ description?: string;
27
+ required?: boolean;
28
+ multiple?: boolean;
29
+ options?: readonly string[];
30
+ }
31
+ /** Builders that match the `Flags.*()` / `Args.*()` API from oclif. */
32
+ export declare const Flags: {
33
+ string<T extends FlagInput>(opts?: T): FlagDescriptor<"string"> & T;
34
+ boolean<T_1 extends FlagInput>(opts?: T_1): FlagDescriptor<"boolean"> & T_1;
35
+ integer<T_2 extends FlagInput & {
36
+ default?: number;
37
+ }>(opts?: T_2): FlagDescriptor<"integer"> & T_2;
38
+ };
39
+ export declare const Args: {
40
+ string<T extends ArgInput>(opts?: T): ArgDescriptor & T;
41
+ };
42
+ type FlagValue<D extends FlagDescriptor> = D["kind"] extends "boolean" ? D extends {
43
+ default: boolean;
44
+ } ? boolean : boolean | undefined : D["kind"] extends "integer" ? D extends {
45
+ default: number;
46
+ } ? number : number | undefined : D extends {
47
+ multiple: true;
48
+ } ? string[] | undefined : string | undefined;
49
+ type ArgValue<D extends ArgDescriptor> = D extends {
50
+ multiple: true;
51
+ } ? string[] | undefined : string | undefined;
52
+ type FlagValues<T extends Record<string, FlagDescriptor>> = {
53
+ [K in keyof T]: FlagValue<T[K]>;
54
+ };
55
+ type ArgValues<T extends Record<string, ArgDescriptor>> = {
56
+ [K in keyof T]: ArgValue<T[K]>;
57
+ };
58
+ export interface ParseOutput<F extends Record<string, FlagDescriptor> = Record<string, FlagDescriptor>, A extends Record<string, ArgDescriptor> = Record<string, ArgDescriptor>> {
59
+ flags: FlagValues<F>;
60
+ args: ArgValues<A>;
61
+ argv: string[];
62
+ }
63
+ export interface CommandCtor {
64
+ new (argv: string[], config: CliConfig): Command;
65
+ description?: string;
66
+ hidden?: boolean;
67
+ strict?: boolean;
68
+ aliases?: string[];
69
+ examples?: string[];
70
+ flags?: Record<string, FlagDescriptor>;
71
+ args?: Record<string, ArgDescriptor>;
72
+ }
73
+ /** Configuration passed to every command instance and help renderers. */
74
+ export interface CliConfig {
75
+ bin: string;
76
+ version: string;
77
+ /** All registered commands keyed by their canonical name. */
78
+ commands: Map<string, CommandCtor>;
79
+ }
80
+ /** Minimal Command base matching the oclif surface we use. */
81
+ export declare abstract class Command {
82
+ argv: string[];
83
+ config: CliConfig;
84
+ constructor(argv: string[], config: CliConfig);
85
+ abstract run(): Promise<void>;
86
+ /**
87
+ * Parse argv against the static `flags` and `args` declared on the
88
+ * concrete command class. Returns a typed `{ flags, args, argv }` object.
89
+ */
90
+ parse<C extends CommandCtor>(_Cmd: C): Promise<ParseOutput<NonNullable<C["flags"]> extends Record<string, FlagDescriptor> ? NonNullable<C["flags"]> : Record<string, FlagDescriptor>, NonNullable<C["args"]> extends Record<string, ArgDescriptor> ? NonNullable<C["args"]> : Record<string, ArgDescriptor>>>;
91
+ }
92
+ /** Render full root help: header, default command details, subcommand list. */
93
+ export declare function renderRootHelp(config: CliConfig): void;
94
+ /** Render help for a single command. */
95
+ export declare function renderCommandHelp(bin: string, id: string, Cmd: CommandCtor): void;
96
+ /** A lazily-loaded command: canonical name, loader, and optional aliases. */
97
+ export interface CommandEntry {
98
+ name: string;
99
+ load: () => Promise<CommandCtor>;
100
+ aliases?: string[];
101
+ }
102
+ export interface RunOptions {
103
+ bin: string;
104
+ version: string;
105
+ argv: string[];
106
+ commands: CommandEntry[];
107
+ /** Custom help renderer. Receives fully-populated config. */
108
+ help?: (config: CliConfig) => Promise<void> | void;
109
+ }
110
+ /**
111
+ * Main entry point — replaces `run()` from @oclif/core.
112
+ *
113
+ * Each command is explicitly registered with a lazy loader.
114
+ * No filesystem scanning, no plugin system, no package.json reading.
115
+ */
116
+ export declare function run(opts: RunOptions): Promise<void>;
117
+ export {};
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Color manipulation utilities for hex colors.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { hexToHsv, hsvToHex } from "@oh-my-pi/pi-utils";
7
+ *
8
+ * // Work with HSV directly
9
+ *
10
+ * // Or work with HSV directly
11
+ * const hsv = hexToHsv("#4ade80");
12
+ * hsv.h = (hsv.h + 90) % 360;
13
+ * const newHex = hsvToHex(hsv);
14
+ * ```
15
+ */
16
+ export interface HSV {
17
+ /** Hue in degrees (0-360) */
18
+ h: number;
19
+ /** Saturation (0-1) */
20
+ s: number;
21
+ /** Value/brightness (0-1) */
22
+ v: number;
23
+ }
24
+ export interface RGB {
25
+ /** Red (0-255) */
26
+ r: number;
27
+ /** Green (0-255) */
28
+ g: number;
29
+ /** Blue (0-255) */
30
+ b: number;
31
+ }
32
+ /**
33
+ * Parse a hex color string to RGB.
34
+ * Supports #RGB, #RRGGBB formats.
35
+ */
36
+ export declare function hexToRgb(hex: string): RGB;
37
+ /**
38
+ * Convert RGB to hex color string.
39
+ */
40
+ export declare function rgbToHex(rgb: RGB): string;
41
+ /**
42
+ * Convert RGB to HSV.
43
+ */
44
+ export declare function rgbToHsv(rgb: RGB): HSV;
45
+ /**
46
+ * Convert HSV to RGB.
47
+ */
48
+ export declare function hsvToRgb(hsv: HSV): RGB;
49
+ /**
50
+ * Convert hex color to HSV.
51
+ */
52
+ export declare function hexToHsv(hex: string): HSV;
53
+ /**
54
+ * Convert HSV to hex color.
55
+ */
56
+ export declare function hsvToHex(hsv: HSV): string;
57
+ /**
58
+ * Shift the hue of a hex color by a given number of degrees.
59
+ */
60
+ export declare function shiftHue(hex: string, degrees: number): string;
61
+ export interface HSVAdjustment {
62
+ /** Hue shift in degrees (additive) */
63
+ h?: number;
64
+ /** Saturation multiplier */
65
+ s?: number;
66
+ /** Value/brightness multiplier */
67
+ v?: number;
68
+ }
69
+ /**
70
+ * Adjust HSV components of a hex color.
71
+ *
72
+ * @param hex - Hex color string (#RGB or #RRGGBB)
73
+ * @param adj - Adjustments: h is additive degrees, s and v are multipliers
74
+ * @returns New hex color string
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * // Shift hue +60°, reduce saturation to 71%
79
+ * adjustHsv("#00ff88", { h: 60, s: 0.71 }) // "#4a9eff"
80
+ * ```
81
+ */
82
+ export declare function adjustHsv(hex: string, adj: HSVAdjustment): string;
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Centralized path helpers for omp config directories.
3
+ *
4
+ * Uses PI_CONFIG_DIR (default ".omp") for the config root and
5
+ * PI_CODING_AGENT_DIR to override the agent directory.
6
+ *
7
+ * On Linux, if XDG_DATA_HOME / XDG_STATE_HOME / XDG_CACHE_HOME environment
8
+ * variables are set, paths are redirected to XDG-compliant locations under
9
+ * $XDG_*_HOME/omp/. This requires running `omp config migrate` first to
10
+ * move data to the new locations. No filesystem existence checks are performed
11
+ * — if the env var is set, omp trusts that the migration has been done.
12
+ */
13
+ /** App name (e.g. "omp") */
14
+ export declare const APP_NAME: string;
15
+ /** Config directory name (e.g. ".omp") */
16
+ export declare const CONFIG_DIR_NAME: string;
17
+ /** Version (e.g. "1.0.0") */
18
+ export declare const VERSION: string;
19
+ /** Minimum Bun version */
20
+ export declare const MIN_BUN_VERSION: string;
21
+ export declare function resolveEquivalentPath(inputPath: string): string;
22
+ export declare function normalizePathForComparison(inputPath: string): string;
23
+ export declare function pathIsWithin(root: string, candidate: string): boolean;
24
+ export declare function relativePathWithinRoot(root: string, candidate: string): string | null;
25
+ /** Get the project directory. */
26
+ export declare function getProjectDir(): string;
27
+ /** Set the project directory. */
28
+ export declare function setProjectDir(dir: string): void;
29
+ /** Get the config directory name relative to home (e.g. ".omp" or PI_CONFIG_DIR override). */
30
+ export declare function getConfigDirName(): string;
31
+ /** Get the config agent directory name relative to home (e.g. ".omp/agent" or PI_CONFIG_DIR + "/agent"). */
32
+ export declare function getConfigAgentDirName(): string;
33
+ /** Get the config root directory (~/.omp). */
34
+ export declare function getConfigRootDir(): string;
35
+ /** Set the coding agent directory. Creates a fresh resolver, invalidating all cached paths. */
36
+ export declare function setAgentDir(dir: string): void;
37
+ /** Get the agent config directory (~/.omp/agent). */
38
+ export declare function getAgentDir(): string;
39
+ /** Get the project-local config directory (.omp). */
40
+ export declare function getProjectAgentDir(cwd?: string): string;
41
+ /** Get the reports directory (~/.omp/reports). */
42
+ export declare function getReportsDir(): string;
43
+ /** Get the logs directory (~/.omp/logs). */
44
+ export declare function getLogsDir(): string;
45
+ /** Get the path to a dated log file (~/.omp/logs/omp.YYYY-MM-DD.log). */
46
+ export declare function getLogPath(date?: Date): string;
47
+ /**
48
+ * Get the plugins directory (~/.omp/plugins or its XDG equivalent).
49
+ *
50
+ * No-arg form (production callers) goes through the XDG-aware DirResolver so
51
+ * reads and writes always agree. The optional `home` parameter is for test
52
+ * isolation: when it differs from `os.homedir()` it short-circuits the resolver
53
+ * and returns `<home>/<configDir>/plugins` so tests with a temp HOME get a
54
+ * deterministic path. Passing `os.homedir()` explicitly is identical to the
55
+ * no-arg form — XDG semantics are preserved.
56
+ */
57
+ export declare function getPluginsDir(home?: string): string;
58
+ /** Where npm installs packages (~/.omp/plugins/node_modules). */
59
+ export declare function getPluginsNodeModules(): string;
60
+ /** Plugin manifest (~/.omp/plugins/package.json). */
61
+ export declare function getPluginsPackageJson(): string;
62
+ /** Plugin lock file (~/.omp/plugins/omp-plugins.lock.json). */
63
+ export declare function getPluginsLockfile(): string;
64
+ /** Get the remote mount directory (~/.omp/remote). */
65
+ export declare function getRemoteDir(): string;
66
+ /** Get the PR worktrees directory (~/.omp/wt). */
67
+ export declare function getWorktreesDir(): string;
68
+ /** Get the SSH control socket directory (~/.omp/ssh-control). */
69
+ export declare function getSshControlDir(): string;
70
+ /** Get the remote host info directory (~/.omp/remote-host). */
71
+ export declare function getRemoteHostDir(): string;
72
+ /** Get the managed Python venv directory (~/.omp/python-env). */
73
+ export declare function getPythonEnvDir(): string;
74
+ /** Get the shared Python gateway state directory (~/.omp/agent/python-gateway; XDG default: $XDG_STATE_HOME/omp/python-gateway). */
75
+ export declare function getPythonGatewayDir(): string;
76
+ /** Get the puppeteer sandbox directory (~/.omp/puppeteer). */
77
+ export declare function getPuppeteerDir(): string;
78
+ /** Get the worktree base directory (~/.omp/wt). */
79
+ export declare function getWorktreeBaseDir(): string;
80
+ /** Get the path to a worktree directory (~/.omp/wt/<project>/<id>). */
81
+ export declare function getWorktreeDir(encodedProject: string, id: string): string;
82
+ /** Get the GPU cache path (~/.omp/gpu_cache.json). */
83
+ export declare function getGpuCachePath(): string;
84
+ /**
85
+ * Get the GitHub view cache database path (~/.omp/cache/github-cache.db).
86
+ * Honors the `OMP_GITHUB_CACHE_DB` env var when set so tests can isolate the
87
+ * cache file without touching the rest of the config root.
88
+ */
89
+ export declare function getGithubCacheDbPath(): string;
90
+ /** Get the natives directory (~/.omp/natives). */
91
+ export declare function getNativesDir(): string;
92
+ /** Get the stats database path (~/.omp/stats.db). */
93
+ export declare function getStatsDbPath(): string;
94
+ /** Get the autoresearch state directory (~/.omp/autoresearch). */
95
+ export declare function getAutoresearchDir(): string;
96
+ /** Get the per-project autoresearch state directory (~/.omp/autoresearch/<encoded-project>). */
97
+ export declare function getAutoresearchProjectDir(encodedProject: string): string;
98
+ /** Get the per-project autoresearch SQLite database path (~/.omp/autoresearch/<encoded-project>.db). */
99
+ export declare function getAutoresearchDbPath(encodedProject: string): string;
100
+ /** Get the per-run artifact directory (~/.omp/autoresearch/<encoded-project>/runs/<runId>). */
101
+ export declare function getAutoresearchRunDir(encodedProject: string, runId: number): string;
102
+ /** Get the path to agent.db (SQLite database for settings and auth storage). */
103
+ export declare function getAgentDbPath(agentDir?: string): string;
104
+ /** Get the path to history.db (SQLite database for session history). */
105
+ export declare function getHistoryDbPath(agentDir?: string): string;
106
+ /** Get the path to models.db (model cache database). */
107
+ export declare function getModelDbPath(agentDir?: string): string;
108
+ /** Get the sessions directory (~/.omp/agent/sessions). */
109
+ export declare function getSessionsDir(agentDir?: string): string;
110
+ /** Get the content-addressed blob store directory (~/.omp/agent/blobs). */
111
+ export declare function getBlobsDir(agentDir?: string): string;
112
+ /** Get the custom themes directory (~/.omp/agent/themes). */
113
+ export declare function getCustomThemesDir(agentDir?: string): string;
114
+ /** Get the tools directory (~/.omp/agent/tools). */
115
+ export declare function getToolsDir(agentDir?: string): string;
116
+ /** Get the slash commands directory (~/.omp/agent/commands). */
117
+ export declare function getCommandsDir(agentDir?: string): string;
118
+ /** Get the prompts directory (~/.omp/agent/prompts). */
119
+ export declare function getPromptsDir(agentDir?: string): string;
120
+ /** Get the user-level Python modules directory (~/.omp/agent/modules). */
121
+ export declare function getAgentModulesDir(agentDir?: string): string;
122
+ /** Get the memories directory (~/.omp/agent/memories). */
123
+ export declare function getMemoriesDir(agentDir?: string): string;
124
+ /** Get the terminal sessions directory (~/.omp/agent/terminal-sessions). */
125
+ export declare function getTerminalSessionsDir(agentDir?: string): string;
126
+ /** Get the crash log path (~/.omp/agent/omp-crash.log). */
127
+ export declare function getCrashLogPath(agentDir?: string): string;
128
+ /** Get the debug log path (~/.omp/agent/omp-debug.log). */
129
+ export declare function getDebugLogPath(agentDir?: string): string;
130
+ /** Get the project-level Python modules directory (.omp/modules). */
131
+ export declare function getProjectModulesDir(cwd?: string): string;
132
+ /** Get the project-level prompts directory (.omp/prompts). */
133
+ export declare function getProjectPromptsDir(cwd?: string): string;
134
+ /** Get the project-level plugin overrides path (.omp/plugin-overrides.json). */
135
+ export declare function getProjectPluginOverridesPath(cwd?: string): string;
136
+ /** Get the primary MCP config file path (first candidate). */
137
+ export declare function getMCPConfigPath(scope: "user" | "project", cwd?: string): string;
138
+ /** Get the SSH config file path. */
139
+ export declare function getSSHConfigPath(scope: "user" | "project", cwd?: string): string;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Intentional re-export of Bun.env.
3
+ *
4
+ * All users should import this env module (import { $env } from "@oh-my-pi/pi-utils")
5
+ * before using environment variables. This ensures that .env files have been loaded and
6
+ * overrides (project, home) have been applied, so $env always reflects the correct values.
7
+ */
8
+ export declare const $env: Record<string, string>;
9
+ /**
10
+ * Resolve the first environment variable value from the given keys.
11
+ * @param keys - The keys to resolve.
12
+ * @returns The first environment variable value, or undefined if no value is found.
13
+ */
14
+ export declare function $pickenv(...keys: string[]): string | undefined;
15
+ /**
16
+ * Parses a positive decimal integer from `$env[name]`.
17
+ * Empty, invalid, NaN, zero, or negative values return `defaultValue`.
18
+ */
19
+ export declare function $envpos(name: string, defaultValue: number): number;
20
+ /** True when `BUN_ENV` or `NODE_ENV` is the string `test`. */
21
+ export declare function isBunTestRuntime(): boolean;
22
+ /**
23
+ * True when this code is running inside a `bun build --compile` standalone
24
+ * binary. Detects via the embedded virtual-filesystem path markers
25
+ * (`$bunfs`, `~BUN`, or its URL-encoded form `%7EBUN`) in `import.meta.url`,
26
+ * which Bun rewrites for every module bundled into the executable. The
27
+ * `PI_COMPILED` env var (set by the build script's `--define`) is checked
28
+ * first for cheap fast-path detection.
29
+ */
30
+ export declare function isCompiledBinary(): boolean;
31
+ export declare function $flag(name: string, def?: boolean): boolean;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Server-suggested retry delay extraction. Merges the patterns historically used
3
+ * by the OpenAI Codex and Google Gemini retry helpers.
4
+ *
5
+ * Header sources (checked in order):
6
+ * - `Retry-After` (numeric seconds, or HTTP date)
7
+ * - `x-ratelimit-reset` (Unix epoch seconds)
8
+ * - `x-ratelimit-reset-after` (seconds)
9
+ *
10
+ * Body patterns:
11
+ * - `Your quota will reset after 18h31m10s` / `10m15s` / `39s`
12
+ * - `Please retry in 250ms` / `Please retry in 12s`
13
+ * - `"retryDelay": "34.074824224s"` (JSON error detail field)
14
+ * - `try again in 250ms` / `try again in 12s` / `try again in 12sec`
15
+ *
16
+ * Returns `undefined` if no signal is found.
17
+ */
18
+ export declare function extractRetryHint(source: Response | Headers | null | undefined, body?: string): number | undefined;
19
+ export interface FetchWithRetryOptions extends RequestInit {
20
+ /** Total fetch attempts (initial + retries). Default `5`. */
21
+ maxAttempts?: number;
22
+ /**
23
+ * Per-delay cap. Server-provided `Retry-After` hints exceeding this return
24
+ * the current response immediately — caller deals with the `!response.ok`.
25
+ * Default `60_000`.
26
+ */
27
+ maxDelayMs?: number;
28
+ /**
29
+ * Fallback delay schedule when no server hint is present. Number, array
30
+ * (indexed by attempt, clamped to last), or function. Default exponential
31
+ * `500ms * 2 ** attempt` capped at `maxDelayMs`.
32
+ */
33
+ defaultDelayMs?: number | readonly number[] | ((attempt: number) => number);
34
+ /**
35
+ * Optional per-attempt overlay merged into the base `RequestInit` each try.
36
+ * Headers from the overlay shallow-merge over the base. Useful for auth
37
+ * token refresh or user-agent rotation.
38
+ */
39
+ prepareInit?: (attempt: number) => RequestInit | Promise<RequestInit>;
40
+ /**
41
+ * Optional `fetch` implementation override. Defaults to `globalThis.fetch`.
42
+ * Useful for routing requests through a proxy, instrumented transport, or
43
+ * mock during tests.
44
+ */
45
+ fetch?: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
46
+ }
47
+ /**
48
+ * Fetch with bounded retries and sensible defaults. Retries on any
49
+ * `isRetryableStatus` (5xx, 408, 429) and on transient network errors. Server
50
+ * `Retry-After`/quota hints are honoured up to `maxDelayMs`; a hint that exceeds
51
+ * the cap returns the current response so the caller can fail fast. Aborts on
52
+ * `init.signal` propagate as `"Request was aborted"`.
53
+ *
54
+ * The caller is responsible for inspecting `!response.ok` once the call returns.
55
+ */
56
+ export declare function fetchWithRetry(url: string | URL | ((attempt: number) => string | URL), options?: FetchWithRetryOptions): Promise<Response>;
57
+ /**
58
+ * Inspect an arbitrary error value (or its `cause` chain, up to depth 2) for an
59
+ * HTTP status code. Reads `status`, `statusCode`, and `response.status` fields,
60
+ * coerces string values, and falls back to scanning the error message for
61
+ * common patterns like `error (429)` or `HTTP 503`.
62
+ */
63
+ export declare function extractHttpStatusFromError(error: unknown): number | undefined;
64
+ /**
65
+ * `true` if the given HTTP status code is one we treat as transient: 408
66
+ * (Request Timeout), 429 (Too Many Requests), or any 5xx (server error).
67
+ */
68
+ export declare function isRetryableStatus(status: number): boolean;
69
+ /**
70
+ * `true` if the message describes an unexpected socket closure — Bun and some
71
+ * proxies surface these for any HTTP/2 stream reset.
72
+ */
73
+ export declare function isUnexpectedSocketCloseMessage(message: string): boolean;
74
+ /**
75
+ * Identify errors that should be retried: aborts/timeouts in the error name or
76
+ * message, retryable HTTP statuses (see `isRetryableStatus`), unexpected socket
77
+ * closes, and the standard transient phrases. 4xx statuses other than 408/429
78
+ * and validation-shaped messages short-circuit to `false`.
79
+ */
80
+ export declare function isRetryableError(error: unknown): boolean;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Format a duration in milliseconds to a short human-readable string.
3
+ * Examples: "123ms", "1.5s", "30m15s", "2h30m", "3d2h"
4
+ */
5
+ export declare function formatDuration(ms: number): string;
6
+ /**
7
+ * Format a number with K/M/B suffix for compact display.
8
+ * Uses 1 decimal for small leading digits when non-zero, rounded otherwise.
9
+ * Examples: "999", "1K", "1.5K", "25K", "1M", "1.5M", "25M", "1.5B"
10
+ */
11
+ export declare function formatNumber(n: number): string;
12
+ /**
13
+ * Format a byte count to a human-readable string.
14
+ * Examples: "512B", "1.5KB", "2.3MB", "1.2GB"
15
+ */
16
+ export declare function formatBytes(bytes: number): string;
17
+ /**
18
+ * Truncate a string to maxLen characters, appending an ellipsis if truncated.
19
+ * For display-width-aware truncation (terminals), use truncateToWidth from @oh-my-pi/pi-tui.
20
+ */
21
+ export declare function truncate(str: string, maxLen: number, ellipsis?: string): string;
22
+ /**
23
+ * Format count with pluralized label (e.g., "3 files", "1 error").
24
+ */
25
+ export declare function formatCount(label: string, count: number): string;
26
+ /**
27
+ * Format age from seconds to human-readable string.
28
+ */
29
+ export declare function formatAge(ageSeconds: number | null | undefined): string;
30
+ /**
31
+ * Pluralize a label based on the count.
32
+ */
33
+ export declare function pluralize(label: string, count: number): string;
34
+ /**
35
+ * Format a ratio as a percentage.
36
+ */
37
+ export declare function formatPercent(ratio: number): string;
@@ -0,0 +1,25 @@
1
+ export declare class FrontmatterError extends Error {
2
+ readonly source?: unknown;
3
+ constructor(error: Error, source?: unknown);
4
+ toString(): string;
5
+ }
6
+ export interface FrontmatterOptions {
7
+ /** Source of the content (alias: source) */
8
+ location?: unknown;
9
+ /** Source of the content (alias for location) */
10
+ source?: unknown;
11
+ /** Fallback frontmatter values */
12
+ fallback?: Record<string, unknown>;
13
+ /** Normalize the content */
14
+ normalize?: boolean;
15
+ /** Level of error handling */
16
+ level?: "off" | "warn" | "fatal";
17
+ }
18
+ /**
19
+ * Parse YAML frontmatter from markdown content
20
+ * Returns { frontmatter, body } where body has frontmatter stripped
21
+ */
22
+ export declare function parseFrontmatter(content: string, options?: FrontmatterOptions): {
23
+ frontmatter: Record<string, unknown>;
24
+ body: string;
25
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Type-safe filesystem error handling utilities.
3
+ *
4
+ * Use these to check error codes without string matching on messages:
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { isEnoent, isFsError } from "@oh-my-pi/pi-utils";
9
+ *
10
+ * try {
11
+ * return await Bun.file(path).text();
12
+ * } catch (err) {
13
+ * if (isEnoent(err)) return null;
14
+ * throw err;
15
+ * }
16
+ * ```
17
+ */
18
+ export interface FsError extends Error {
19
+ code: string;
20
+ errno?: number;
21
+ syscall?: string;
22
+ path?: string;
23
+ }
24
+ export declare function isFsError(err: unknown): err is FsError;
25
+ export declare function isEnoent(err: unknown): err is FsError;
26
+ export declare function isEacces(err: unknown): err is FsError;
27
+ export declare function isEisdir(err: unknown): err is FsError;
28
+ export declare function isEnotdir(err: unknown): err is FsError;
29
+ export declare function isEexist(err: unknown): err is FsError;
30
+ export declare function isEnotempty(err: unknown): err is FsError;
31
+ export declare function hasFsCode(err: unknown, code: string): err is FsError;
@@ -0,0 +1,28 @@
1
+ export interface GlobPathsOptions {
2
+ /** Base directory for glob patterns. Defaults to getProjectDir(). */
3
+ cwd?: string;
4
+ /** Glob exclusion patterns. */
5
+ exclude?: string[];
6
+ /** Abort signal to cancel the glob. */
7
+ signal?: AbortSignal;
8
+ /** Timeout in milliseconds for the glob operation. */
9
+ timeoutMs?: number;
10
+ /** Include dotfiles when true. */
11
+ dot?: boolean;
12
+ /** Only return files (skip directories). Default: true. */
13
+ onlyFiles?: boolean;
14
+ /** Respect .gitignore files when true. Walks up directory tree to find all applicable .gitignore files. */
15
+ gitignore?: boolean;
16
+ }
17
+ /**
18
+ * Load .gitignore patterns from a directory and its parents.
19
+ * Walks up the directory tree to find all applicable .gitignore files.
20
+ * Returns glob-compatible exclude patterns.
21
+ */
22
+ export declare function loadGitignorePatterns(baseDir: string): Promise<string[]>;
23
+ /**
24
+ * Resolve filesystem paths matching glob patterns with optional exclude filters.
25
+ * Returns paths relative to the provided cwd (or getProjectDir()).
26
+ * Errors and abort/timeouts are surfaced to the caller.
27
+ */
28
+ export declare function globPaths(patterns: string | string[], options?: GlobPathsOptions): Promise<string[]>;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Intercept `globalThis.fetch` with a middleware-style handler.
3
+ *
4
+ * Returns a `Disposable` so callers can use `using` for automatic cleanup:
5
+ *
6
+ * ```ts
7
+ * using _hook = hookFetch((input, init, next) => {
8
+ * if (shouldIntercept(input)) {
9
+ * return new Response("mocked");
10
+ * }
11
+ * return next(input, init);
12
+ * });
13
+ * ```
14
+ */
15
+ export type FetchHandler = (input: string | URL | Request, init: RequestInit | undefined, next: typeof fetch) => Response | Promise<Response>;
16
+ export declare function hookFetch(handler: FetchHandler): Disposable;
@@ -0,0 +1,28 @@
1
+ export { createAbortableStream, once, untilAborted } from "./abortable";
2
+ export * from "./async";
3
+ export * from "./color";
4
+ export * from "./dirs";
5
+ export * from "./env";
6
+ export * from "./fetch-retry";
7
+ export * from "./format";
8
+ export * from "./frontmatter";
9
+ export * from "./fs-error";
10
+ export * from "./glob";
11
+ export * from "./hook-fetch";
12
+ export * from "./json";
13
+ export * as logger from "./logger";
14
+ export * from "./mermaid-ascii";
15
+ export * from "./mime";
16
+ export * from "./peek-file";
17
+ export * as postmortem from "./postmortem";
18
+ export * as procmgr from "./procmgr";
19
+ export * as prompt from "./prompt";
20
+ export * as ptree from "./ptree";
21
+ export { AbortError, ChildProcess, Exception, NonZeroExitError } from "./ptree";
22
+ export * from "./snowflake";
23
+ export * from "./stream";
24
+ export * from "./tab-spacing";
25
+ export * from "./temp";
26
+ export * from "./type-guards";
27
+ export * from "./which";
28
+ export declare function structuredCloneJSON<T>(value: T): T;
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Try to parse JSON, returning null on failure.
3
+ */
4
+ export declare function tryParseJson<T = unknown>(content: string): T | null;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Log an error message.
3
+ * @param message - The message to log.
4
+ * @param context - The context to log.
5
+ */
6
+ export declare function error(message: string, context?: Record<string, unknown>): void;
7
+ /**
8
+ * Log a warning message.
9
+ * @param message - The message to log.
10
+ * @param context - The context to log.
11
+ */
12
+ export declare function warn(message: string, context?: Record<string, unknown>): void;
13
+ /**
14
+ * Log a debug message.
15
+ * @param message - The message to log.
16
+ * @param context - The context to log.
17
+ */
18
+ export declare function debug(message: string, context?: Record<string, unknown>): void;
19
+ /**
20
+ * Print collected timings as an indented tree.
21
+ * Each span shows wall duration; parents with children also show "(self)" for unattributed time.
22
+ * Sibling spans are sorted by start time. Spans whose intervals overlap with siblings ran in parallel.
23
+ */
24
+ export declare function printTimings(): void;
25
+ /**
26
+ * Begin recording startup timings under a new root span.
27
+ * Idempotent: a second call while already recording is a no-op so that side-effect
28
+ * starters (see module-timer.ts) and explicit starters (main.ts) can coexist.
29
+ */
30
+ export declare function startTiming(): void;
31
+ /**
32
+ * Record an externally-measured span as a leaf child of the active span (or root
33
+ * when no span is active). Used by the module-load timing plugin to splice load
34
+ * events into the tree retroactively.
35
+ */
36
+ export declare function recordModuleLoadSpan(path: string, start: number, durationMs: number): void;
37
+ /**
38
+ * End timing window and clear buffers.
39
+ */
40
+ export declare function endTiming(): void;
41
+ /**
42
+ * Time a span. Three forms:
43
+ * time(op) — point event (zero-duration breadcrumb)
44
+ * time(op, fn, ...args) — wrap fn in a span; returns fn's return value (sync or Promise)
45
+ *
46
+ * Spans nest hierarchically via AsyncLocalStorage: a child started inside another span's fn
47
+ * (even across awaits) becomes that span's child. Parallel children are recorded as siblings
48
+ * with overlapping intervals.
49
+ */
50
+ export declare function time(op: string): void;
51
+ export declare function time<T, A extends unknown[]>(op: string, fn: (...args: A) => T, ...args: A): T;
@@ -0,0 +1,11 @@
1
+ import { type AsciiRenderOptions } from "beautiful-mermaid";
2
+ export type { AsciiRenderOptions as MermaidAsciiRenderOptions };
3
+ export declare function renderMermaidAscii(source: string, options?: AsciiRenderOptions): string;
4
+ export declare function renderMermaidAsciiSafe(source: string, options?: AsciiRenderOptions): string | null;
5
+ /**
6
+ * Extract mermaid code blocks from markdown text.
7
+ */
8
+ export declare function extractMermaidBlocks(markdown: string): {
9
+ source: string;
10
+ hash: bigint | number;
11
+ }[];
@@ -0,0 +1,29 @@
1
+ export declare const SUPPORTED_IMAGE_MIME_TYPES: Set<string>;
2
+ export type ImageMetadata = {
3
+ mimeType: "image/png";
4
+ width?: number;
5
+ height?: number;
6
+ channels?: number;
7
+ hasAlpha?: boolean;
8
+ } | {
9
+ mimeType: "image/jpeg";
10
+ width?: number;
11
+ height?: number;
12
+ channels?: number;
13
+ hasAlpha?: false;
14
+ } | {
15
+ mimeType: "image/gif";
16
+ width?: number;
17
+ height?: number;
18
+ channels?: 3;
19
+ hasAlpha?: never;
20
+ } | {
21
+ mimeType: "image/webp";
22
+ width?: number;
23
+ height?: number;
24
+ channels?: number;
25
+ hasAlpha?: boolean;
26
+ };
27
+ export declare function parseImageMetadata(header: Uint8Array): ImageMetadata | null;
28
+ export declare function readImageMetadataSync(filePath: string, maxBytes?: number): ImageMetadata | null;
29
+ export declare function readImageMetadata(filePath: string, maxBytes?: number): Promise<ImageMetadata | null>;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Synchronously reads up to `maxBytes` from the start of `filePath` and returns `op(header)`.
3
+ * If the file is shorter, `header` is only the bytes actually read.
4
+ */
5
+ export declare function peekFileSync<T>(filePath: string, maxBytes: number, op: (header: Uint8Array) => T): T;
6
+ /**
7
+ * Like {@link peekFileSync} but uses async I/O.
8
+ */
9
+ export declare function peekFile<T>(filePath: string, maxBytes: number, op: (header: Uint8Array) => T): Promise<T>;
@@ -0,0 +1,29 @@
1
+ export declare enum Reason {
2
+ PRE_EXIT = "pre_exit",// Pre-exit phase (not used by default)
3
+ EXIT = "exit",// Normal process exit
4
+ SIGINT = "sigint",// Ctrl-C or SIGINT
5
+ SIGTERM = "sigterm",// SIGTERM
6
+ SIGHUP = "sighup",// SIGHUP
7
+ UNCAUGHT_EXCEPTION = "uncaught_exception",// Fatal exception
8
+ UNHANDLED_REJECTION = "unhandled_rejection",// Unhandled promise rejection
9
+ MANUAL = "manual"
10
+ }
11
+ /**
12
+ * Register a process cleanup callback, to be run on shutdown, signal, or fatal error.
13
+ *
14
+ * Returns a Callback instance that can be used to cancel (unregister) or manually clean up.
15
+ * If register is called after cleanup already began, invokes callback on a microtask.
16
+ */
17
+ export declare function register(id: string, callback: (reason: Reason) => void | Promise<void>): () => void;
18
+ /**
19
+ * Runs all cleanup callbacks without exiting.
20
+ * Use this in workers or when you need to clean up but continue execution.
21
+ */
22
+ export declare function cleanup(): Promise<void>;
23
+ /**
24
+ * Runs all cleanup callbacks and exits.
25
+ *
26
+ * In main thread: waits for stdout drain, then calls process.exit().
27
+ * In workers: runs cleanup only (process.exit would kill entire process).
28
+ */
29
+ export declare function quit(code?: number): Promise<void>;
@@ -0,0 +1,35 @@
1
+ import type { Subprocess } from "bun";
2
+ export interface ShellConfig {
3
+ shell: string;
4
+ args: string[];
5
+ env: Record<string, string>;
6
+ prefix: string | undefined;
7
+ }
8
+ /**
9
+ * Strip disabled macOS malloc-stack-logging vars from `process.env` in place.
10
+ *
11
+ * macOS leaves `MallocStackLogging=0` (or similar) inherited by debug-attached
12
+ * shells. Bun's libc init then prints `MallocStackLogging: can't turn off
13
+ * malloc stack logging because it was not enabled.` to stderr for every
14
+ * subprocess. Scrubbing once at startup means every child we spawn — bash,
15
+ * bun subagents, plugin installs, ptree commands — inherits a clean env.
16
+ */
17
+ export declare function scrubProcessEnv(): void;
18
+ /**
19
+ * Resolve a basic shell (bash or sh) as fallback.
20
+ */
21
+ export declare function resolveBasicShell(): string | undefined;
22
+ /**
23
+ * Get shell configuration based on platform.
24
+ * Resolution order:
25
+ * 1. User-specified shellPath in settings.json
26
+ * 2. On Windows: Git Bash in known locations, then bash on PATH
27
+ * 3. On Unix: $SHELL if bash/zsh, then fallback paths
28
+ * 4. Fallback: sh
29
+ */
30
+ export declare function getShellConfig(customShellPath?: string): ShellConfig;
31
+ /**
32
+ * Check if a process is running.
33
+ */
34
+ export declare function isPidRunning(pid: number | Subprocess): boolean;
35
+ export declare function onProcessExit(proc: Subprocess | number, abortSignal?: AbortSignal): Promise<boolean>;
@@ -0,0 +1,18 @@
1
+ import type { HelperDelegate, HelperOptions, Template, TemplateDelegate } from "handlebars";
2
+ export type { HelperDelegate, HelperOptions, Template, TemplateDelegate };
3
+ export type PromptRenderPhase = "pre-render" | "post-render";
4
+ export interface PromptFormatOptions {
5
+ renderPhase?: PromptRenderPhase;
6
+ replaceAsciiSymbols?: boolean;
7
+ normalizeRfc2119?: boolean;
8
+ }
9
+ export declare function format(content: string, options?: PromptFormatOptions): string;
10
+ export interface TemplateContext extends Record<string, unknown> {
11
+ args?: string[];
12
+ ARGUMENTS?: string;
13
+ arguments?: string;
14
+ }
15
+ export declare function registerHelper(name: string, fn: HelperDelegate): void;
16
+ export declare function registerPartial(name: string, fn: Template): void;
17
+ export declare function compile(template: string): (context: TemplateContext) => string;
18
+ export declare function render(template: string, context?: TemplateContext): string;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Process tree management utilities for Bun subprocesses.
3
+ *
4
+ * - Track managed child processes for cleanup on shutdown (postmortem).
5
+ * - Drain stdout/stderr to avoid subprocess pipe deadlocks.
6
+ * - Cross-platform tree kill for process groups (Windows taskkill, Unix -pid).
7
+ * - Convenience helpers: captureText / execText, AbortSignal, timeouts.
8
+ */
9
+ import type { Spawn, Subprocess } from "bun";
10
+ type InMask = "pipe" | "ignore" | Buffer | Uint8Array | null;
11
+ /** A Bun subprocess with stdout/stderr always piped (stdin may vary). */
12
+ type PipedSubprocess<In extends InMask = InMask> = Subprocess<In, "pipe", "pipe">;
13
+ /**
14
+ * Base for all exceptions representing child process nonzero exit, killed, or
15
+ * cancellation.
16
+ */
17
+ export declare abstract class Exception extends Error {
18
+ readonly exitCode: number;
19
+ readonly stderr: string;
20
+ constructor(message: string, exitCode: number, stderr: string);
21
+ abstract readonly aborted: boolean;
22
+ }
23
+ /** Exception for nonzero exit codes (not cancellation). */
24
+ export declare class NonZeroExitError extends Exception {
25
+ static readonly MAX_TRACE: number;
26
+ constructor(exitCode: number, stderr: string);
27
+ get aborted(): boolean;
28
+ }
29
+ /** Exception for explicit process abortion (via signal). */
30
+ export declare class AbortError extends Exception {
31
+ readonly reason: unknown;
32
+ constructor(reason: unknown, stderr: string);
33
+ get aborted(): boolean;
34
+ }
35
+ /** Exception for process timeout. */
36
+ export declare class TimeoutError extends AbortError {
37
+ constructor(timeout: number, stderr: string);
38
+ }
39
+ /** Options for waiting for process exit and capturing output. */
40
+ export interface WaitOptions {
41
+ allowNonZero?: boolean;
42
+ allowAbort?: boolean;
43
+ stderr?: "full" | "buffer";
44
+ }
45
+ /** Result from wait and exec. */
46
+ export interface ExecResult {
47
+ stdout: string;
48
+ stderr: string;
49
+ exitCode: number | null;
50
+ ok: boolean;
51
+ exitError?: Exception;
52
+ }
53
+ /**
54
+ * ChildProcess wraps a managed subprocess, capturing stderr tail, providing
55
+ * cross-platform kill/detach logic plus AbortSignal integration.
56
+ *
57
+ * Stdout is exposed directly from the underlying Bun subprocess; consumers
58
+ * must read it (via text(), wait(), etc.) to prevent pipe deadlock.
59
+ * Stderr is eagerly drained into an internal buffer.
60
+ */
61
+ export declare class ChildProcess<In extends InMask = InMask> {
62
+ #private;
63
+ readonly proc: PipedSubprocess<In>;
64
+ readonly exposeStderr: boolean;
65
+ constructor(proc: PipedSubprocess<In>, exposeStderr: boolean);
66
+ get pid(): number;
67
+ get exited(): Promise<number>;
68
+ get exitCode(): number | null;
69
+ get exitReason(): Exception | undefined;
70
+ get killed(): boolean;
71
+ get stdin(): Bun.SpawnOptions.WritableToIO<In>;
72
+ /** Raw stdout stream. Must be consumed to prevent pipe deadlock. */
73
+ get stdout(): ReadableStream<Uint8Array<ArrayBuffer>>;
74
+ /** Optional stderr stream (only when requested in spawn options). */
75
+ get stderr(): ReadableStream<Uint8Array<ArrayBufferLike>> | undefined;
76
+ get exitedCleanly(): Promise<number>;
77
+ /** Returns the truncated stderr tail (last 32KB). */
78
+ peekStderr(): string;
79
+ nothrow(): this;
80
+ kill(reason?: Exception): void;
81
+ text(): Promise<string>;
82
+ blob(): Promise<Blob>;
83
+ json(): Promise<unknown>;
84
+ arrayBuffer(): Promise<ArrayBuffer>;
85
+ bytes(): Promise<Uint8Array>;
86
+ wait(opts?: WaitOptions): Promise<ExecResult>;
87
+ attachSignal(signal: AbortSignal): void;
88
+ attachTimeout(ms: number): void;
89
+ [Symbol.dispose](): void;
90
+ }
91
+ /** Options for child spawn. Always pipes stdout/stderr. */
92
+ type ChildSpawnOptions<In extends InMask = InMask> = Omit<Spawn.SpawnOptions<In, "pipe", "pipe">, "stdout" | "stderr" | "detached"> & {
93
+ signal?: AbortSignal;
94
+ detached?: boolean;
95
+ stderr?: "full" | null;
96
+ };
97
+ /** Spawn a child process with piped stdout/stderr. */
98
+ export declare function spawn<In extends InMask = InMask>(cmd: string[], opts?: ChildSpawnOptions<In>): ChildProcess<In>;
99
+ /** Options for exec. */
100
+ export interface ExecOptions extends Omit<ChildSpawnOptions, "stderr" | "stdin">, WaitOptions {
101
+ input?: string | Buffer | Uint8Array;
102
+ }
103
+ /** Spawn, wait, and return captured output. */
104
+ export declare function exec(cmd: string[], opts?: ExecOptions): Promise<ExecResult>;
105
+ type SignalValue = AbortSignal | number | null | undefined;
106
+ /** Combine AbortSignals and timeout values into a single signal. */
107
+ export declare function combineSignals(...signals: SignalValue[]): AbortSignal | undefined;
108
+ export {};
@@ -0,0 +1,93 @@
1
+ /**
2
+ * A fixed-capacity circular buffer that supports efficient push/pop/shift/unshift operations.
3
+ * When the buffer is full, adding new items overwrites the oldest items (FIFO behavior).
4
+ *
5
+ * @template T The type of elements stored in the buffer.
6
+ */
7
+ export declare class RingBuffer<T> {
8
+ #private;
9
+ readonly capacity: number;
10
+ /**
11
+ * Creates a new ring buffer with the specified capacity.
12
+ *
13
+ * @param capacity - The maximum number of elements the buffer can hold. Must be positive.
14
+ */
15
+ constructor(capacity: number);
16
+ /**
17
+ * The number of elements currently in the buffer.
18
+ */
19
+ get length(): number;
20
+ /**
21
+ * Whether the buffer is at full capacity.
22
+ */
23
+ get isFull(): boolean;
24
+ /**
25
+ * Whether the buffer is empty (contains no elements).
26
+ */
27
+ get isEmpty(): boolean;
28
+ /**
29
+ * Adds an item to the end of the buffer.
30
+ * If the buffer is full, the oldest item is overwritten and returned.
31
+ *
32
+ * @param item - The item to add.
33
+ * @returns The overwritten item if the buffer was full, otherwise `undefined`.
34
+ */
35
+ push(item: T): T | undefined;
36
+ /**
37
+ * Removes and returns the first (oldest) item from the buffer.
38
+ *
39
+ * @returns The removed item, or `undefined` if the buffer is empty.
40
+ */
41
+ shift(): T | undefined;
42
+ /**
43
+ * Removes and returns the last (newest) item from the buffer.
44
+ *
45
+ * @returns The removed item, or `undefined` if the buffer is empty.
46
+ */
47
+ pop(): T | undefined;
48
+ /**
49
+ * Adds an item to the beginning of the buffer.
50
+ * If the buffer is full, the newest item is overwritten and returned.
51
+ *
52
+ * @param item - The item to add.
53
+ * @returns The overwritten item if the buffer was full, otherwise `undefined`.
54
+ */
55
+ unshift(item: T): T | undefined;
56
+ /**
57
+ * Returns the element at the specified index without removing it.
58
+ * Supports negative indices (e.g., `-1` for the last element).
59
+ *
60
+ * @param index - The zero-based index, or negative index from the end.
61
+ * @returns The element at the index, or `undefined` if the index is out of bounds.
62
+ */
63
+ at(index: number): T | undefined;
64
+ /**
65
+ * Returns the first (oldest) element without removing it.
66
+ *
67
+ * @returns The first element, or `undefined` if the buffer is empty.
68
+ */
69
+ peek(): T | undefined;
70
+ /**
71
+ * Returns the last (newest) element without removing it.
72
+ *
73
+ * @returns The last element, or `undefined` if the buffer is empty.
74
+ */
75
+ peekBack(): T | undefined;
76
+ /**
77
+ * Removes all elements from the buffer, resetting it to an empty state.
78
+ */
79
+ clear(): void;
80
+ /**
81
+ * Returns an iterator that yields elements in logical order (oldest to newest).
82
+ * Allows the buffer to be used with `for...of` loops and spread syntax.
83
+ *
84
+ * @yields Elements in FIFO order.
85
+ */
86
+ [Symbol.iterator](): Iterator<T>;
87
+ /**
88
+ * Creates a new array containing all elements in logical order (oldest to newest).
89
+ *
90
+ * @returns A new array with all buffer elements.
91
+ */
92
+ toArray(): T[];
93
+ }
@@ -0,0 +1,25 @@
1
+ type Snowflake = string & {
2
+ readonly __brand: unique symbol;
3
+ };
4
+ declare namespace Snowflake {
5
+ const PATTERN: RegExp;
6
+ const EPOCH_TIMESTAMP = 1420070400000;
7
+ const MAX_SEQUENCE = 4194303;
8
+ function formatParts(dt: number, seq: number): Snowflake;
9
+ class Source {
10
+ #private;
11
+ constructor(sequence?: number);
12
+ get sequence(): number;
13
+ set sequence(v: number);
14
+ reset(): void;
15
+ generate(timestamp: number): Snowflake;
16
+ }
17
+ function next(timestamp?: number): Snowflake;
18
+ function valid(value: string): value is Snowflake;
19
+ function lowerbound(timelike: Date | number | Snowflake): Snowflake;
20
+ function upperbound(timelike: Date | number | Snowflake): Snowflake;
21
+ function getSequence(value: Snowflake): number;
22
+ function getTimestamp(value: Snowflake): number;
23
+ function getDate(value: Snowflake): Date;
24
+ }
25
+ export { Snowflake };
@@ -0,0 +1,68 @@
1
+ export declare function readLines(stream: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<Uint8Array>;
2
+ export declare function readJsonl<T>(stream: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<T>;
3
+ /**
4
+ * Stream parsed JSON objects from SSE `data:` lines.
5
+ *
6
+ * Thin wrapper over {@link readSseEvents}: yields one parsed JSON value per
7
+ * dispatched SSE event, skipping events with empty `data` and stopping at the
8
+ * OpenAI-style `[DONE]` sentinel. If your consumer doesn't care about `event:`
9
+ * names or doesn't need a custom parse step, use this; otherwise call
10
+ * `readSseEvents` directly.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * for await (const obj of readSseJson(response.body!)) {
15
+ * console.log(obj);
16
+ * }
17
+ * ```
18
+ */
19
+ export type SseEventObserver = (event: ServerSentEvent) => void;
20
+ export declare function readSseJson<T>(stream: ReadableStream<Uint8Array>, signal?: AbortSignal, onEvent?: SseEventObserver): AsyncGenerator<T>;
21
+ /**
22
+ * A single Server-Sent Event dispatched on a blank-line boundary.
23
+ *
24
+ * - `event` is the value of the most recent `event:` field, or `null` if none.
25
+ * - `data` is the concatenation (joined by `\n`) of every `data:` field in the
26
+ * event, exactly as required by the SSE spec.
27
+ * - `raw` is the list of decoded non-empty lines that made up the event,
28
+ * preserved for diagnostic context (error reporting, debugging). The
29
+ * dispatching blank line is not included.
30
+ */
31
+ export interface ServerSentEvent {
32
+ event: string | null;
33
+ data: string;
34
+ raw: string[];
35
+ }
36
+ /**
37
+ * Stream raw Server-Sent Events from an HTTP response body.
38
+ *
39
+ * Yields one `ServerSentEvent` per blank-line dispatch. The consumer is
40
+ * responsible for parsing `data` (e.g. JSON, plain text, error envelope).
41
+ * Use `readSseJson` instead when every event is a single `data:` JSON object
42
+ * and you don't need access to the `event:` field.
43
+ *
44
+ * Internally backed by a Buffer-based line reader (`ConcatSink`) so chunk
45
+ * concatenation is O(n) and never triggers per-line string slicing of the
46
+ * accumulated buffer.
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * for await (const sse of readSseEvents(response.body!)) {
51
+ * if (sse.event === "ping") continue;
52
+ * const obj = JSON.parse(sse.data);
53
+ * }
54
+ * ```
55
+ */
56
+ export declare function readSseEvents(stream: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<ServerSentEvent>;
57
+ /**
58
+ * Parse a complete JSONL string, skipping malformed lines instead of throwing.
59
+ *
60
+ * Uses `Bun.JSONL.parseChunk` internally. On parse errors, the malformed
61
+ * region is skipped up to the next newline and parsing continues.
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * const entries = parseJsonlLenient<MyType>(fileContents);
66
+ * ```
67
+ */
68
+ export declare function parseJsonlLenient<T>(buffer: string): T[];
@@ -0,0 +1,9 @@
1
+ export declare const MIN_TAB_WIDTH = 1;
2
+ export declare const MAX_TAB_WIDTH = 16;
3
+ export declare const DEFAULT_TAB_WIDTH = 3;
4
+ export declare function getDefaultTabWidth(): number;
5
+ export declare function setDefaultTabWidth(width: number): void;
6
+ /**
7
+ * Visible tab width in columns for `file` (from `.editorconfig` + default), or the default when `file` is omitted.
8
+ */
9
+ export declare function getIndentation(file?: string | null, projectDir?: string | null): number;
@@ -0,0 +1,14 @@
1
+ export declare class TempDir {
2
+ #private;
3
+ private constructor();
4
+ static createSync(prefix?: string): TempDir;
5
+ static create(prefix?: string): Promise<TempDir>;
6
+ path(): string;
7
+ absolute(): string;
8
+ remove(): Promise<void>;
9
+ removeSync(): void;
10
+ toString(): string;
11
+ join(...paths: string[]): string;
12
+ [Symbol.asyncDispose](): Promise<void>;
13
+ [Symbol.dispose](): void;
14
+ }
@@ -0,0 +1,3 @@
1
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
2
+ export declare function asRecord(value: unknown): Record<string, unknown> | null;
3
+ export declare function toError(value: unknown): Error;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Cache policy for which lookups.
3
+ */
4
+ export declare const enum WhichCachePolicy {
5
+ /**
6
+ * Use cached result if available.
7
+ */
8
+ Cached = 0,
9
+ /**
10
+ * Bypass cache and perform a new lookup.
11
+ */
12
+ Bypass = 1,
13
+ /**
14
+ * Always update cache.
15
+ */
16
+ Fresh = 2,
17
+ /**
18
+ * Read-only, serves from cache if present, but doesn't write.
19
+ */
20
+ ReadOnly = 3
21
+ }
22
+ export interface WhichOptions extends Bun.WhichOptions {
23
+ /**
24
+ * Cache policy for the lookup.
25
+ * Defaults to `WhichCachePolicy.Fresh`.
26
+ */
27
+ cache?: WhichCachePolicy;
28
+ }
29
+ export declare const whichFresh: typeof Bun.which;
30
+ /**
31
+ * Locate binary on PATH (with flexible caching).
32
+ *
33
+ * @param command - Binary name to resolve
34
+ * @param options - Bun.WhichOptions plus `cache` control
35
+ * @returns Filesystem path if found, else null
36
+ */
37
+ export declare function $which(command: string, options?: WhichOptions): string | null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-utils",
4
- "version": "15.1.0",
4
+ "version": "15.1.2",
5
5
  "description": "Shared utilities for pi packages",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -21,7 +21,7 @@
21
21
  "streams"
22
22
  ],
23
23
  "main": "./src/index.ts",
24
- "types": "./src/index.ts",
24
+ "types": "./dist/types/index.d.ts",
25
25
  "scripts": {
26
26
  "check": "biome check . && bun run check:types",
27
27
  "check:types": "tsgo -p tsconfig.json --noEmit",
@@ -38,21 +38,22 @@
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/bun": "^1.3.14",
41
- "@oh-my-pi/pi-natives": "15.1.0"
41
+ "@oh-my-pi/pi-natives": "15.1.2"
42
42
  },
43
43
  "engines": {
44
44
  "bun": ">=1.3.14"
45
45
  },
46
46
  "files": [
47
- "src"
47
+ "src",
48
+ "dist/types"
48
49
  ],
49
50
  "exports": {
50
51
  ".": {
51
- "types": "./src/index.ts",
52
+ "types": "./dist/types/index.d.ts",
52
53
  "import": "./src/index.ts"
53
54
  },
54
55
  "./*": {
55
- "types": "./src/*.ts",
56
+ "types": "./dist/types/*.d.ts",
56
57
  "import": "./src/*.ts"
57
58
  },
58
59
  "./*.js": "./src/*.ts"
@@ -86,7 +86,7 @@ export function parseFrontmatter(
86
86
  const loc = location ?? source;
87
87
  const frontmatter: Record<string, unknown> = { ...fallback };
88
88
 
89
- const normalized = normalize ? stripHtmlComments(content.replace(/\r\n/g, "\n").replace(/\r/g, "\n")) : content;
89
+ const normalized = normalize ? stripHtmlComments(content.replace(/\r\n?/g, "\n")) : content;
90
90
  if (!normalized.startsWith("---")) {
91
91
  return { frontmatter, body: normalized };
92
92
  }