@verbatra/sdk 0.3.0 → 0.4.0

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/dist/index.d.ts CHANGED
@@ -1,15 +1,12 @@
1
+ export { SupportedFormat } from '@verbatra/core';
1
2
  import { AnthropicModel, OpenAiModel, GeminiModel, ProviderNotice, TranslationProvider } from '@verbatra/ai-providers';
2
3
  import { z } from 'zod';
3
4
  import { AdapterRegistry } from '@verbatra/format-adapters';
4
5
 
5
6
  /**
6
- * The provider section of the config: a discriminated union over the provider id,
7
- * reusing each provider's own exported config schema. There is no key field anywhere
8
- * in this union. The provider reads its API key from the environment at construction.
9
- *
10
- * This union and the factory table below are co-located on purpose: adding a provider
11
- * is a single edit here (one union variant plus one table entry), and the mapped-type
12
- * table makes the two sets provably identical at compile time.
7
+ * The provider section of the config: a discriminated union over the provider id, reusing each
8
+ * provider's own config schema. There is no key field anywhere in this union; the provider reads its
9
+ * API key from the environment at construction.
13
10
  */
14
11
  declare const providerConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
15
12
  id: z.ZodLiteral<"anthropic">;
@@ -38,6 +35,11 @@ declare const providerConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
38
35
  type ProviderConfig = z.infer<typeof providerConfigSchema>;
39
36
  type ProviderId = ProviderConfig["id"];
40
37
 
38
+ /**
39
+ * The verbatra project configuration. It carries no API key (the provider reads its key from the
40
+ * environment), and unknown top-level keys are rejected so a stray secret cannot hide in it. Validated
41
+ * with zod at the boundary regardless of where it was loaded from.
42
+ */
41
43
  declare const verbatraConfigSchema: z.ZodObject<{
42
44
  sourceLocale: z.ZodString;
43
45
  targetLocales: z.ZodArray<z.ZodString>;
@@ -46,6 +48,9 @@ declare const verbatraConfigSchema: z.ZodObject<{
46
48
  "vue-i18n-json": "vue-i18n-json";
47
49
  "next-intl-json": "next-intl-json";
48
50
  "ngx-translate-json": "ngx-translate-json";
51
+ xliff: "xliff";
52
+ yaml: "yaml";
53
+ arb: "arb";
49
54
  }>;
50
55
  files: z.ZodObject<{
51
56
  pattern: z.ZodString;
@@ -86,16 +91,6 @@ declare const verbatraConfigSchema: z.ZodObject<{
86
91
  }, z.core.$strict>;
87
92
  type VerbatraConfig = z.infer<typeof verbatraConfigSchema>;
88
93
 
89
- /**
90
- * The closed set of a provider's known model IDs: exactly the string literals its SDK
91
- * model type ships, with the SDK's own open `string & {}` arm stripped out. Distributing
92
- * over the union and dropping any arm the wide `string` extends leaves only the literals,
93
- * so the authoring field offers and ACCEPTS only the selected provider's known models. A
94
- * foreign or unknown model (for example a Claude model under `id: "gemini"`) is a type
95
- * error at authoring time. This is a static authoring constraint only; the runtime schema
96
- * stays `z.string().min(1)` and still accepts any non-empty string, so a brand-new model
97
- * the installed SDK does not yet list is flagged in the editor but still runs.
98
- */
99
94
  type KnownModels<M extends string> = M extends string ? (string extends M ? never : M) : never;
100
95
  type AuthoringVariant<Id extends ProviderId, M extends string> = Extract<ProviderConfig, {
101
96
  id: Id;
@@ -108,13 +103,6 @@ type AuthoringVariant<Id extends ProviderId, M extends string> = Extract<Provide
108
103
  model: KnownModels<M>;
109
104
  };
110
105
  } : never : never;
111
- /**
112
- * The authoring view of one provider variant, keyed by id. LLM providers (anthropic,
113
- * openai, gemini) restrict `options.model` to that provider's known model literals; DeepL
114
- * has no model field and is carried through unchanged. Keying by id (rather than a flat
115
- * union) lets {@link AuthoringConfigFor} select exactly one variant for a literal id, so
116
- * the `options.model` site is one provider's literal set and never a multi-provider union.
117
- */
118
106
  type AuthoringProviderVariant = {
119
107
  anthropic: AuthoringVariant<"anthropic", AnthropicModel>;
120
108
  openai: AuthoringVariant<"openai", OpenAiModel>;
@@ -124,20 +112,10 @@ type AuthoringProviderVariant = {
124
112
  }>;
125
113
  };
126
114
  /**
127
- * The authoring view of the whole config for a given provider id. It is structurally a
128
- * {@link VerbatraConfig} whose `provider` is the single authoring variant for `TId`.
129
- *
130
- * When `TId` is a single provider literal, `provider` is one concrete variant, so
131
- * `options.model` is that provider's known model literals alone and not a union across
132
- * providers. `defineConfig` declares one overload per provider parameterized on this type
133
- * (`AuthoringConfigFor<"openai">` and so on), so overload resolution picks the variant from
134
- * the `provider.id` literal. That, together with the closed {@link KnownModels} set, makes a
135
- * foreign model (for example a Claude model under `id: "gemini"`) a type error and aims to
136
- * keep editors with weaker discriminated-union narrowing (for example the JetBrains/WebStorm
137
- * completion engine) offering only the selected provider's models, since each overload's
138
- * parameter is already a single variant with no nested union to narrow. When `TId` defaults
139
- * to `ProviderId`, `provider` is the full authoring union. Every value assignable here is
140
- * assignable to {@link VerbatraConfig}, because a model literal is a subtype of `string`.
115
+ * The authoring view of the whole config for a provider id: a {@link VerbatraConfig} whose `provider`
116
+ * is that id's single authoring variant, so `options.model` offers only that provider's models. When
117
+ * `TId` defaults to `ProviderId`, `provider` is the full authoring union. Every value here is assignable
118
+ * to {@link VerbatraConfig}, since a model literal is a subtype of `string`.
141
119
  */
142
120
  type AuthoringConfigFor<TId extends ProviderId = ProviderId> = Omit<VerbatraConfig, "provider"> & {
143
121
  provider: AuthoringProviderVariant[TId];
@@ -150,30 +128,16 @@ type AuthoringConfigFor<TId extends ProviderId = ProviderId> = Omit<VerbatraConf
150
128
  type AuthoringConfig = AuthoringConfigFor;
151
129
 
152
130
  /**
153
- * Identity helper for authoring a code-defined verbatra.config.ts. It returns its
154
- * argument unchanged; its only purpose is to give the author full type inference and
155
- * editor autocomplete on the config object.
131
+ * Identity helper for authoring a typed `verbatra.config.ts`. It returns its argument unchanged; its
132
+ * only purpose is full type inference and editor autocomplete on the config object, including
133
+ * completion of the selected provider's known model literals.
156
134
  *
157
- * It is declared as one overload per provider id rather than a single generic. Each
158
- * overload's parameter is the concrete single-provider authoring config
159
- * ({@link AuthoringConfigFor}), so `provider.options.model` is already that one provider's
160
- * known model literals, with no union across providers and no generic for an editor to
161
- * infer. Overload resolution selects the matching overload from the `provider.id` literal,
162
- * so the editor offers only the selected provider's models and a foreign or unknown model
163
- * (for example a Claude model under `id: "gemini"`) is a type error. This is deliberately
164
- * overload-based: a generic whose type parameter is inferred from a nested `provider.id`
165
- * collapses correctly in tsserver but not in editors with weaker inference (notably the
166
- * JetBrains/WebStorm completion engine), which then fall back to the full union and offer
167
- * every provider's models. Concrete per-provider signatures avoid that inference step.
135
+ * The model restriction is a static authoring constraint only: `loadConfig` validates `model` as a
136
+ * non-empty string, so a model the installed provider SDK does not yet list is flagged in the editor
137
+ * but still runs.
168
138
  *
169
- * The final overload accepts the full {@link AuthoringConfig} union, so a config whose
170
- * provider id is not a single literal (for example a value typed as the union) still
171
- * type-checks.
172
- *
173
- * The return type is the runtime {@link VerbatraConfig}. The model restriction is a static
174
- * authoring constraint, not a runtime one: `loadConfig` still validates `model` as
175
- * `z.string().min(1)`, so a model the installed provider SDK does not yet list is flagged
176
- * in the editor but still runs.
139
+ * @param config - The verbatra configuration object.
140
+ * @returns The same config, typed as {@link VerbatraConfig}.
177
141
  */
178
142
  declare function defineConfig(config: AuthoringConfigFor<"anthropic">): VerbatraConfig;
179
143
  declare function defineConfig(config: AuthoringConfigFor<"openai">): VerbatraConfig;
@@ -190,12 +154,10 @@ interface LoadConfigOptions {
190
154
  */
191
155
  readonly configOverride?: unknown;
192
156
  /**
193
- * An explicit config file to load instead of searching. A relative path resolves
194
- * against `cwd` (an absolute path is used as given), then cosmiconfig's load() parses
195
- * it with the same loaders search uses (.json/.yaml/.ts), and it is zod-validated at
196
- * the boundary exactly like a searched file. A missing file is CONFIG_NOT_FOUND; a
197
- * present-but-unparseable/invalid file is CONFIG_INVALID. Precedence: configOverride
198
- * wins over configPath, which wins over search.
157
+ * An explicit config file to load instead of searching. A relative path resolves against `cwd`; an
158
+ * absolute path is used as given. Parsed and zod-validated exactly like a searched file: a missing
159
+ * file is `CONFIG_NOT_FOUND`, a present but invalid one is `CONFIG_INVALID`. Takes precedence over
160
+ * search, but `configOverride` takes precedence over it.
199
161
  */
200
162
  readonly configPath?: string;
201
163
  }
@@ -259,14 +221,148 @@ declare class SdkError extends Error {
259
221
  constructor(code: SdkErrorCode, message: string);
260
222
  }
261
223
 
224
+ /** Outcome of a bounded read: the content, or why it could not be read in bounds. */
225
+ type BoundedFileRead = {
226
+ readonly kind: "ok";
227
+ readonly content: string;
228
+ } | {
229
+ readonly kind: "missing";
230
+ } | {
231
+ readonly kind: "too-large";
232
+ };
233
+ /** Outcome of a bounded binary read: the bytes, or why they could not be read in bounds. */
234
+ type BoundedBytesRead = {
235
+ readonly kind: "ok";
236
+ readonly bytes: Uint8Array;
237
+ } | {
238
+ readonly kind: "missing";
239
+ } | {
240
+ readonly kind: "too-large";
241
+ };
242
+ /**
243
+ * The minimal file-system surface the SDK needs. Reads are bounded and writes are atomic. Injectable so
244
+ * tests stay deterministic; the format adapters do their own file IO and bypass this seam.
245
+ */
246
+ interface SdkFs {
247
+ /** Whether a readable file exists at the path. */
248
+ fileExists(path: string): Promise<boolean>;
249
+ /**
250
+ * Read a file as UTF-8 through a single handle, bounded to maxBytes. TOCTOU-safe: the handle is
251
+ * fstat'd and the read never advances past the sized length, so swapping in a larger file cannot
252
+ * bypass the cap. A missing or unreadable path is "missing"; a file over the cap is "too-large".
253
+ */
254
+ readFileBounded(path: string, maxBytes: number): Promise<BoundedFileRead>;
255
+ /** Read a file as raw bytes with the same TOCTOU-safe, bounded discipline as {@link readFileBounded}. */
256
+ readBytesBounded(path: string, maxBytes: number): Promise<BoundedBytesRead>;
257
+ /** Write atomically: a temp file in the same directory, then rename over the target. */
258
+ writeFile(path: string, data: string): Promise<void>;
259
+ /** Write raw bytes atomically (temp file, then rename over the target). Used for the workbook. */
260
+ writeBytes(path: string, data: Uint8Array): Promise<void>;
261
+ }
262
+
263
+ /** Per-locale drift status: counts only, no key lists. */
264
+ interface LocaleCheckSummary {
265
+ /** The target locale this entry reports on. */
266
+ readonly locale: string;
267
+ /** Keys present in source but absent from the target. */
268
+ readonly missing: number;
269
+ /** Keys whose source changed since the target was last translated. */
270
+ readonly stale: number;
271
+ /** Keys whose recorded baseline still matches the source. */
272
+ readonly upToDate: number;
273
+ /** True when nothing needs (re)translating for this locale. */
274
+ readonly inSync: boolean;
275
+ }
276
+ /** The aggregate read-only status across all checked target locales. */
277
+ interface CheckSummary {
278
+ /** True only when every checked locale is in sync. */
279
+ readonly inSync: boolean;
280
+ /** One entry per checked target locale, in config order. */
281
+ readonly locales: readonly LocaleCheckSummary[];
282
+ }
283
+ /** Input for {@link check}: the validated config and which locales to check. */
284
+ interface CheckInput {
285
+ /** The validated configuration (typically from {@link loadConfig}). */
286
+ readonly config: VerbatraConfig;
287
+ /** Directory the file pattern and lock-file resolve against; defaults to cwd. */
288
+ readonly cwd?: string;
289
+ /** Subset of target locales to check; defaults to all configured target locales. */
290
+ readonly locales?: readonly string[];
291
+ }
292
+ /** Composition seam for {@link check}: inject a registry and a file system for tests. */
293
+ interface CheckDeps {
294
+ readonly adapterRegistry?: AdapterRegistry;
295
+ readonly fs?: SdkFs;
296
+ }
297
+ /**
298
+ * Report which keys are missing or stale per target locale, without calling a provider, writing any
299
+ * file, or touching the lock. Each locale is reported as counts only (missing, stale, up-to-date);
300
+ * a locale is `inSync` when nothing is missing or stale, and the top-level `inSync` is true only when
301
+ * every checked locale is. Orphaned keys and integrity are not reported, since they concern a write.
302
+ *
303
+ * @param input - The validated config and which locales to check.
304
+ * @param deps - Optional composition seams (registry, file system) for tests.
305
+ * @returns The aggregate and per-locale drift status.
306
+ * @throws {@link SdkError} `UNKNOWN_FORMAT`, `SOURCE_UNREADABLE`, `SOURCE_INVALID`, `LOCK_FILE_INVALID`
307
+ * with the same meanings as in `translate`.
308
+ */
309
+ declare function check(input: CheckInput, deps?: CheckDeps): Promise<CheckSummary>;
310
+
311
+ /** Per-locale pending change: the key lists, not counts. */
312
+ interface LocaleDiff {
313
+ /** The target locale this entry reports on. */
314
+ readonly locale: string;
315
+ /** Keys present in source but absent from the target; would be added by a run. */
316
+ readonly missing: readonly string[];
317
+ /** Keys whose source changed since last translated; would be re-translated. */
318
+ readonly changed: readonly string[];
319
+ /** Keys present in target but absent from source; report-only, never pending. */
320
+ readonly orphaned: readonly string[];
321
+ /** True when the locale has missing or changed keys; orphaned keys do not count. */
322
+ readonly hasPendingChanges: boolean;
323
+ }
324
+ /** The aggregate read-only diff across all checked target locales. */
325
+ interface DiffSummary {
326
+ /** True when any checked locale has pending changes. */
327
+ readonly hasPendingChanges: boolean;
328
+ /** One entry per checked target locale, in config order. */
329
+ readonly locales: readonly LocaleDiff[];
330
+ }
331
+ /** Input for {@link diff}: the validated config and which locales to diff. */
332
+ interface DiffInput {
333
+ /** The validated configuration (typically from {@link loadConfig}). */
334
+ readonly config: VerbatraConfig;
335
+ /** Directory the file pattern and lock-file resolve against; defaults to cwd. */
336
+ readonly cwd?: string;
337
+ /** Subset of target locales to diff; defaults to all configured target locales. */
338
+ readonly locales?: readonly string[];
339
+ }
340
+ /** Composition seam for {@link diff}: inject a registry and a file system for tests. */
341
+ interface DiffDeps {
342
+ readonly adapterRegistry?: AdapterRegistry;
343
+ readonly fs?: SdkFs;
344
+ }
345
+ /**
346
+ * Report the exact pending change per target locale as three key lists (missing, changed, orphaned),
347
+ * without calling a provider, writing any file, or touching the lock. This is the detailed sibling of
348
+ * {@link check}. A locale's `hasPendingChanges` is driven by missing or changed only; orphaned keys are
349
+ * reported but do not flip it, since a default `translate` run does not prune. The top-level
350
+ * `hasPendingChanges` is true when any checked locale has pending changes.
351
+ *
352
+ * @param input - The validated config and which locales to diff.
353
+ * @param deps - Optional composition seams (registry, file system) for tests.
354
+ * @returns The aggregate and per-locale pending change.
355
+ * @throws {@link SdkError} `UNKNOWN_FORMAT`, `SOURCE_UNREADABLE`, `SOURCE_INVALID`, `LOCK_FILE_INVALID`
356
+ * with the same meanings as in `translate`.
357
+ */
358
+ declare function diff(input: DiffInput, deps?: DiffDeps): Promise<DiffSummary>;
359
+
262
360
  /** A stable code for an SDK-originated notice (not a provider notice). */
263
361
  type SdkNoticeCode = "PLURAL_CATEGORIES_INCOMPLETE" | "SUB_BATCH_FAILED";
264
362
  /**
265
363
  * A notice raised by the SDK itself (not a provider), structurally identical to a {@link ProviderNotice}
266
364
  * so both share the {@link LocaleSummary.notices} channel. Carries only a stable code and a static,
267
- * secret-free message; never a key value or translatable content. The SDK keeps its own codes here so
268
- * the ai-providers `ProviderNoticeCode` union stays free of SDK concerns (the dependency arrow points
269
- * sdk -> ai-providers, never the reverse).
365
+ * secret-free message; never a key value or translatable content.
270
366
  */
271
367
  interface SdkNotice {
272
368
  /** The stable {@link SdkNoticeCode} for what the SDK is reporting. */
@@ -302,10 +398,9 @@ interface LocaleSummary {
302
398
  /** Translated keys that failed the placeholder-integrity check and were withheld. */
303
399
  readonly integrityMismatches: readonly string[];
304
400
  /**
305
- * Plural-category keys verbatra SYNTHESIZED this run (for example a Polish `items_few` the source
306
- * never supplied), kept distinct from {@link translated} because they were generated from the meaning
307
- * of the source plural forms, not translated 1:1 from a source string. Empty unless plural generation
308
- * was enabled and acted. In a dry-run this is empty (the provider is not called).
401
+ * Plural-category keys verbatra synthesized this run (for example a Polish `items_few` the source
402
+ * never supplied), kept distinct from {@link translated}. Empty unless plural generation was enabled
403
+ * and acted, and empty in a dry-run.
309
404
  */
310
405
  readonly generated: readonly string[];
311
406
  /**
@@ -336,54 +431,6 @@ interface RunSummary {
336
431
  readonly failed: readonly string[];
337
432
  }
338
433
 
339
- /** Outcome of a bounded read: the content, or why it could not be read in bounds. */
340
- type BoundedFileRead = {
341
- readonly kind: "ok";
342
- readonly content: string;
343
- } | {
344
- readonly kind: "missing";
345
- } | {
346
- readonly kind: "too-large";
347
- };
348
- /** Outcome of a bounded binary read: the bytes, or why they could not be read in bounds. */
349
- type BoundedBytesRead = {
350
- readonly kind: "ok";
351
- readonly bytes: Uint8Array;
352
- } | {
353
- readonly kind: "missing";
354
- } | {
355
- readonly kind: "too-large";
356
- };
357
- /**
358
- * The minimal file-system surface the SDK needs: existence checks, the lock-file read/write,
359
- * and the untrusted workbook bytes read/write for the export/import flow. Reads are bounded
360
- * (see {@link readFileBounded} / {@link readBytesBounded}) and writes are atomic. Injectable so
361
- * tests stay deterministic; the format adapters do their own file IO and are not routed through
362
- * this seam.
363
- */
364
- interface SdkFs {
365
- /** Whether a readable file exists at the path. */
366
- fileExists(path: string): Promise<boolean>;
367
- /**
368
- * Read a file as UTF-8 through a single handle, bounded to maxBytes. TOCTOU-safe: the
369
- * handle is fstat'd and the read never advances past the sized length, so swapping the
370
- * path for a larger file after the size check cannot bypass the cap. A missing or
371
- * unreadable path is "missing" (first-run); a file over the cap is "too-large".
372
- */
373
- readFileBounded(path: string, maxBytes: number): Promise<BoundedFileRead>;
374
- /**
375
- * Read a file as raw bytes through a single handle, bounded to maxBytes, with the SAME
376
- * TOCTOU-safe discipline as {@link readFileBounded}: the handle is fstat'd and the read never
377
- * advances past the sized length. Used for the untrusted workbook on import; a file over the
378
- * cap is "too-large", a missing path is "missing".
379
- */
380
- readBytesBounded(path: string, maxBytes: number): Promise<BoundedBytesRead>;
381
- /** Write atomically: a temp file in the same directory, then rename over the target. */
382
- writeFile(path: string, data: string): Promise<void>;
383
- /** Write raw bytes atomically (temp file, then rename over the target). Used for the workbook. */
384
- writeBytes(path: string, data: Uint8Array): Promise<void>;
385
- }
386
-
387
434
  /** Builds the provider from its config. Injectable so tests stay offline. */
388
435
  type CreateProvider = (config: ProviderConfig) => TranslationProvider;
389
436
 
@@ -497,11 +544,10 @@ interface ExportWorkbookResult {
497
544
  }[];
498
545
  }
499
546
  /**
500
- * Export the strings needing human translation into a styled `.xlsx` workbook. Reuses the same
501
- * source read, adapter selection, and lock baseline the translate flow uses, runs `diffResources`
502
- * per target locale to pick the rows (missing and changed by default; add unchanged with
503
- * `includeUnchanged`), hands the neutral row model to `@verbatra/exchange`'s `buildWorkbook`, and
504
- * writes the bytes through the {@link SdkFs} seam. No provider is called and no lock-file is written.
547
+ * Export the strings needing human translation into a styled `.xlsx` workbook. Each target locale is
548
+ * diffed against the source and lock baseline to pick the rows (missing and changed by default; add
549
+ * unchanged with `includeUnchanged`), and the bytes are written to `out`. No provider is called and no
550
+ * lock-file is written.
505
551
  *
506
552
  * @param input - The validated config and export options.
507
553
  * @param deps - Optional composition seams (registry, file system) for tests.
@@ -528,19 +574,16 @@ interface ImportWorkbookDeps {
528
574
  readonly fs?: SdkFs;
529
575
  }
530
576
  /**
531
- * Import a filled workbook back into the locale files. Reads the untrusted workbook through the
532
- * SDK's bounded read, parses it with `@verbatra/exchange`'s `readWorkbook` (which bounds and
533
- * sanitizes it), then for each target-locale data sheet runs the EXISTING core checks (source-drift
534
- * via `contentHash`, placeholder integrity via `checkPlaceholders`, ICU via the adapter's
535
- * `validateMessage`), writes the accepted values through the format adapter, and updates the lock
536
- * through the existing lock logic. Returns a {@link RunSummary} structurally identical to
537
- * `translate`'s, so the CLI formatter and exit-code rule are shared with no special case.
577
+ * Import a filled workbook back into the locale files. Each target-locale data sheet runs the same
578
+ * source-drift, placeholder, and ICU checks as the translate flow, the accepted values are written
579
+ * through the format adapter, and the lock is updated. Returns a {@link RunSummary} structurally
580
+ * identical to `translate`'s.
538
581
  *
539
582
  * Whole-run failures (unknown format, unreadable/invalid/oversized workbook, corrupt lock) throw a
540
583
  * structured {@link SdkError}. A per-sheet failure (a locale not in config, a broken-round-trip key,
541
584
  * a write failure) is isolated as that locale's `status: "failed"`, not a throw; per-row rejections
542
- * are withheld and reported on the locale, exactly as the provider path treats integrity mismatches.
543
- * `--dry-run` validates and reports without writing any locale or lock file.
585
+ * are withheld and reported on the locale. Dry-run validates and reports without writing any locale or
586
+ * lock file.
544
587
  *
545
588
  * @param input - The validated config, the workbook path, and run options.
546
589
  * @param deps - Optional composition seams (registry, file system) for tests.
@@ -549,6 +592,28 @@ interface ImportWorkbookDeps {
549
592
  */
550
593
  declare function importWorkbook(input: ImportWorkbookInput, deps?: ImportWorkbookDeps): Promise<RunSummary>;
551
594
 
595
+ /**
596
+ * Read-only metadata the CLI `init` scaffold derives its tables from, so the CLI never restates the
597
+ * provider, env-var, model, or format truth owned by core and ai-providers.
598
+ */
599
+ declare const scaffoldingMetadata: {
600
+ /** Provider id -> the environment variable its API key is read from. Owned by ai-providers. */
601
+ readonly providerEnv: {
602
+ readonly anthropic: "ANTHROPIC_API_KEY";
603
+ readonly openai: "OPENAI_API_KEY";
604
+ readonly gemini: "GEMINI_API_KEY";
605
+ readonly deepl: "DEEPL_API_KEY";
606
+ };
607
+ /** LLM provider id -> a cosmetic default scaffold model. Owned by ai-providers. DeepL has none. */
608
+ readonly scaffoldModels: {
609
+ readonly anthropic: "claude-sonnet-4-6";
610
+ readonly openai: "gpt-5.4-mini";
611
+ readonly gemini: "gemini-2.5-flash";
612
+ };
613
+ /** The closed set of source format ids. Owned by core. */
614
+ readonly supportedFormats: readonly ["i18next-json", "vue-i18n-json", "next-intl-json", "ngx-translate-json", "xliff", "yaml", "arb"];
615
+ };
616
+
552
617
  /** A minimal source-change event source. Production wraps chokidar; tests inject a stub. */
553
618
  interface Watcher {
554
619
  /** Register a listener invoked once per coalesced source-change event. */
@@ -605,16 +670,11 @@ interface WatchController {
605
670
  stop(): Promise<void>;
606
671
  }
607
672
  /**
608
- * Start watching the source file and re-run the one-shot translate on each debounced change.
609
- * Watch adds no translation/diff/lock logic: it watches, debounces, serializes, and invokes the
610
- * existing translate() unchanged. The state machine is IDLE <-> RUNNING with a single boolean
611
- * pending-rerun flag (mid-run changes collapse into one immediate follow-up; never two runs at
612
- * once). A missing source at startup is a hard error; a run that fails after start is reported and
613
- * watching continues. Returns a controller whose stop() closes the watcher and awaits the in-flight
614
- * run (the caller wires any signal, e.g. SIGINT, to it).
615
- *
616
- * Only the startup source check throws; every run outcome after start is surfaced through `onRun` as a
617
- * {@link WatchRunResult} (a failed run never rejects the in-flight promise).
673
+ * Start watching the source file and re-run the one-shot {@link translate} on each debounced change.
674
+ * Runs are serialized: changes during a run collapse into a single follow-up, so two runs never
675
+ * overlap. A missing source at startup throws; every run outcome after start is surfaced through
676
+ * `onRun` as a {@link WatchRunResult} and watching continues. Returns a controller whose `stop()`
677
+ * closes the watcher and awaits the in-flight run.
618
678
  *
619
679
  * @param input - The config, optional cwd/debounce, and the `onRun` callback that receives each result.
620
680
  * @param deps - Optional composition seams (watcher, run, registry, provider builder, file system) for tests.
@@ -646,4 +706,4 @@ interface WatchController {
646
706
  */
647
707
  declare function watch(input: WatchInput, deps?: WatchDeps): Promise<WatchController>;
648
708
 
649
- export { type CreateProvider, type CreateWatcher, DEFAULT_WORKBOOK_PATH, type ExportWorkbookDeps, type ExportWorkbookInput, type ExportWorkbookResult, type ImportWorkbookDeps, type ImportWorkbookInput, type LoadConfigOptions, type LocaleNotice, type LocaleSummary, type ProviderConfig, type ProviderId, type RunSummary, type RunTranslate, SdkError, type SdkErrorCode, type SdkFs, type SdkNotice, type SdkNoticeCode, type TranslateDeps, type TranslateInput, type VerbatraConfig, type WatchController, type WatchDeps, type WatchInput, type WatchRunResult, type Watcher, defineConfig, exportWorkbook, importWorkbook, loadConfig, translate, verbatraConfigSchema, watch };
709
+ export { type CheckDeps, type CheckInput, type CheckSummary, type CreateProvider, type CreateWatcher, DEFAULT_WORKBOOK_PATH, type DiffDeps, type DiffInput, type DiffSummary, type ExportWorkbookDeps, type ExportWorkbookInput, type ExportWorkbookResult, type ImportWorkbookDeps, type ImportWorkbookInput, type LoadConfigOptions, type LocaleCheckSummary, type LocaleDiff, type LocaleNotice, type LocaleSummary, type ProviderConfig, type ProviderId, type RunSummary, type RunTranslate, SdkError, type SdkErrorCode, type SdkFs, type SdkNotice, type SdkNoticeCode, type TranslateDeps, type TranslateInput, type VerbatraConfig, type WatchController, type WatchDeps, type WatchInput, type WatchRunResult, type Watcher, check, defineConfig, diff, exportWorkbook, importWorkbook, loadConfig, scaffoldingMetadata, translate, verbatraConfigSchema, watch };