@linxiraos/pi-utils 1.1.8 → 1.1.10

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.
package/CHANGELOG.md CHANGED
@@ -2,10 +2,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
- ## [1.1.8] - 2026-09-04
5
+ ## [1.1.10] - 2026-09-07
6
6
 
7
- - Added `symlinkDirectorySync` Windows-correct directory links (junction) for tests and tooling that must link temp trees without elevation.
8
- - Extended the temp-removal retry window to 7.5s: measured Windows handle-release latency after a child process dies reaches ~5s, and the previous 2s window flaked suites cleaning up trees containing SQLite databases.
7
+ - Fixed `extractRetryHint` dropping the longer timing signal when an error body carries both an account reset and an appended retry hint: competing signals now merge by longest window instead of first match, so retries honor the provider's full backoff.
8
+
9
+ ## [1.1.9] - 2026-09-05
10
+
11
+ - USER_AGENT constant restored to zeta/<version> (the v18.1.10 merge re-introduced the upstream omp/ UA on every provider request); brand wording in path-resolver docs corrected; profile validation error message Zeta'd.
9
12
 
10
13
  ## [1.1.6] - 2026-08-30
11
14
 
package/README.md CHANGED
@@ -1,25 +1,25 @@
1
1
  # @linxiraos/pi-utils
2
2
 
3
- Shared utilities for [Zeta](https://github.com/Linxira-OS/linxira-zeta) packages. Zero ceremony, Bun-first.
3
+ Shared utilities for [oh-my-pi](https://github.com/can1357/oh-my-pi) packages. Zero ceremony, Bun-first.
4
4
 
5
5
  ## Notable modules
6
6
 
7
- | Module | Purpose |
8
- | --- | --- |
9
- | `logger` | Centralized logger writing to `~/.zeta/logs/` with rotation (TUI-safe — never stdout) |
10
- | `prompt` | Handlebars-based prompt templating and formatting helpers |
11
- | `dirs` | Path helpers for Zeta config directories (`~/.zeta`, XDG-aware on Linux) |
12
- | `stream` | `readStream` / `readLines` helpers over `ReadableStream` |
13
- | `ptree` / `procmgr` | Process trees, `ChildProcess` wrapper, process lifecycle management |
14
- | `postmortem` | Cleanup callbacks on exit, signals, and fatal exceptions |
15
- | `which` | `$which()` binary lookup with caching |
16
- | `fetch-retry` | `fetch` with retry/backoff policies |
17
- | `fs-error` | Errno guards (`isEnoent` and friends) |
18
- | `env` / `worker-host` | Environment plumbing and side-effect-free worker-host entry contract (`workerHostEntry`) |
19
- | `abortable` / `async` | AbortSignal-aware stream/promise helpers |
20
- | `math-delimiters` | LaTeX span/block delimiter grammar (offsets only) shared by the TUI and collab-web renderers |
21
- | `peek-file` | Read the first N bytes of a file with pooled buffers |
22
- | `frontmatter`, `glob`, `mime`, `temp`, `format`, `color`, `snowflake`, `tab-spacing`, `path-tree`, `sanitize-text` | Smaller single-purpose helpers |
7
+ | Module | Purpose |
8
+ | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
9
+ | `logger` | Centralized logger writing to `~/.zeta/logs/` with rotation (TUI-safe — never stdout) |
10
+ | `prompt` | Handlebars-based prompt templating and formatting helpers |
11
+ | `dirs` | Path helpers for omp config directories (`~/.zeta`, XDG-aware on Linux) |
12
+ | `stream` | `readStream` / `readLines` helpers over `ReadableStream` |
13
+ | `ptree` / `procmgr` | Process trees, `ChildProcess` wrapper, process lifecycle management |
14
+ | `postmortem` | Cleanup callbacks on exit, signals, and fatal exceptions |
15
+ | `which` | `$which()` binary lookup with caching |
16
+ | `fetch-retry` | `fetch` with retry/backoff policies |
17
+ | `fs-error` | Errno guards (`isEnoent` and friends) |
18
+ | `env` / `worker-host` | Environment plumbing and side-effect-free worker-host entry contract (`workerHostEntry`) |
19
+ | `abortable` / `async` | AbortSignal-aware stream/promise helpers |
20
+ | `math-delimiters` | LaTeX span/block delimiter grammar (offsets only) shared by the TUI and collab-web renderers |
21
+ | `peek-file` | Read the first N bytes of a file with pooled buffers |
22
+ | `frontmatter`, `glob`, `mime`, `temp`, `format`, `color`, `snowflake`, `tab-spacing`, `path-tree`, `sanitize-text` | Smaller single-purpose helpers |
23
23
 
24
24
  Import from the root barrel or per-module subpaths (`@linxiraos/pi-utils/<module>`).
25
25
 
@@ -33,5 +33,5 @@ Ships TypeScript source directly (no build step); requires Bun ≥ 1.3.14.
33
33
 
34
34
  ## References
35
35
 
36
- - [Monorepo README](https://github.com/Linxira-OS/linxira-zeta#readme)
36
+ - [Monorepo README](https://github.com/can1357/oh-my-pi#readme)
37
37
  - [CHANGELOG](./CHANGELOG.md)
@@ -20,3 +20,13 @@ export declare class AsyncDrain<T> {
20
20
  /** Runs the pending batch handler immediately and returns its completion promise. */
21
21
  flush(): Promise<void>;
22
22
  }
23
+ /**
24
+ * Runs async operations one at a time in call order. Each `run` starts after
25
+ * the previous operation settles (success or failure) and returns that
26
+ * operation's own promise, so a rejected step never poisons the queue. Used by
27
+ * stateful cursors whose concurrent pulls must not interleave.
28
+ */
29
+ export declare class Serial {
30
+ #private;
31
+ run<T>(op: () => Promise<T>): Promise<T>;
32
+ }
@@ -8,9 +8,9 @@
8
8
  * variables are set, paths are redirected to XDG-compliant locations under
9
9
  * $XDG_*_HOME/zeta/. This requires running `zeta config migrate` first to
10
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.
11
+ * — if the env var is set, zeta trusts that the migration has been done.
12
12
  */
13
- /** App name (e.g. "omp") */
13
+ /** App name (e.g. "zeta") */
14
14
  export declare const APP_NAME: string;
15
15
  /** Config directory name (e.g. ".zeta") */
16
16
  export declare const CONFIG_DIR_NAME: string;
@@ -273,6 +273,8 @@ export declare function getCrashLogPath(agentDir?: string): string;
273
273
  export declare function getDebugLogPath(agentDir?: string): string;
274
274
  /** Get the secret placeholder key path (~/.zeta/agent/secret-placeholder.key; XDG default: $XDG_STATE_HOME/zeta/secret-placeholder.key). Adopts a legacy key on first XDG resolution. */
275
275
  export declare function getSecretPlaceholderKeyPath(): string;
276
+ /** Directory holding the per-model tiny-worker sockets and logs (~/.zeta/run/tiny; XDG default: $XDG_STATE_HOME/zeta/run/tiny). */
277
+ export declare function getTinyWorkerRuntimeDir(): string;
276
278
  /** Root directory containing every per-project daemon runtime scope (~/.zeta/run/daemons; XDG default: $XDG_STATE_HOME/zeta/run/daemons). */
277
279
  export declare function getDaemonRuntimeRoot(): string;
278
280
  /** Get the daemon runtime directory for a project (~/.zeta/run/daemons/<hash>; XDG default: $XDG_STATE_HOME/zeta/run/daemons/<hash>). */
@@ -317,9 +319,9 @@ export declare function getAppName(): string;
317
319
  * profile / `getInstallId` call order.
318
320
  */
319
321
  export declare function getInstallId(): string;
320
- /** Test-only: clear cached install id. Never call from production code. */
321
- export declare function __resetInstallIdCacheForTests(): void;
322
322
  /** Get the project-level tracking directory (<project>/.zeta/tracking). */
323
323
  export declare function getProjectTrackingDir(cwd?: string): string;
324
324
  /** Get the global tracking index path (~/.zeta/agent/tracking-index.json). */
325
325
  export declare function getTrackingIndexPath(agentDir?: string): string;
326
+ /** Test-only: clear cached install id. Never call from production code. */
327
+ export declare function __resetInstallIdCacheForTests(): void;
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Typed cursors over a JSON document while its text is still arriving.
3
+ *
4
+ * {@link IncomingDoc.channel} returns a push-side {@link IncomingFeed} and a
5
+ * read-side {@link IncomingDoc}. The producer appends text fragments, then
6
+ * explicitly calls {@link IncomingFeed.finish} or {@link IncomingFeed.abort};
7
+ * a feed that is never closed leaves every pending pull waiting forever.
8
+ * There is one shared append-only buffer; cursors are cheap, immutable
9
+ * path handles over it, and every pull is an ordinary promise that
10
+ * re-scans the buffer whenever the feed changes. There are no snapshots,
11
+ * per-field events, or fan-out channels.
12
+ *
13
+ * A scalar completes at its closing quote/delimiter, and a container
14
+ * completes only when its closing delimiter arrives. Finished-but-truncated
15
+ * input rejects with kind `incomplete`; abandoned input rejects with
16
+ * `aborted`. String chunks contain only decoded text whose meaning is
17
+ * stable, so an escape or Unicode escape may span any number of fragments.
18
+ *
19
+ * Pulling an {@link IncomingObject.key} makes that key required: a missing
20
+ * or mistyped value is a structured {@link IncomingJsonError}. Object members
21
+ * never pulled are skipped without validation. {@link IncomingDoc.whole} is
22
+ * the explicit whole-document pull and runs only after successful input
23
+ * completion.
24
+ *
25
+ * Object cursors bind the first occurrence of a duplicate key, whereas
26
+ * complete-value pulls (`value()`, `collect()`, `whole()`) go through the
27
+ * final parser (`parseJsonWithRepair`), whose objects are last-write-wins.
28
+ *
29
+ * Mid-stream cursors tolerate incomplete tokens but read double-quoted
30
+ * strings with the final parser's strict closing rule: an unescaped inner
31
+ * `"` can never swallow a sibling key or value. A pulled scalar completes
32
+ * only once a value terminator follows it, like numbers, so structural
33
+ * garbage after a value surfaces as `incomplete` rather than a silently
34
+ * misparsed pull. Single-quote recovery (`'it's'`) is shared with the final
35
+ * parser and passes both.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * const { feed, doc } = IncomingDoc.channel();
40
+ * const args = doc.root().object();
41
+ * const content = args.key("content").string();
42
+ * feed.push('{"path":"a.ts","content":"hel');
43
+ * await content.nextChunk(); // "hel"
44
+ * feed.push('lo"}');
45
+ * feed.finish();
46
+ * await content.nextChunk(); // "lo"
47
+ * await content.nextChunk(); // undefined
48
+ * await args.key("path").value<string>(); // "a.ts"
49
+ * ```
50
+ */
51
+ /** Location component in a pulled JSON path: an object member name or an array index. */
52
+ export type PullPathSegment = string | number;
53
+ /** JSON shape observed by a started pull. */
54
+ export type IncomingValueKind = "null" | "boolean" | "number" | "string" | "array" | "object";
55
+ /** Why a pull could not produce the requested shape. */
56
+ export type PullIssueKind =
57
+ /** The requested member was absent when its container completed. */
58
+ "missing"
59
+ /** The producer finished before the pulled value's closing token. */
60
+ | "incomplete"
61
+ /** The producer abandoned the input before the pull completed. */
62
+ | "aborted"
63
+ /** A complete pulled value could not be parsed. */
64
+ | "malformed"
65
+ /** A value was present with a different JSON shape (see `found`). */
66
+ | "mismatch";
67
+ /** Structured failure while awaiting an incoming JSON value. */
68
+ export declare class IncomingJsonError extends Error {
69
+ /** Full key/index path pulled by the consumer. */
70
+ readonly path: readonly PullPathSegment[];
71
+ /** Shape requested by the typed cursor. */
72
+ readonly expected: string;
73
+ /** Why the pull could not produce that shape. */
74
+ readonly kind: PullIssueKind;
75
+ /** Shape observed in the input when `kind` is `mismatch`. */
76
+ readonly found?: string;
77
+ constructor(path: readonly PullPathSegment[], expected: string, kind: PullIssueKind, options?: {
78
+ found?: string;
79
+ cause?: unknown;
80
+ });
81
+ }
82
+ type Shape = {
83
+ kind: "null" | "array" | "object";
84
+ } | {
85
+ kind: "boolean";
86
+ value: boolean;
87
+ } | {
88
+ kind: "number";
89
+ value: number;
90
+ } | {
91
+ kind: "string";
92
+ value: string;
93
+ stableLen: number;
94
+ };
95
+ interface Located {
96
+ tag: "located";
97
+ /** Source offset of the value's first char. */
98
+ start: number;
99
+ /** Source offset just past the value's closing token; `undefined` while it is still open. */
100
+ end: number | undefined;
101
+ shape: Shape;
102
+ }
103
+ /** Readiness predicate a pull waits for once its value has been located. */
104
+ type Ready = (located: Located) => boolean;
105
+ /** Append-only buffer, terminal state, and change notification shared by one feed and its cursors. */
106
+ declare class Shared {
107
+ #private;
108
+ text: string;
109
+ end: "open" | "finished" | "aborted";
110
+ /** Resolves once the buffer or terminal state changes after this call. */
111
+ get changed(): Promise<void>;
112
+ notify(): void;
113
+ /** Await input completion; rejects with `aborted` when the feed was abandoned. */
114
+ finished(): Promise<void>;
115
+ /**
116
+ * Await the value at `path` until `ready` accepts it. Resolves `undefined`
117
+ * when the value's container completed without it.
118
+ */
119
+ pull(path: readonly PullPathSegment[], expected: string, ready: Ready): Promise<Located | undefined>;
120
+ }
121
+ /**
122
+ * Push side of an {@link IncomingDoc} channel. Call {@link finish} to mark
123
+ * the document complete or {@link abort} to abandon it; both are idempotent
124
+ * and every pending pull settles on the first one.
125
+ */
126
+ export declare class IncomingFeed {
127
+ #private;
128
+ constructor(shared: Shared);
129
+ /** Append one text fragment and wake every pending cursor. Throws once the feed is closed. */
130
+ push(fragment: string): void;
131
+ /** Mark the input complete. */
132
+ finish(): void;
133
+ /** Abandon the input; pending and future pulls reject with kind `aborted`. */
134
+ abort(): void;
135
+ }
136
+ /** Read side of one growing JSON document. */
137
+ export declare class IncomingDoc {
138
+ #private;
139
+ constructor(shared: Shared);
140
+ /** Create a push feed and its read-side document. */
141
+ static channel(): {
142
+ feed: IncomingFeed;
143
+ doc: IncomingDoc;
144
+ };
145
+ /** Text received so far. */
146
+ get text(): string;
147
+ /** Await explicit input completion; rejects with kind `aborted` if the feed was abandoned. */
148
+ finished(): Promise<void>;
149
+ /**
150
+ * Parse the entire finished document with the final tolerant parser.
151
+ * Waits for {@link IncomingFeed.finish}; aborted input is not decoded and a
152
+ * document that fails the final parse rejects with kind `malformed`.
153
+ */
154
+ whole<T = unknown>(): Promise<T>;
155
+ /** Cursor for the root JSON value. */
156
+ root(): IncomingJson;
157
+ }
158
+ /** Cursor for one JSON value in the incoming document, addressed by path. */
159
+ export declare class IncomingJson {
160
+ #private;
161
+ readonly path: readonly PullPathSegment[];
162
+ constructor(shared: Shared, path: readonly PullPathSegment[]);
163
+ /** Await the value's first token and report its JSON shape. */
164
+ kind(): Promise<IncomingValueKind>;
165
+ /** Await and parse the complete value. Containers go through the final tolerant parser. */
166
+ value<T = unknown>(): Promise<T>;
167
+ /** Await a complete number. */
168
+ number(): Promise<number>;
169
+ /** Await a complete boolean. */
170
+ boolean(): Promise<boolean>;
171
+ /** Await a complete `null`. */
172
+ null(): Promise<null>;
173
+ /** View this value as an incremental decoded string. */
174
+ string(): IncomingString;
175
+ /** View this value as an array of element cursors. */
176
+ array(): IncomingArray;
177
+ /** View this value as an object with keyed cursors. */
178
+ object(): IncomingObject;
179
+ }
180
+ /**
181
+ * Incremental decoded string consumer. Chunks are emitted in order without
182
+ * overlap and are always prefixes of the final decoded string;
183
+ * {@link text} returns the complete string independently of whether chunks
184
+ * were consumed. Async iteration yields chunks. Concurrent `nextChunk` /
185
+ * `nextLine` calls are served in call order, like a stream reader: a call
186
+ * whose result is abandoned still consumes its chunk.
187
+ */
188
+ export declare class IncomingString implements AsyncIterable<string> {
189
+ #private;
190
+ constructor(shared: Shared, path: readonly PullPathSegment[]);
191
+ /** Await the next stable decoded chunk, or `undefined` after the closing quote. */
192
+ nextChunk(): Promise<string | undefined>;
193
+ /**
194
+ * Await the next complete decoded line, retaining its trailing newline.
195
+ * A final unterminated line is returned once the closing quote arrives.
196
+ */
197
+ nextLine(): Promise<string | undefined>;
198
+ /** Iterate complete decoded lines. */
199
+ lines(): AsyncGenerator<string, void, undefined>;
200
+ [Symbol.asyncIterator](): AsyncGenerator<string, void, undefined>;
201
+ /** Await the closing quote and return the complete decoded string. */
202
+ text(): Promise<string>;
203
+ }
204
+ /**
205
+ * Linear cursor over elements of an incoming array. Async iteration yields
206
+ * element cursors. Concurrent `next` calls are served in call order.
207
+ */
208
+ export declare class IncomingArray implements AsyncIterable<IncomingJson> {
209
+ #private;
210
+ constructor(shared: Shared, path: readonly PullPathSegment[]);
211
+ /** Await the start of the next element; `undefined` only after the closing bracket. */
212
+ next(): Promise<IncomingJson | undefined>;
213
+ [Symbol.asyncIterator](): AsyncGenerator<IncomingJson, void, undefined>;
214
+ /** Await the closing bracket and collect the fully parsed elements. */
215
+ collect<T = unknown>(): Promise<T[]>;
216
+ }
217
+ /** Keyed cursor and final collection for an incoming object. */
218
+ export declare class IncomingObject {
219
+ #private;
220
+ constructor(shared: Shared, path: readonly PullPathSegment[]);
221
+ /**
222
+ * Cursor bound to the first occurrence of `name`. Pulling it makes the
223
+ * key required: a missing member rejects with kind `missing`.
224
+ */
225
+ key(name: string): IncomingJson;
226
+ /**
227
+ * Await the closing brace and collect the object through the final parser,
228
+ * whose duplicate keys are last-write-wins unlike {@link key}.
229
+ */
230
+ collect<T = Record<string, unknown>>(): Promise<T>;
231
+ }
232
+ export {};
@@ -10,6 +10,7 @@ export * from "./format.js";
10
10
  export * from "./frontmatter.js";
11
11
  export * from "./fs-error.js";
12
12
  export * from "./glob.js";
13
+ export * from "./incoming-json.js";
13
14
  export * from "./json.js";
14
15
  export * from "./json-parse.js";
15
16
  export * as logger from "./logger.js";
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Tolerant JSON lexer shared by the final parser (`parseJsonWithRepair`), the
3
+ * streaming partial builder (`parseStreamingJson`), and the incoming cursors
4
+ * (`IncomingDoc`). {@link JsonLexerMode} selects how truncation and unescaped
5
+ * inner double quotes are treated.
6
+ *
7
+ * The grammar is a forgiving superset of JSON covering malformations commonly
8
+ * produced by language models:
9
+ *
10
+ * - single-quoted strings and unquoted object keys (JSON5);
11
+ * - trailing / stray commas, and `//` + block comments;
12
+ * - Python literals `True` / `False` / `None`, plus `0x` / `0b` numbers;
13
+ * - raw control characters and invalid `\x` escapes inside strings (kept literally);
14
+ * - unescaped quotes inside strings — a single quote only closes a string when
15
+ * followed by a value terminator, recovering apostrophes such as `'it's'`;
16
+ * the same recovery applies to double quotes in `streaming` mode only,
17
+ * everywhere else they close strictly;
18
+ * - unquoted string values in value position — an unrecognized bareword such
19
+ * as `{"paths": packages/foo/*}` is recovered as a string up to the next
20
+ * `,` / `}` / `]` / newline.
21
+ */
22
+ export declare const QUOTE = 34;
23
+ export declare const SQUOTE = 39;
24
+ export declare const BACKSLASH = 92;
25
+ export declare const SLASH = 47;
26
+ export declare const COLON = 58;
27
+ export declare const COMMA = 44;
28
+ export declare const LBRACE = 123;
29
+ export declare const RBRACE = 125;
30
+ export declare const LBRACKET = 91;
31
+ export declare const RBRACKET = 93;
32
+ /** Valid chars after `\` in a strict JSON escape: `" \ / b f n r t u`. */
33
+ export declare const VALID_ESCAPE_CHAR: Uint8Array<ArrayBuffer>;
34
+ export declare function isHexDigit(cp: number): boolean;
35
+ /** JSON insignificant whitespace (RFC 8259 §2). */
36
+ export declare function isWhitespace(cp: number): boolean;
37
+ /** First char of a numeric token: sign, dot, or digit. */
38
+ export declare function isNumberStart(cp: number): boolean;
39
+ /**
40
+ * Grammar tolerance selected by the lexer's consumer.
41
+ *
42
+ * - `strict`: final parse — complete input required, double quotes close strictly.
43
+ * - `streaming`: mid-stream snapshot — incomplete tokens tolerated and unescaped
44
+ * inner double quotes recovered for display.
45
+ * - `incoming`: incremental typed pulls — incomplete tokens tolerated, but double
46
+ * quotes close strictly so pulled values match the final parse.
47
+ */
48
+ export type JsonLexerMode = "strict" | "streaming" | "incoming";
49
+ /** Decoded state of a string token at the current streaming edge. */
50
+ export interface JsonStringProgress {
51
+ /** Decoded content so far (complete when `complete` is true). */
52
+ value: string;
53
+ /**
54
+ * Length of the prefix of `value` whose meaning cannot change when more
55
+ * input arrives. Excludes a trailing split escape, a high surrogate whose
56
+ * low half may still follow, and everything from a quote whose close/inner
57
+ * reading is still undecidable at the buffer edge.
58
+ */
59
+ stableLen: number;
60
+ /** Whether the closing quote was consumed. */
61
+ complete: boolean;
62
+ }
63
+ /**
64
+ * Cursor over the input with the tolerant token readers. `pos` is the current
65
+ * offset; readers advance it. In `strict` mode a truncated or malformed token
66
+ * throws `SyntaxError`; the lenient modes report progress or return
67
+ * `undefined` so the caller can roll back.
68
+ */
69
+ export declare class JsonLexer {
70
+ #private;
71
+ readonly src: string;
72
+ readonly mode: JsonLexerMode;
73
+ pos: number;
74
+ constructor(src: string, mode: JsonLexerMode, pos?: number);
75
+ get atEnd(): boolean;
76
+ /** Char code at the cursor; `NaN` at end of input (so every comparison is false). */
77
+ peek(): number;
78
+ /** Skip whitespace plus `//` line and `/* *\/` block comments. */
79
+ ws(): void;
80
+ /**
81
+ * Read a string starting at the opening `quote`, retaining the information
82
+ * an incremental consumer needs. Strict mode throws on an unterminated
83
+ * string; lenient modes consume to the end of input and report progress.
84
+ */
85
+ string(quote: number): JsonStringProgress;
86
+ /**
87
+ * Read a numeric token with JS `Number()` semantics (decimal with optional
88
+ * sign / leading or trailing dot / exponent, plus `0x` hex and `0b` binary).
89
+ * Non-finite or malformed tokens throw in strict mode and return
90
+ * `undefined` in lenient modes; the cursor is left past the token either way.
91
+ */
92
+ number(): number | undefined;
93
+ /**
94
+ * Match a keyword literal at the cursor; consumes only on success and
95
+ * returns `undefined` otherwise. Requires a non-identifier boundary so
96
+ * `Truex` / `nullish` are not misread as the keyword followed by junk.
97
+ */
98
+ keyword(): boolean | null | undefined;
99
+ /** Read an unquoted object key: everything up to `:` / `,` / `}` / whitespace. May be empty. */
100
+ unquotedKey(): string;
101
+ /**
102
+ * Recover an unquoted string value, e.g. `{"paths": packages/foo/*}`:
103
+ * consume until `,` / `}` / `]` / newline and trim trailing whitespace.
104
+ * Recovery still fails — so a final parse never accepts a half-formed or
105
+ * non-finite argument — when the token:
106
+ * - hits end-of-input before a delimiter (truncated value);
107
+ * - contains a `"`, `{`, `[`, or a key-like `:` — this grammar accepts
108
+ * unquoted keys, so a missed comma (`{"a": foo "b": 1}`) would otherwise
109
+ * silently swallow the following field. A colon followed by `/` or `\`
110
+ * stays literal so URL and Windows-path values recover;
111
+ * - is a non-finite atom ({@link NON_RECOVERABLE_BAREWORDS}).
112
+ *
113
+ * Failure throws in strict mode and returns `undefined` in lenient modes.
114
+ */
115
+ bareword(): string | undefined;
116
+ }
@@ -32,6 +32,8 @@ export declare function isCmdShell(shell: string): boolean;
32
32
  * parse `-l` as the command and fail with `The term '-l' is not recognized`.
33
33
  */
34
34
  export declare function isPowerShell(shell: string): boolean;
35
+ /** Whether the executable is a known shell whose command language uses POSIX quoting. */
36
+ export declare function isPosixShell(shell: string): boolean;
35
37
  /**
36
38
  * Resolve a basic shell (bash or sh) as fallback.
37
39
  */
@@ -16,8 +16,30 @@ export declare class ExtraCaError extends Error {
16
16
  cause?: unknown;
17
17
  });
18
18
  }
19
+ /**
20
+ * Returns the PEM bytes referenced by `NODE_EXTRA_CA_CERTS`, or `undefined`
21
+ * when the env var is unset/empty.
22
+ *
23
+ * Accepts the same shapes Node accepts plus an inline-PEM escape hatch:
24
+ * - Inline PEM (`-----BEGIN CERTIFICATE-----...`). Literal `\n` escapes in
25
+ * the env value are expanded so callers can ship single-line PEMs through
26
+ * shell exports.
27
+ * - File path. Anything that does not contain a PEM header is treated as a
28
+ * path, matching Node's "extensionless filename is still a path" contract.
29
+ * `ENOENT` becomes {@link ExtraCaError}; other I/O errors bubble.
30
+ */
31
+ export declare function resolveExtraCa(): string | undefined;
19
32
  /** Test seam: drop the cached PEM so a follow-up call re-reads the env. */
20
33
  export declare function __resetExtraCaCache(): void;
34
+ /**
35
+ * Merge `extraCa` into `init.tls.ca`. When the caller has not supplied a CA
36
+ * list, the system root store is included alongside the extra bundle —
37
+ * Bun's `tls.ca` replaces the default trust store, so omitting roots would
38
+ * break every public host. When the caller already curated a list (e.g.
39
+ * Anthropic Foundry's mTLS options, which already seed
40
+ * `tls.rootCertificates`), only the extra CA is appended.
41
+ */
42
+ export declare function withExtraCaInit(init: RequestInit | undefined, extraCa: string): RequestInit;
21
43
  /**
22
44
  * Wrap `fetchImpl` so every call honours `NODE_EXTRA_CA_CERTS`. Idempotent:
23
45
  * a fetch already wrapped is returned unchanged so repeated composition
@@ -26,12 +48,3 @@ export declare function __resetExtraCaCache(): void;
26
48
  * default deployments pay nothing.
27
49
  */
28
50
  export declare function wrapFetchForExtraCa(fetchImpl: FetchImpl): FetchImpl;
29
- /**
30
- * Convenience for options-bag composition (e.g. the stream-entry path in
31
- * `@linxiraos/pi-ai`'s `stream.ts`, which mirrors `withRequestDebugFetch` so
32
- * the proxy/debug/extra-CA wrappers compose uniformly). No-op when the env
33
- * var is unset.
34
- */
35
- export declare function withExtraCaFetch<T extends {
36
- fetch?: FetchImpl;
37
- } | undefined>(options: T): T;
package/package.json CHANGED
@@ -1,44 +1,36 @@
1
1
  {
2
- "type": "module",
3
- "name": "@linxiraos/pi-utils",
4
- "version": "1.1.8",
5
- "description": "Shared utilities for pi packages",
6
- "homepage": "https://linxira-os.github.io/zeta/",
7
- "author": "Can Boluk",
8
- "license": "MIT",
9
- "repository": {
10
- "type": "git",
11
- "url": "git+https://github.com/Linxira-OS/linxira-zeta.git",
12
- "directory": "packages/utils"
13
- },
14
- "bugs": {
15
- "url": "https://github.com/Linxira-OS/linxira-zeta/issues"
2
+ "engines": {
3
+ "bun": ">=1.3.14"
16
4
  },
17
- "keywords": [
18
- "utilities",
19
- "cli",
20
- "logging",
21
- "streams"
22
- ],
23
- "main": "./src/index.ts",
24
- "types": "./dist/types/index.d.ts",
25
- "scripts": {
26
- "check": "biome check . && bun run check:types",
27
- "check:types": "tsgo -p tsconfig.json --noEmit",
28
- "lint": "biome lint .",
29
- "test": "bun test --parallel",
30
- "fix": "biome check --write --unsafe .",
31
- "fmt": "biome format --write ."
5
+ "devDependencies": {
6
+ "@types/bun": "^1.3.14"
32
7
  },
33
8
  "dependencies": {
34
- "@linxiraos/pi-natives": "1.1.8"
9
+ "@linxiraos/pi-natives": "1.1.10"
35
10
  },
36
- "devDependencies": {
37
- "@types/bun": "^1.3.14"
11
+ "scripts": {
12
+ "test": "bun test --parallel",
13
+ "check:types": "tsgo -p tsconfig.json --noEmit",
14
+ "check": "tsgo -p tsconfig.json --noEmit"
38
15
  },
39
- "engines": {
40
- "bun": ">=1.3.14"
16
+ "exports": {
17
+ "./*.js": "./src/*.ts",
18
+ "./*": {
19
+ "import": "./src/*.ts",
20
+ "types": "./dist/types/*.d.ts"
21
+ },
22
+ "./ar": {
23
+ "import": "./src/ar/index.ts",
24
+ "types": "./dist/types/ar/index.d.ts"
25
+ },
26
+ ".": {
27
+ "import": "./src/index.ts",
28
+ "types": "./dist/types/index.d.ts"
29
+ }
41
30
  },
31
+ "types": "./dist/types/index.d.ts",
32
+ "main": "./src/index.ts",
33
+ "type": "module",
42
34
  "files": [
43
35
  "src",
44
36
  "README.md",
@@ -47,19 +39,24 @@
47
39
  "THIRD-PARTY-NOTICES.txt",
48
40
  "dist/types"
49
41
  ],
50
- "exports": {
51
- ".": {
52
- "types": "./dist/types/index.d.ts",
53
- "import": "./src/index.ts"
54
- },
55
- "./ar": {
56
- "types": "./dist/types/ar/index.d.ts",
57
- "import": "./src/ar/index.ts"
58
- },
59
- "./*": {
60
- "types": "./dist/types/*.d.ts",
61
- "import": "./src/*.ts"
62
- },
63
- "./*.js": "./src/*.ts"
64
- }
42
+ "repository": {
43
+ "directory": "packages/utils",
44
+ "url": "git+https://github.com/Linxira-OS/linxira-zeta.git",
45
+ "type": "git"
46
+ },
47
+ "author": "Can Boluk",
48
+ "license": "MIT",
49
+ "bugs": {
50
+ "url": "https://github.com/Linxira-OS/linxira-zeta/issues"
51
+ },
52
+ "homepage": "https://linxira-os.github.io/zeta/",
53
+ "keywords": [
54
+ "utilities",
55
+ "cli",
56
+ "logging",
57
+ "streams"
58
+ ],
59
+ "description": "Shared utilities for pi packages",
60
+ "version": "1.1.10",
61
+ "name": "@linxiraos/pi-utils"
65
62
  }