@mrgoonie/multix 1.0.8 → 1.0.9

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
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.9](https://github.com/mrgoonie/multix-cli/compare/v1.0.8...v1.0.9) (2026-10-11)
9
+
10
+
11
+ ### Fixed
12
+
13
+ * **build:** ship the programmatic entry point declared in package.json ([#132](https://github.com/mrgoonie/multix-cli/issues/132)) ([1c41397](https://github.com/mrgoonie/multix-cli/commit/1c41397c0b8fda141311fef0fbc5c278b660a67f))
14
+
8
15
  ## [1.0.8](https://github.com/mrgoonie/multix-cli/compare/v1.0.7...v1.0.8) (2026-10-11)
9
16
 
10
17
 
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Secret redaction helpers. Runtime-neutral (no node:* imports) so they can be
3
+ * shared by the CLI and non-Node runtimes such as a Cloudflare Worker.
4
+ */
5
+ /**
6
+ * Redact an API key for safe logging: show first 6 + last 4 chars.
7
+ * e.g. "AIzaSy...abcd"
8
+ */
9
+ declare function redact(key: string): string;
10
+ /**
11
+ * Strip the query string and fragment from a URL so API keys passed as query
12
+ * parameters and pre-signed media tokens never reach logs or error messages.
13
+ * Opaque URLs (data:, blob:, file:) collapse to their scheme. Secrets embedded
14
+ * in the path itself are not detected. Non-URL input is returned unchanged
15
+ * except for anything after "?" or "#".
16
+ */
17
+ declare function redactUrl(url: string): string;
18
+
19
+ interface LoadEnvOptions {
20
+ /**
21
+ * Names a project env file (cwd/.env, or a legacy `.claude` file under cwd)
22
+ * may set: the variables multix reads (see src/project-env-names.ts). Shell
23
+ * variables and user-level files are not filtered.
24
+ */
25
+ isProjectEnvName: (name: string) => boolean;
26
+ }
27
+ /**
28
+ * Resolve a single environment variable by name.
29
+ * Returns undefined if not set (after env loading).
30
+ */
31
+ declare function resolveKey(name: string): string | undefined;
32
+
33
+ /**
34
+ * Custom error hierarchy for multix CLI.
35
+ * All errors carry a short `code` string for programmatic handling.
36
+ */
37
+ declare class MultixError extends Error {
38
+ readonly code: string;
39
+ constructor(message: string, code: string, options?: ErrorOptions);
40
+ }
41
+ /** Missing or invalid configuration (env vars, flags). */
42
+ declare class ConfigError extends MultixError {
43
+ constructor(message: string, options?: ErrorOptions);
44
+ }
45
+ /** Provider API returned an error or unexpected response. */
46
+ declare class ProviderError extends MultixError {
47
+ readonly provider: string;
48
+ constructor(message: string, provider: string, options?: ErrorOptions);
49
+ }
50
+ /** Input validation failed (zod parse, file not found, etc.). */
51
+ declare class ValidationError extends MultixError {
52
+ constructor(message: string);
53
+ }
54
+ /**
55
+ * HTTP request failed. The message and `url` show the request URL with its
56
+ * query string redacted so API keys and signed-URL tokens never reach
57
+ * terminal, CI or Worker logs; `rawUrl` keeps the original (non-enumerable).
58
+ */
59
+ declare class HttpError extends MultixError {
60
+ readonly status: number;
61
+ readonly snippet: string;
62
+ /** Parsed Retry-After header (ms), when the server sent one. */
63
+ readonly retryAfterMs?: number | undefined;
64
+ /** Request URL with query string and fragment redacted — safe to log or serialize. */
65
+ readonly url: string;
66
+ /** Raw request URL. Non-enumerable so JSON.stringify/console output never includes it. */
67
+ readonly rawUrl: string;
68
+ constructor(status: number, snippet: string, url: string,
69
+ /** Parsed Retry-After header (ms), when the server sent one. */
70
+ retryAfterMs?: number | undefined, options?: ErrorOptions);
71
+ }
72
+
73
+ /**
74
+ * Discriminated union Result type for all provider operations.
75
+ * Avoids throwing across async boundaries for expected failures.
76
+ */
77
+ type OkResult<T> = {
78
+ status: "success";
79
+ } & T;
80
+ type ErrResult = {
81
+ status: "error";
82
+ error: string;
83
+ };
84
+ type Result<T> = OkResult<T> | ErrResult;
85
+ /** Wrap a success payload into a Result. */
86
+ declare function ok<T extends Record<string, unknown>>(data: T): OkResult<T>;
87
+ /** Wrap an error message into a Result. */
88
+ declare function err(message: string): ErrResult;
89
+ /** Type guard: check if Result is success. */
90
+ declare function isOk<T>(result: Result<T>): result is OkResult<T>;
91
+ /** Type guard: check if Result is error. */
92
+ declare function isErr<T>(result: Result<T>): result is ErrResult;
93
+
94
+ /**
95
+ * Output directory management for generated files.
96
+ * Default: ./multix-output/ (override via MULTIX_OUTPUT_DIR env var).
97
+ * Created lazily on first call; memoized thereafter.
98
+ */
99
+ /**
100
+ * Returns the absolute path to the output directory, creating it if needed.
101
+ * Respects MULTIX_OUTPUT_DIR environment variable.
102
+ */
103
+ declare function getOutputDir(): string;
104
+
105
+ /**
106
+ * Parse a CLI `--input` argument as inline JSON or `@path/to/file.json`.
107
+ * Shared by provider "run" commands that forward model-specific payloads.
108
+ */
109
+ declare function parseJsonInput(raw: string, flag?: string): Record<string, unknown>;
110
+
111
+ /**
112
+ * Redirect handling for requests that carry credentials (API key headers).
113
+ * Runtime-neutral: no node:* imports, so provider HTTP clients that also run
114
+ * outside Node (e.g. a Cloudflare Worker) can share it.
115
+ *
116
+ * fetch() follows redirects by itself and, per the Fetch spec, strips only
117
+ * `Authorization` on a cross-origin hop: custom key headers such as
118
+ * `X-Api-Key` or `x-goog-api-key` would be forwarded to the new origin. The
119
+ * helpers here follow redirects one hop at a time instead, so request headers
120
+ * never reach another origin (an https to http hop is an origin change too).
121
+ */
122
+
123
+ /** Minimal fetch signature (matches globalThis.fetch for string URLs). */
124
+ type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
125
+
126
+ /**
127
+ * Runtime-neutral JSON-over-HTTP helper (no node:* imports).
128
+ * Provider clients that must also run outside Node (e.g. a Cloudflare Worker)
129
+ * use this with an injected `fetch`; the CLI's `httpJson` delegates here.
130
+ */
131
+
132
+ interface FetchJsonOptions {
133
+ url: string;
134
+ method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
135
+ headers?: Record<string, string>;
136
+ body?: Record<string, unknown> | unknown[] | any;
137
+ timeoutMs?: number;
138
+ /** Defaults to globalThis.fetch, resolved at call time so tests can stub it. */
139
+ fetch?: FetchLike;
140
+ }
141
+ /**
142
+ * Parse a Retry-After header (delta-seconds or HTTP-date) into milliseconds.
143
+ * Returns undefined when absent or unparseable.
144
+ */
145
+ declare function parseRetryAfter(value: string | null | undefined, now?: number): number | undefined;
146
+ /** Build an HttpError from a non-2xx response, reading a bounded body snippet. */
147
+ declare function httpErrorFromResponse(response: Response, url: string): Promise<HttpError>;
148
+ /**
149
+ * Make an HTTP request and parse the JSON response.
150
+ * Throws HttpError on non-2xx status or network failure (status 0); a
151
+ * deterministic fetch rejection (invalid header or URL) becomes a
152
+ * non-retryable ConfigError or ProviderError (see `toFetchError`). When the
153
+ * request carries a header that may be a credential (anything but Accept,
154
+ * Accept-Language, Content-Type, User-Agent), redirects are followed within
155
+ * the same origin only; one to another origin is refused (HttpError with the
156
+ * 3xx status) so the key is never sent there. Requests without such headers
157
+ * follow redirects to any origin.
158
+ */
159
+ declare function fetchJson<T>(opts: FetchJsonOptions): Promise<T>;
160
+
161
+ /**
162
+ * HTTP utilities for multix CLI providers.
163
+ * Uses Node 20+ globalThis.fetch so tests can mock it via globalThis.fetch assignment.
164
+ * downloadFile streams to disk without buffering the full body.
165
+ */
166
+
167
+ /**
168
+ * Make an HTTP request and parse the JSON response.
169
+ * Throws HttpError on non-2xx status.
170
+ */
171
+ declare function httpJson<T>(opts: FetchJsonOptions): Promise<T>;
172
+ /**
173
+ * Download a URL to a local file path, streaming to avoid large memory usage.
174
+ * Parent directory is created if it does not exist.
175
+ * Resolves with the response Content-Type (if any) so callers can name
176
+ * extensionless outputs. `headers` (e.g. an API key) are sent to the URL's
177
+ * own origin only: redirects are then followed by hand and the headers are
178
+ * dropped once a hop changes origin, including an https to http downgrade.
179
+ */
180
+ declare function downloadFile(url: string, dest: string, timeoutMs?: number, headers?: Record<string, string>): Promise<string | undefined>;
181
+ /**
182
+ * Decode a data: URI or download an http(s) URL, returning raw bytes.
183
+ */
184
+ declare function fetchBytes(urlOrDataUri: string): Promise<Buffer>;
185
+
186
+ /**
187
+ * Bounded polling for asynchronous provider jobs (predictions, video renders).
188
+ * Runtime-neutral (no node:* imports): exponential backoff with jitter, a hard
189
+ * deadline, transient-error retries, and Retry-After support on 429.
190
+ */
191
+
192
+ declare class PollTimeoutError extends MultixError {
193
+ readonly lastStatus?: unknown | undefined;
194
+ constructor(message: string, lastStatus?: unknown | undefined);
195
+ }
196
+ interface PollOptions<T> {
197
+ /**
198
+ * Fetch the current job state. `remainingMs` is the time left before the
199
+ * deadline; use it as the request timeout so one slow request cannot
200
+ * overrun the overall wait.
201
+ */
202
+ fetchStatus: (ctx: {
203
+ remainingMs: number;
204
+ attempt: number;
205
+ }) => Promise<T>;
206
+ /** True once the job reached a terminal state (success OR failure). */
207
+ isTerminal: (state: T) => boolean;
208
+ /** Hard upper bound for the whole wait. */
209
+ timeoutMs: number;
210
+ /** First delay between polls. Default 3000ms. */
211
+ intervalMs?: number;
212
+ /** Ceiling for the backoff delay. Default 15000ms. */
213
+ maxIntervalMs?: number;
214
+ /** Multiplier applied after each poll. Default 1.5. */
215
+ backoffFactor?: number;
216
+ /** Add up to ±20% random jitter to each delay. Default true. */
217
+ jitter?: boolean;
218
+ /** Lower bound for any delay, including Retry-After: 0. Default 500ms. */
219
+ minIntervalMs?: number;
220
+ /** Errors that should be retried instead of aborting. Default: network/timeout, 408, 429, 5xx except 501/505. */
221
+ isTransientError?: (error: unknown) => boolean;
222
+ /** Called after each successful status fetch. */
223
+ onPoll?: (state: T, attempt: number) => void;
224
+ /** Appended to the timeout message, e.g. a resume command. */
225
+ resumeHint?: string;
226
+ sleep?: (ms: number) => Promise<void>;
227
+ now?: () => number;
228
+ random?: () => number;
229
+ }
230
+ /**
231
+ * Network failures and timeouts (status 0), 408, rate limits and server errors
232
+ * are worth retrying. Anything else — ConfigError, ValidationError, other 4xx,
233
+ * 501 Not Implemented, 505 HTTP Version Not Supported, plain errors — fails the
234
+ * same way on every attempt.
235
+ */
236
+ declare function isTransientHttpError(error: unknown): boolean;
237
+ /**
238
+ * Poll `fetchStatus` until `isTerminal` returns true or the deadline passes.
239
+ * Returns the terminal state; callers decide whether it is success or failure.
240
+ * Throws PollTimeoutError on timeout and rethrows non-transient errors.
241
+ */
242
+ declare function pollUntil<T>(opts: PollOptions<T>): Promise<T>;
243
+
244
+ /**
245
+ * Simple ANSI-coloured logger for multix CLI.
246
+ * Uses no external deps — raw ANSI escape codes only.
247
+ * debug() is a no-op unless verbose mode is enabled.
248
+ */
249
+ interface Logger {
250
+ info(msg: string): void;
251
+ success(msg: string): void;
252
+ warn(msg: string): void;
253
+ error(msg: string): void;
254
+ debug(msg: string): void;
255
+ /** Print a section header. */
256
+ header(msg: string): void;
257
+ }
258
+ declare function createLogger(opts: {
259
+ verbose: boolean;
260
+ }): Logger;
261
+ /** Convenience singleton used when verbose flag is not yet parsed. */
262
+ declare const defaultLogger: Logger;
263
+
264
+ /**
265
+ * Generic thumbnail/cover detection for provider video responses.
266
+ *
267
+ * Walks an arbitrary object looking for a small set of well-known field names
268
+ * (cover_image_url, thumbnail_url, etc.) whose value is an http(s) URL pointing
269
+ * to an image. Used by all video commands so a generated video's thumbnail is
270
+ * downloaded next to the .mp4 when the provider exposes one.
271
+ */
272
+
273
+ /**
274
+ * Recursively walk `obj` for the first known thumbnail field whose value is a
275
+ * plausible image URL. Returns the URL string or null.
276
+ */
277
+ declare function detectThumbUrl(obj: unknown): string | null;
278
+ /**
279
+ * Downloads `url` to `dest`; lets a caller apply its own transport and naming
280
+ * policy. A result with a string `dest` names the file actually used (e.g. a
281
+ * free name beside `dest`, or a thumbnail that was already saved).
282
+ */
283
+ type ThumbDownloader = (url: string, dest: string) => Promise<unknown>;
284
+ /**
285
+ * Derive a `<base>_thumb.<ext>` path next to `videoPath` and download `thumbUrl`.
286
+ * Extension comes from the URL when recognizable, otherwise falls back to .jpg.
287
+ * Returns the saved path on success, null on failure (logged as warn).
288
+ */
289
+ declare function downloadThumbBeside(thumbUrl: string, videoPath: string, logger?: Logger, download?: ThumbDownloader): Promise<string | null>;
290
+ /**
291
+ * Convenience: detect and download in one call. Honors `skip` (e.g. --no-thumb).
292
+ * Also copies the thumb next to a user-supplied --output path when given.
293
+ */
294
+ declare function maybeDownloadThumb(source: unknown, videoPath: string, opts?: {
295
+ skip?: boolean;
296
+ copyTo?: string;
297
+ logger?: Logger;
298
+ download?: ThumbDownloader;
299
+ }): Promise<string | null>;
300
+
301
+ /**
302
+ * Variable names a project-level `.env` may set: the names multix itself
303
+ * reads, minus the opt-ins a repository must not switch on for the user.
304
+ * Anything else in a project `.env` (NODE_OPTIONS, npm_config_*, LD_PRELOAD,
305
+ * proxy and TLS settings, …) is ignored by loadEnv, so a cloned repository
306
+ * cannot change how multix or the tools it spawns behave.
307
+ *
308
+ * Built from the constants the providers export where they exist; settings
309
+ * that are read inline are listed by hand below. Unit tests check the list
310
+ * against `.env.example` and against the names the source reads in the
311
+ * common forms (process.env reads, resolveKey calls, *_ENV constants, any
312
+ * `MULTIX_*` literal); a name read some other way must be added here by hand.
313
+ */
314
+ /**
315
+ * Names multix reads that a project `.env` may NOT set: opt-ins that widen
316
+ * what a repository can do with the user's keys. MULTIX_LEGACY_CLAUDE_ENV
317
+ * would load credentials from the home folder's `.claude` files into a run
318
+ * the repository controls. Set these in the shell or ~/.multix/.env.
319
+ */
320
+ declare const PROJECT_ENV_EXCLUDED: ReadonlySet<string>;
321
+ /** Every fixed name a project `.env` may set (numbered Gemini keys are matched by pattern). */
322
+ declare const PROJECT_ENV_NAMES: ReadonlySet<string>;
323
+ /**
324
+ * True when a project `.env` may set `name`. On Windows env names are
325
+ * case-insensitive, so the check ignores case there, as the OS does.
326
+ */
327
+ declare function isProjectEnvName(name: string, platform?: NodeJS.Platform): boolean;
328
+
329
+ /**
330
+ * Programmatic API — re-exports public surface for library consumers.
331
+ */
332
+
333
+ /**
334
+ * Load .env files once (see src/core/env-loader.ts). With no argument a
335
+ * project `.env` may only set the names multix reads (isProjectEnvName).
336
+ */
337
+ declare function loadEnv(options?: LoadEnvOptions): void;
338
+
339
+ export { ConfigError, type ErrResult, type FetchJsonOptions, type FetchLike, HttpError, type FetchJsonOptions as HttpJsonOptions, type LoadEnvOptions, type Logger, MultixError, type OkResult, PROJECT_ENV_EXCLUDED, PROJECT_ENV_NAMES, type PollOptions, PollTimeoutError, ProviderError, type Result, ValidationError, createLogger, defaultLogger, detectThumbUrl, downloadFile, downloadThumbBeside, err, fetchBytes, fetchJson, getOutputDir, httpErrorFromResponse, httpJson, isErr, isOk, isProjectEnvName, isTransientHttpError, loadEnv, maybeDownloadThumb, ok, parseJsonInput, parseRetryAfter, pollUntil, redact, redactUrl, resolveKey };