@lingo.dev/cli 1.0.2 → 1.1.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/bin.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/bin.js CHANGED
@@ -1,5 +1,5 @@
1
- import { a as extractCommandInfo, b as ApiClientLive, d as subcommands, f as TelemetryService, g as cancel, i as collectOptions, l as helpConfig, m as trackCommand, n as renderRootHelp, o as unwrapCommand, p as TelemetryServiceLive, s as cliConfig, t as renderCommandHelp, u as run, w as AuthServiceLive, x as AuthContext, y as ConfigServiceLive } from "./renderer-D2iDOMA6.js";
2
- import { n as UpdateService, r as UpdateServiceLive } from "./update-RHUBOb93.js";
1
+ import { a as extractCommandInfo, b as ApiClientLive, d as subcommands, f as TelemetryService, g as cancel, i as collectOptions, l as helpConfig, m as trackCommand, n as renderRootHelp, o as unwrapCommand, p as TelemetryServiceLive, s as cliConfig, t as renderCommandHelp, u as run, w as AuthServiceLive, x as AuthContext, y as ConfigServiceLive } from "./renderer.js";
2
+ import { n as UpdateService, r as UpdateServiceLive } from "./update.js";
3
3
  import { AutoCorrect, CliConfig, ValidationError } from "@effect/cli";
4
4
  import { NodeContext, NodeRuntime } from "@effect/platform-node";
5
5
  import { Cause, Effect, Layer, pipe } from "effect";
@@ -68,7 +68,7 @@ process.on("SIGINT", () => process.exit(0));
68
68
  process.on("SIGTERM", () => process.exit(0));
69
69
  const args = process.argv;
70
70
  const userArgs = args.slice(2).filter((a) => a !== "--");
71
- if (userArgs[0] === "mcp") import("./server-DGIsMSAq.js").then((m) => m.startMcpServer()).catch((e) => {
71
+ if (userArgs[0] === "mcp") import("./server.js").then((m) => m.startMcpServer()).catch((e) => {
72
72
  process.stderr.write(`MCP server error: ${e}\n`);
73
73
  process.exit(1);
74
74
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,356 @@
1
+ import { CliConfig, Command } from "@effect/cli";
2
+ import { Context, Effect, Layer } from "effect";
3
+ import "@supabase/supabase-js";
4
+ import { z } from "zod";
5
+ import * as effect_Cause0 from "effect/Cause";
6
+ import * as effect_Types0 from "effect/Types";
7
+ import * as effect_Option0 from "effect/Option";
8
+ import * as _effect_cli_ValidationError0 from "@effect/cli/ValidationError";
9
+ import * as _effect_cli_CliApp0 from "@effect/cli/CliApp";
10
+ import * as effect_Effect0 from "effect/Effect";
11
+
12
+ //#region src/services/auth.d.ts
13
+ type SessionCredentials = {
14
+ readonly type: "session";
15
+ readonly email: string;
16
+ readonly accessToken: string;
17
+ readonly refreshToken: string;
18
+ readonly expiresAt: number;
19
+ };
20
+ type ApiKeyCredentials = {
21
+ readonly type: "api-key";
22
+ readonly apiKey: string;
23
+ };
24
+ type AuthCredentials = SessionCredentials | ApiKeyCredentials;
25
+ declare const AuthError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
26
+ readonly _tag: "AuthError";
27
+ } & Readonly<A>;
28
+ declare class AuthError extends AuthError_base<{
29
+ readonly message: string;
30
+ }> {}
31
+ declare const AuthContext_base: Context.TagClass<AuthContext, "AuthContext", {
32
+ readonly apiKeyFlag?: string;
33
+ readonly homeDir?: string;
34
+ }>;
35
+ declare class AuthContext extends AuthContext_base {}
36
+ declare const AuthService_base: Context.TagClass<AuthService, "AuthService", {
37
+ readonly resolve: Effect.Effect<AuthCredentials, AuthError>;
38
+ readonly store: (creds: AuthCredentials) => Effect.Effect<void, AuthError>;
39
+ readonly clear: Effect.Effect<void, AuthError>;
40
+ readonly ensureFresh: Effect.Effect<AuthCredentials, AuthError>;
41
+ }>;
42
+ declare class AuthService extends AuthService_base {}
43
+ declare const AuthServiceLive: Layer.Layer<AuthService, never, AuthContext>;
44
+ //#endregion
45
+ //#region src/services/api-client.d.ts
46
+ declare const ApiError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
47
+ readonly _tag: "ApiError";
48
+ } & Readonly<A>;
49
+ declare class ApiError extends ApiError_base<{
50
+ readonly message: string;
51
+ readonly status?: number;
52
+ }> {}
53
+ type Membership = {
54
+ id: string;
55
+ userId: string;
56
+ organizationId: string;
57
+ organizationName: string;
58
+ role: string;
59
+ };
60
+ type Organization = {
61
+ id: string;
62
+ name: string;
63
+ };
64
+ type Engine = {
65
+ id: string;
66
+ ownerOrganizationId: string;
67
+ name: string;
68
+ description: string | null;
69
+ };
70
+ type LocalizeRequest = {
71
+ sourceLocale: string;
72
+ targetLocale: string;
73
+ data: Record<string, string>;
74
+ engineId?: string;
75
+ hints?: Record<string, string[]>;
76
+ triggerType?: "cli" | "ci";
77
+ };
78
+ type LocalizeResponse = {
79
+ sourceLocale: string;
80
+ targetLocale: string;
81
+ data: Record<string, string>;
82
+ };
83
+ declare const ApiClient_base: Context.TagClass<ApiClient, "ApiClient", {
84
+ readonly listMemberships: Effect.Effect<Membership[], ApiError | AuthError>;
85
+ readonly createOrganization: (name: string) => Effect.Effect<Organization, ApiError | AuthError>;
86
+ readonly listEngines: (orgId: string) => Effect.Effect<Engine[], ApiError | AuthError>;
87
+ readonly createEngine: (orgId: string, name: string) => Effect.Effect<Engine, ApiError | AuthError>;
88
+ readonly getMe: Effect.Effect<{
89
+ id: string;
90
+ email: string;
91
+ name: string;
92
+ }, ApiError | AuthError>;
93
+ readonly updateMe: (payload: {
94
+ selfAttribution?: string;
95
+ usecases?: string[];
96
+ }) => Effect.Effect<void, ApiError | AuthError>;
97
+ readonly localize: (params: LocalizeRequest) => Effect.Effect<LocalizeResponse, ApiError | AuthError>;
98
+ }>;
99
+ declare class ApiClient extends ApiClient_base {}
100
+ //#endregion
101
+ //#region src/services/config.d.ts
102
+ type ProjectConfig = {
103
+ readonly orgId: string;
104
+ readonly engineId: string;
105
+ };
106
+ declare const ConfigError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
107
+ readonly _tag: "ConfigError";
108
+ } & Readonly<A>;
109
+ declare class ConfigError extends ConfigError_base<{
110
+ readonly message: string;
111
+ }> {}
112
+ declare const ConfigService_base: Context.TagClass<ConfigService, "ConfigService", {
113
+ readonly resolve: Effect.Effect<ProjectConfig | null>;
114
+ readonly store: (config: ProjectConfig) => Effect.Effect<void, ConfigError>;
115
+ readonly clear: Effect.Effect<void, ConfigError>;
116
+ readonly resolvedPath: Effect.Effect<string | null>;
117
+ }>;
118
+ declare class ConfigService extends ConfigService_base {}
119
+ declare const ConfigServiceLive: Layer.Layer<ConfigService, never, never>;
120
+ //#endregion
121
+ //#region src/services/telemetry.d.ts
122
+ declare const TelemetryService_base: Context.TagClass<TelemetryService, "TelemetryService", {
123
+ readonly capture: (event: string, properties?: Record<string, unknown>) => Effect.Effect<void>;
124
+ readonly identify: (traits?: Record<string, unknown>) => Effect.Effect<void>;
125
+ readonly shutdown: Effect.Effect<void>;
126
+ }>;
127
+ declare class TelemetryService extends TelemetryService_base {}
128
+ //#endregion
129
+ //#region src/services/supabase.d.ts
130
+ declare const SupabaseError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
131
+ readonly _tag: "SupabaseError";
132
+ } & Readonly<A>;
133
+ declare class SupabaseError extends SupabaseError_base<{
134
+ readonly cause: unknown;
135
+ readonly message: string;
136
+ }> {}
137
+ //#endregion
138
+ //#region src/help/renderer.d.ts
139
+ type HelpConfig = {
140
+ name: string;
141
+ version: string;
142
+ };
143
+ declare function renderRootHelp(config: HelpConfig, subcommandDescriptors: readonly any[]): string;
144
+ declare function renderCommandHelp(config: HelpConfig, descriptor: any): string;
145
+ //#endregion
146
+ //#region src/cli.d.ts
147
+ declare const subcommands: readonly [Command.Command<"extract", never, effect_Cause0.UnknownException, {
148
+ readonly check: boolean;
149
+ readonly src: string;
150
+ readonly sourceLocale: string;
151
+ readonly out: string;
152
+ readonly types: string;
153
+ }>, Command.Command<"localize", ApiClient | ConfigService, effect_Cause0.UnknownException, {
154
+ readonly sourceLocale: string;
155
+ readonly targetLocale: string[];
156
+ readonly out: string;
157
+ readonly dryRun: boolean;
158
+ }>, Command.Command<"ship", ApiClient | ConfigService, effect_Cause0.UnknownException, {
159
+ readonly sourceLocale: string;
160
+ readonly targetLocale: string[];
161
+ readonly src: string;
162
+ readonly out: string;
163
+ readonly json: boolean;
164
+ }>, Command.Command<"status", never, effect_Cause0.UnknownException, {
165
+ readonly json: boolean;
166
+ readonly sourceLocale: string;
167
+ readonly out: string;
168
+ }>, Command.Command<"check", never, effect_Cause0.UnknownException, {
169
+ readonly json: boolean;
170
+ readonly src: string;
171
+ readonly sourceLocale: string;
172
+ readonly out: string;
173
+ }>, Command.Command<"guide", never, effect_Cause0.UnknownException, {
174
+ readonly guide: string;
175
+ }>, Command.Command<"install", never, effect_Cause0.UnknownException, {}>, Command.Command<"login", AuthService | TelemetryService, AuthError | effect_Cause0.UnknownException | PromptCancelledError | SupabaseError, {
176
+ readonly email: effect_Option0.Option<string>;
177
+ readonly code: effect_Option0.Option<string>;
178
+ readonly apiKey: effect_Option0.Option<string>;
179
+ }>, Command.Command<"logout", AuthService | TelemetryService, AuthError, {}>, Command.Command<"whoami", AuthService | ApiClient | ConfigService, AuthError, {
180
+ readonly json: boolean;
181
+ }>, Command.Command<"link", AuthService | ApiClient | ConfigService | TelemetryService, AuthError | effect_Cause0.UnknownException | ApiError | ConfigError | PromptCancelledError, {
182
+ readonly org: effect_Option0.Option<string>;
183
+ readonly engine: effect_Option0.Option<string>;
184
+ }>, Command.Command<"unlink", ConfigService | TelemetryService, ConfigError, {}>, Command.Command<"update", TelemetryService | UpdateService, never, {}>, Command.Command<"score", never, never, {
185
+ readonly source: string;
186
+ readonly translation: string;
187
+ readonly reference: effect_Option0.Option<string>;
188
+ readonly sourceLocale: string;
189
+ readonly targetLocale: string;
190
+ readonly scenario: string;
191
+ readonly outputDir: string;
192
+ readonly config: effect_Option0.Option<string>;
193
+ }>];
194
+ declare const command: Command.Command<"lingo", AuthService | ApiClient | ConfigService | TelemetryService | UpdateService, AuthError | effect_Cause0.UnknownException | ApiError | ConfigError | PromptCancelledError | SupabaseError, {
195
+ readonly subcommand: effect_Option0.Option<{
196
+ readonly check: boolean;
197
+ readonly src: string;
198
+ readonly sourceLocale: string;
199
+ readonly out: string;
200
+ readonly types: string;
201
+ } | {
202
+ readonly sourceLocale: string;
203
+ readonly targetLocale: string[];
204
+ readonly out: string;
205
+ readonly dryRun: boolean;
206
+ } | {
207
+ readonly sourceLocale: string;
208
+ readonly targetLocale: string[];
209
+ readonly src: string;
210
+ readonly out: string;
211
+ readonly json: boolean;
212
+ } | {
213
+ readonly json: boolean;
214
+ readonly sourceLocale: string;
215
+ readonly out: string;
216
+ } | {
217
+ readonly json: boolean;
218
+ readonly src: string;
219
+ readonly sourceLocale: string;
220
+ readonly out: string;
221
+ } | {
222
+ readonly guide: string;
223
+ } | {} | {
224
+ readonly email: effect_Option0.Option<string>;
225
+ readonly code: effect_Option0.Option<string>;
226
+ readonly apiKey: effect_Option0.Option<string>;
227
+ } | {} | {
228
+ readonly json: boolean;
229
+ } | {
230
+ readonly org: effect_Option0.Option<string>;
231
+ readonly engine: effect_Option0.Option<string>;
232
+ } | {} | {} | {
233
+ readonly source: string;
234
+ readonly translation: string;
235
+ readonly reference: effect_Option0.Option<string>;
236
+ readonly sourceLocale: string;
237
+ readonly targetLocale: string;
238
+ readonly scenario: string;
239
+ readonly outputDir: string;
240
+ readonly config: effect_Option0.Option<string>;
241
+ }>;
242
+ }>;
243
+ declare const cliConfig: CliConfig.CliConfig;
244
+ declare const helpConfig: HelpConfig;
245
+ declare const run: (args: ReadonlyArray<string>) => effect_Effect0.Effect<void, AuthError | effect_Cause0.UnknownException | ApiError | ConfigError | PromptCancelledError | SupabaseError | _effect_cli_ValidationError0.ValidationError, AuthService | ApiClient | ConfigService | TelemetryService | UpdateService | _effect_cli_CliApp0.CliApp.Environment>;
246
+ //#endregion
247
+ //#region src/help/descriptor.d.ts
248
+ type CommandInfo = {
249
+ name: string;
250
+ description: string;
251
+ };
252
+ type ArgInfo = {
253
+ name: string;
254
+ description: string;
255
+ required: boolean;
256
+ defaultValue?: string;
257
+ };
258
+ type OptionInfo = {
259
+ long: string;
260
+ short?: string;
261
+ type: string;
262
+ description: string;
263
+ required: boolean;
264
+ defaultValue?: string;
265
+ repeatable: boolean;
266
+ isBool: boolean;
267
+ };
268
+ declare function extractCommandInfo(desc: any): CommandInfo;
269
+ declare function collectArgs(node: any): ArgInfo[];
270
+ declare function collectOptions(node: any): OptionInfo[];
271
+ //#endregion
272
+ //#region src/services/prompt.d.ts
273
+ declare const PromptCancelledError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
274
+ readonly _tag: "PromptCancelledError";
275
+ } & Readonly<A>;
276
+ declare class PromptCancelledError extends PromptCancelledError_base<{
277
+ readonly message: string;
278
+ }> {}
279
+ //#endregion
280
+ //#region src/services/update.d.ts
281
+ type InstallMethod = {
282
+ readonly type: "npm-global";
283
+ } | {
284
+ readonly type: "pnpm-global";
285
+ } | {
286
+ readonly type: "bun-global";
287
+ } | {
288
+ readonly type: "yarn-global";
289
+ } | {
290
+ readonly type: "local";
291
+ readonly pm: "npm" | "pnpm" | "bun" | "yarn";
292
+ } | {
293
+ readonly type: "npx";
294
+ } | {
295
+ readonly type: "unknown";
296
+ };
297
+ type UpdateInfo = {
298
+ readonly current: string;
299
+ readonly latest: string;
300
+ readonly isMajor: boolean;
301
+ };
302
+ declare const UpdateError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
303
+ readonly _tag: "UpdateError";
304
+ } & Readonly<A>;
305
+ declare class UpdateError extends UpdateError_base<{
306
+ readonly message: string;
307
+ }> {}
308
+ declare const UpdateService_base: Context.TagClass<UpdateService, "UpdateService", {
309
+ readonly check: Effect.Effect<UpdateInfo | null>;
310
+ readonly execute: (version?: string) => Effect.Effect<void, UpdateError>;
311
+ readonly installMethod: InstallMethod;
312
+ }>;
313
+ declare class UpdateService extends UpdateService_base {}
314
+ declare const UpdateServiceLive: Layer.Layer<UpdateService, never, never>;
315
+ //#endregion
316
+ //#region src/version.d.ts
317
+ declare const VERSION: string;
318
+ //#endregion
319
+ //#region ../scorer/dist/index.d.ts
320
+ //#region src/types.d.ts
321
+ type ScorerInput = {
322
+ readonly sourceLocale: string;
323
+ readonly targetLocale: string;
324
+ readonly sourceContent: string;
325
+ readonly targetContent: string;
326
+ readonly referenceContent: string | undefined;
327
+ };
328
+ type ScorerMeta = {
329
+ readonly name: string;
330
+ readonly description: string;
331
+ readonly version: string;
332
+ };
333
+ type LingoScorer<S extends z.ZodType = z.ZodType> = {
334
+ readonly meta: ScorerMeta;
335
+ readonly schema: S;
336
+ readonly prompt: (input: ScorerInput) => string;
337
+ readonly postprocess: (input: ScorerInput, output: z.infer<S>) => number;
338
+ };
339
+ type AnyLingoScorer = LingoScorer<any>;
340
+ //#endregion
341
+ //#region src/define-scorer.d.ts
342
+ //#endregion
343
+ //#region src/services/lingo-config.d.ts
344
+ type ScoringModel = {
345
+ readonly provider: string;
346
+ readonly model: string;
347
+ };
348
+ type LingoConfig = {
349
+ readonly scoring: {
350
+ readonly plugins: readonly AnyLingoScorer[];
351
+ readonly models: readonly ScoringModel[];
352
+ };
353
+ };
354
+ declare function defineConfig(config: LingoConfig): LingoConfig;
355
+ //#endregion
356
+ export { type ApiKeyCredentials, type ArgInfo, AuthContext, type AuthCredentials, AuthError, AuthService, AuthServiceLive, type CommandInfo, ConfigError, ConfigService, ConfigServiceLive, type HelpConfig, type InstallMethod, type LingoConfig, type OptionInfo, type ProjectConfig, PromptCancelledError, type ScoringModel, type SessionCredentials, UpdateError, type UpdateInfo, UpdateService, UpdateServiceLive, VERSION, cliConfig, collectArgs, collectOptions, command, defineConfig, extractCommandInfo, helpConfig, renderCommandHelp, renderRootHelp, run, subcommands };
package/dist/index.js CHANGED
@@ -1,3 +1,8 @@
1
- import { C as AuthService, S as AuthError, _ as ConfigError, a as extractCommandInfo, c as command, d as subcommands, h as PromptCancelledError, i as collectOptions, l as helpConfig, n as renderRootHelp, r as collectArgs, s as cliConfig, t as renderCommandHelp, u as run, v as ConfigService, w as AuthServiceLive, x as AuthContext, y as ConfigServiceLive } from "./renderer-D2iDOMA6.js";
2
- import { n as UpdateService, r as UpdateServiceLive, s as VERSION, t as UpdateError } from "./update-RHUBOb93.js";
3
- export { AuthContext, AuthError, AuthService, AuthServiceLive, ConfigError, ConfigService, ConfigServiceLive, PromptCancelledError, UpdateError, UpdateService, UpdateServiceLive, VERSION, cliConfig, collectArgs, collectOptions, command, extractCommandInfo, helpConfig, renderCommandHelp, renderRootHelp, run, subcommands };
1
+ import { C as AuthService, S as AuthError, _ as ConfigError, a as extractCommandInfo, c as command, d as subcommands, h as PromptCancelledError, i as collectOptions, l as helpConfig, n as renderRootHelp, r as collectArgs, s as cliConfig, t as renderCommandHelp, u as run, v as ConfigService, w as AuthServiceLive, x as AuthContext, y as ConfigServiceLive } from "./renderer.js";
2
+ import { n as UpdateService, r as UpdateServiceLive, s as VERSION, t as UpdateError } from "./update.js";
3
+ //#region src/services/lingo-config.ts
4
+ function defineConfig(config) {
5
+ return config;
6
+ }
7
+ //#endregion
8
+ export { AuthContext, AuthError, AuthService, AuthServiceLive, ConfigError, ConfigService, ConfigServiceLive, PromptCancelledError, UpdateError, UpdateService, UpdateServiceLive, VERSION, cliConfig, collectArgs, collectOptions, command, defineConfig, extractCommandInfo, helpConfig, renderCommandHelp, renderRootHelp, run, subcommands };
@@ -1,4 +1,4 @@
1
- import { C as discoverLocales, S as writeLocaleFile, T as tryReadFile, _ as runExtractionPipeline, b as mergeEntries, c as formatCheckResult, d as computeSourceStatus, f as formatStatusTable, g as generateTypes, h as toApiPayload, l as runChecks, m as planLocalization, n as UpdateService, p as applyTranslations, s as VERSION, t as UpdateError, u as computeLocaleStatus, v as toLocaleEntries, w as findSourceFiles, x as readLocaleFile, y as getActiveEntries } from "./update-RHUBOb93.js";
1
+ import { C as discoverLocales, S as writeLocaleFile, T as tryReadFile, _ as runExtractionPipeline, b as mergeEntries, c as formatCheckResult, d as computeSourceStatus, f as formatStatusTable, g as generateTypes, h as toApiPayload, l as runChecks, m as planLocalization, n as UpdateService, p as applyTranslations, s as VERSION, t as UpdateError, u as computeLocaleStatus, v as toLocaleEntries, w as findSourceFiles, x as readLocaleFile, y as getActiveEntries } from "./update.js";
2
2
  import { Args, CliConfig, Command, Options } from "@effect/cli";
3
3
  import { Console, Context, Data, Effect, Layer, Option, pipe } from "effect";
4
4
  import pc from "picocolors";
@@ -14,6 +14,8 @@ import { spawn } from "node:child_process";
14
14
  import * as ci from "ci-info";
15
15
  import * as crypto from "node:crypto";
16
16
  import { fileURLToPath } from "node:url";
17
+ import { Output, generateText } from "ai";
18
+ import { createOpenRouter } from "@openrouter/ai-sdk-provider";
17
19
  //#region src/services/cli-options.ts
18
20
  /**
19
21
  * Shared CLI option definitions used across extract, localize, status, and ship commands.
@@ -1293,6 +1295,145 @@ const updateCommand = Command.make("update", {}, () => Effect.gen(function* () {
1293
1295
  });
1294
1296
  })).pipe(Command.withDescription("Update the CLI to the latest version"));
1295
1297
  //#endregion
1298
+ //#region src/services/lingo-config-loader.ts
1299
+ const CONFIG_FILENAME = "lingo.config.ts";
1300
+ function findConfigFile(startDir) {
1301
+ let dir = path.resolve(startDir);
1302
+ const root = path.parse(dir).root;
1303
+ while (true) {
1304
+ const candidate = path.join(dir, CONFIG_FILENAME);
1305
+ if (fs.existsSync(candidate)) return candidate;
1306
+ const parent = path.dirname(dir);
1307
+ if (parent === dir || dir === root) return null;
1308
+ dir = parent;
1309
+ }
1310
+ }
1311
+ function validate(value) {
1312
+ if (!value || typeof value !== "object") throw new Error(`Invalid lingo config: expected an object, got ${typeof value}`);
1313
+ const config = value;
1314
+ if (!config.scoring || typeof config.scoring !== "object") throw new Error("Invalid lingo config: missing 'scoring' section");
1315
+ const scoring = config.scoring;
1316
+ if (!Array.isArray(scoring.plugins)) throw new Error("Invalid lingo config: 'scoring.plugins' must be an array");
1317
+ for (const [i, plugin] of scoring.plugins.entries()) {
1318
+ if (!plugin || typeof plugin !== "object") throw new Error(`Invalid lingo config: scoring.plugins[${i}] is not an object`);
1319
+ const p = plugin;
1320
+ if (!p.meta || typeof p.meta !== "object") throw new Error(`Invalid lingo config: scoring.plugins[${i}] is missing 'meta'`);
1321
+ if (typeof p.prompt !== "function") throw new Error(`Invalid lingo config: scoring.plugins[${i}] is missing 'prompt' function`);
1322
+ if (typeof p.postprocess !== "function") throw new Error(`Invalid lingo config: scoring.plugins[${i}] is missing 'postprocess' function`);
1323
+ if (!p.schema) throw new Error(`Invalid lingo config: scoring.plugins[${i}] is missing 'schema'`);
1324
+ }
1325
+ if (!Array.isArray(scoring.models)) throw new Error("Invalid lingo config: 'scoring.models' must be an array");
1326
+ for (const [i, model] of scoring.models.entries()) {
1327
+ if (!model || typeof model !== "object") throw new Error(`Invalid lingo config: scoring.models[${i}] is not an object`);
1328
+ const m = model;
1329
+ if (typeof m.provider !== "string" || !m.provider) throw new Error(`Invalid lingo config: scoring.models[${i}] is missing 'provider'`);
1330
+ if (typeof m.model !== "string" || !m.model) throw new Error(`Invalid lingo config: scoring.models[${i}] is missing 'model'`);
1331
+ }
1332
+ return value;
1333
+ }
1334
+ async function loadLingoConfig(configPath) {
1335
+ const resolved = configPath ? path.resolve(configPath) : findConfigFile(process.cwd());
1336
+ if (!resolved) throw new Error(`Could not find ${CONFIG_FILENAME}. Create one or pass --config.`);
1337
+ if (!fs.existsSync(resolved)) throw new Error(`Config file not found: ${resolved}`);
1338
+ const { createJiti } = await import("jiti");
1339
+ const raw = await createJiti(resolved, { interopDefault: true }).import(resolved);
1340
+ return validate(raw.default ?? raw);
1341
+ }
1342
+ //#endregion
1343
+ //#region src/services/scorer-runner.ts
1344
+ async function runScorer(scorer, input, model) {
1345
+ const apiKey = process.env.OPENROUTER_API_KEY;
1346
+ if (!apiKey) throw new Error("OPENROUTER_API_KEY environment variable is required for scoring.");
1347
+ const openrouter = createOpenRouter({ apiKey });
1348
+ const slug = `${model.provider}/${model.model}`;
1349
+ const systemPrompt = scorer.prompt(input);
1350
+ const result = await generateText({
1351
+ model: openrouter(slug),
1352
+ output: Output.object({ schema: scorer.schema }),
1353
+ system: systemPrompt,
1354
+ prompt: "Evaluate the translation."
1355
+ });
1356
+ const score = scorer.postprocess(input, result.output);
1357
+ return {
1358
+ scorer: scorer.meta.name,
1359
+ providerId: model.provider,
1360
+ modelId: model.model,
1361
+ score,
1362
+ details: result.output
1363
+ };
1364
+ }
1365
+ //#endregion
1366
+ //#region src/commands/score.ts
1367
+ const source = Options.text("source").pipe(Options.withDescription("Path to source markdown file"));
1368
+ const translation = Options.text("translation").pipe(Options.withDescription("Path to translated markdown file"));
1369
+ const reference = pipe(Options.text("reference"), Options.withDescription("Path to human reference markdown file"), Options.optional);
1370
+ const sourceLocale = Options.text("source-locale").pipe(Options.withDescription("Source locale code (e.g. en)"));
1371
+ const targetLocale = Options.text("target-locale").pipe(Options.withDescription("Target locale code (e.g. de)"));
1372
+ const scenario = Options.text("scenario").pipe(Options.withDescription("Scenario name, used as output filename (e.g. mistral-raw-en-de)"));
1373
+ const outputDir = Options.text("output-dir").pipe(Options.withDescription("Directory for results JSON"), Options.withDefault("./scenarios"));
1374
+ const configPath = pipe(Options.text("config"), Options.withDescription("Path to lingo.config.ts"), Options.optional);
1375
+ const scoreCommand = Command.make("score", {
1376
+ source,
1377
+ translation,
1378
+ reference,
1379
+ sourceLocale,
1380
+ targetLocale,
1381
+ scenario,
1382
+ outputDir,
1383
+ config: configPath
1384
+ }, (opts) => Effect.gen(function* () {
1385
+ const configFlag = Option.getOrUndefined(opts.config);
1386
+ const { plugins, models } = (yield* Effect.promise(() => loadLingoConfig(configFlag))).scoring;
1387
+ yield* Console.log(`Scoring with ${plugins.length} plugin(s) x ${models.length} model(s)`);
1388
+ const sourceContent = yield* Effect.promise(() => fs$1.readFile(path.resolve(opts.source), "utf-8"));
1389
+ const translationContent = yield* Effect.promise(() => fs$1.readFile(path.resolve(opts.translation), "utf-8"));
1390
+ const referenceFlag = Option.getOrUndefined(opts.reference);
1391
+ const referenceContent = referenceFlag ? yield* Effect.promise(() => fs$1.readFile(path.resolve(referenceFlag), "utf-8")) : void 0;
1392
+ const input = {
1393
+ sourceLocale: opts.sourceLocale,
1394
+ targetLocale: opts.targetLocale,
1395
+ sourceContent,
1396
+ targetContent: translationContent,
1397
+ referenceContent
1398
+ };
1399
+ const dir = path.resolve(opts.outputDir);
1400
+ const outPath = path.join(dir, `${opts.scenario}.json`);
1401
+ let existingResults = [];
1402
+ const existingRaw = yield* Effect.promise(() => fs$1.readFile(outPath, "utf-8").catch(() => null));
1403
+ if (existingRaw) try {
1404
+ existingResults = JSON.parse(existingRaw).results ?? [];
1405
+ } catch {}
1406
+ const results = [...existingResults];
1407
+ let skipped = 0;
1408
+ for (const plugin of plugins) for (const model of models) {
1409
+ const modelId = `${model.provider}/${model.model}`;
1410
+ if (existingResults.some((r) => r.scorer === plugin.meta.name && r.providerId === model.provider && r.modelId === model.model)) {
1411
+ skipped++;
1412
+ continue;
1413
+ }
1414
+ yield* Console.log(` ${plugin.meta.name} x ${modelId}...`);
1415
+ const result = yield* Effect.promise(() => runScorer(plugin, input, model).catch((e) => {
1416
+ process.stderr.write(` ERROR: ${e.message}\n`);
1417
+ throw e;
1418
+ }));
1419
+ yield* Console.log(` score: ${result.score.toFixed(4)}`);
1420
+ results.push(result);
1421
+ }
1422
+ if (skipped > 0) yield* Console.log(` (skipped ${skipped} already-scored combo${skipped > 1 ? "s" : ""})`);
1423
+ const payload = {
1424
+ scenario: opts.scenario,
1425
+ sourceLocale: opts.sourceLocale,
1426
+ targetLocale: opts.targetLocale,
1427
+ source: opts.source,
1428
+ translation: opts.translation,
1429
+ reference: referenceFlag ?? null,
1430
+ results
1431
+ };
1432
+ yield* Effect.promise(() => fs$1.mkdir(dir, { recursive: true }));
1433
+ yield* Effect.promise(() => fs$1.writeFile(outPath, JSON.stringify(payload, null, 2) + "\n"));
1434
+ yield* Console.log(`\nResults written to ${outPath}`);
1435
+ })).pipe(Command.withDescription("Score a translation using configured scorer plugins"));
1436
+ //#endregion
1296
1437
  //#region src/cli.ts
1297
1438
  const subcommands = [
1298
1439
  extractCommand,
@@ -1307,7 +1448,8 @@ const subcommands = [
1307
1448
  whoamiCommand,
1308
1449
  linkCommand,
1309
1450
  unlinkCommand,
1310
- updateCommand
1451
+ updateCommand,
1452
+ scoreCommand
1311
1453
  ];
1312
1454
  const command = Command.make("lingo").pipe(Command.withDescription(TAGLINE), Command.withSubcommands([
1313
1455
  extractCommand,
@@ -1322,7 +1464,8 @@ const command = Command.make("lingo").pipe(Command.withDescription(TAGLINE), Com
1322
1464
  whoamiCommand,
1323
1465
  linkCommand,
1324
1466
  unlinkCommand,
1325
- updateCommand
1467
+ updateCommand,
1468
+ scoreCommand
1326
1469
  ]));
1327
1470
  const cliConfig = CliConfig.make({
1328
1471
  showBuiltIns: false,
@@ -1515,9 +1658,10 @@ function renderCommandHelp(config, descriptor) {
1515
1658
  for (let i = 0; i < opts.length; i++) {
1516
1659
  const sig = signatures[i];
1517
1660
  const pad = " ".repeat(maxLen - stripAnsi(sig).length);
1518
- const parts = [opts[i].description];
1519
- if (opts[i].defaultValue !== void 0) parts.push(pc.dim(`[default: ${opts[i].defaultValue}]`));
1520
- if (opts[i].repeatable) parts.push(pc.dim("(repeatable)"));
1661
+ const opt = opts[i];
1662
+ const parts = [opt.description];
1663
+ if (opt.defaultValue !== void 0) parts.push(pc.dim(`[default: ${opt.defaultValue}]`));
1664
+ if (opt.repeatable) parts.push(pc.dim("(repeatable)"));
1521
1665
  lines.push(` ${sig}${pad} ${parts.filter(Boolean).join(" ")}`);
1522
1666
  }
1523
1667
  }
@@ -1,4 +1,4 @@
1
- import { C as discoverLocales, S as writeLocaleFile, T as tryReadFile, _ as runExtractionPipeline, a as detectInstallMethod, b as mergeEntries, d as computeSourceStatus, g as generateTypes, h as toApiPayload, i as compareVersions, l as runChecks, m as planLocalization, o as generateUpdateCommand, p as applyTranslations, s as VERSION, u as computeLocaleStatus, v as toLocaleEntries, w as findSourceFiles } from "./update-RHUBOb93.js";
1
+ import { C as discoverLocales, S as writeLocaleFile, T as tryReadFile, _ as runExtractionPipeline, a as detectInstallMethod, b as mergeEntries, d as computeSourceStatus, g as generateTypes, h as toApiPayload, i as compareVersions, l as runChecks, m as planLocalization, o as generateUpdateCommand, p as applyTranslations, s as VERSION, u as computeLocaleStatus, v as toLocaleEntries, w as findSourceFiles } from "./update.js";
2
2
  import * as fs from "node:fs/promises";
3
3
  import * as path from "node:path";
4
4
  import { computeKey, getActiveEntries, readLocaleFile } from "@lingo.dev/spec";
package/guides/api.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # API Reference – @lingo.dev/react
2
2
 
3
3
  ## Hooks
4
+
4
5
  - `useLingo()` → returns Lingo with translation + formatting helpers.
5
6
 
6
7
  ## Translation helpers (context required)
8
+
7
9
  ```tsx
8
10
  l.text(source, { context, values? })
9
11
  l.rich(source, { context, tags?, values? })
@@ -12,43 +14,52 @@ l.select(value, { ...forms, other }, { context })
12
14
  ```
13
15
 
14
16
  Examples:
17
+
15
18
  ```tsx
16
- l.text("Save", { context: "Form submit button" })
17
- l.text("Hello, {name}!", { context: "Dashboard welcome", values: { name } })
18
- l.rich("Read <link>docs</link>", { context: "Footer link", tags: { link: c => <a href="/docs">{c}</a> } })
19
- l.plural(items, { one: "# item", other: "# items" }, { context: "Cart count" })
20
- l.select(role, { admin: "Admin", user: "User", other: "Guest" }, { context: "Nav label" })
19
+ l.text("Save", { context: "Form submit button" });
20
+ l.text("Hello, {name}!", { context: "Dashboard welcome", values: { name } });
21
+ l.rich("Read <link>docs</link>", { context: "Footer link", tags: { link: (c) => <a href="/docs">{c}</a> } });
22
+ l.plural(items, { one: "# item", other: "# items" }, { context: "Cart count" });
23
+ l.select(role, { admin: "Admin", user: "User", other: "Guest" }, { context: "Nav label" });
21
24
  ```
22
25
 
23
26
  Context rules:
24
- 1) Always required on text/rich/plural/select.
25
- 2) Describes where/how the string is used ("Checkout CTA", "Toast error").
26
- 3) Same source + different context = different translation key.
27
+
28
+ 1. Always required on text/rich/plural/select.
29
+ 2. Describes where/how the string is used ("Checkout CTA", "Toast error").
30
+ 3. Same source + different context = different translation key.
27
31
 
28
32
  ## Formatting helpers (no context needed)
33
+
29
34
  ```tsx
30
- l.num(1234567) // "1,234,567"
31
- l.currency(29.99, "USD") // "$29.99"
32
- l.percent(0.156) // "16%"
33
- l.date(new Date()) // locale date
34
- l.time(new Date()) // locale time
35
- l.relative(-1, "day") // "yesterday"
36
- l.list(["A","B","C"]) // "A, B, and C"
35
+ l.num(1234567); // "1,234,567"
36
+ l.currency(29.99, "USD"); // "$29.99"
37
+ l.percent(0.156); // "16%"
38
+ l.date(new Date()); // locale date
39
+ l.time(new Date()); // locale time
40
+ l.relative(-1, "day"); // "yesterday"
41
+ l.list(["A", "B", "C"]); // "A, B, and C"
37
42
  ```
38
43
 
39
44
  ## Components / HOCs (Next.js Pages Router)
40
- - `withLingoApp(App)` → wraps _app.tsx; sets dir for RTL; warns if page missing withLingoProps.
45
+
46
+ - `withLingoApp(App)` → wraps \_app.tsx; sets dir for RTL; warns if page missing withLingoProps.
41
47
  - `withLingoProps({ route, handler? })` → load route-specific messages in getStaticProps/SSR.
42
48
  - `withLingoPaths(handler)` → expands getStaticPaths across locales (fallback: "blocking").
43
49
  - `useLocaleSwitch()` → sets NEXT_LOCALE cookie + router.push with locale.
44
50
  - `LingoHead` → hreflang SEO tags.
45
51
 
46
52
  ## Files generated by CLI
53
+
47
54
  - `locales/<locale>.jsonc` – source/translated messages with @context + @src metadata.
48
55
  - `lingo.d.ts` – TypeScript augmentation with exact context unions and required values.
49
56
 
50
57
  ## Required patterns
58
+
51
59
  - Every page using translations must export withLingoProps({ route }).
52
60
  - Every translation call must include context.
53
61
  - Always run `npx @lingo.dev/cli check` before finishing.
62
+
63
+ ```
64
+
54
65
  ```
package/guides/index.md CHANGED
@@ -3,6 +3,7 @@
3
3
  Use `npx @lingo.dev/cli guide <name>` to view a guide.
4
4
 
5
5
  Available guides:
6
+
6
7
  - `setup` – first-time setup for @lingo.dev/react (Next.js Pages Router focus, works for React too)
7
8
  - `api` – API reference for `l.text`, `l.rich`, `l.plural`, `l.select`, formatting helpers
8
9
  - `migrate` – migrate from next-intl, react-intl, i18next, lingui to @lingo.dev/react
package/guides/migrate.md CHANGED
@@ -1,34 +1,41 @@
1
1
  # Migration Guide – to @lingo.dev/react
2
2
 
3
3
  ## Key differences
4
+
4
5
  - No manual keys. Use source text + required context: `l.text("Save", { context: "Form button" })`.
5
6
  - Automatic hash keys; per-route splitting via withLingoProps({ route }).
6
7
  - TypeScript enforces context and required values after `lingo extract`.
7
8
 
8
9
  ## API mapping
9
- | Pattern | next-intl | react-intl | i18next | @lingo.dev/react |
10
- |------------------|----------------------------|---------------------------------|---------------------------|------------------------------------------------------|
11
- | Plain text | `t("key")` | `formatMessage({ id })` | `t("key")` | `l.text("source", { context: "..." })` |
12
- | With values | `t("key", { name })` | `formatMessage({ id }, { name })`| `t("key", { name })` | `l.text("Hello, {name}", { context: "...", values: { name } })` |
13
- | Rich text | `t.rich("key", { link })`| `<FormattedMessage components>` | `<Trans components>` | `l.rich("Click <link>here</link>", { context: "...", tags: { link } })` |
14
- | Plurals | ICU in JSON | `<FormattedPlural>` | ICU in JSON | `l.plural(count, { one: "# item", other: "# items" }, { context: "..." })` |
15
- | Provider | `NextIntlProvider` | `IntlProvider` | `I18nextProvider` | `withLingoApp` (wraps LingoProvider) |
16
- | Hook | `useTranslations()` | `useIntl()` | `useTranslation()` | `useLingo()` |
10
+
11
+ | Pattern | next-intl | react-intl | i18next | @lingo.dev/react |
12
+ | ----------- | ------------------------- | --------------------------------- | -------------------- | -------------------------------------------------------------------------- |
13
+ | Plain text | `t("key")` | `formatMessage({ id })` | `t("key")` | `l.text("source", { context: "..." })` |
14
+ | With values | `t("key", { name })` | `formatMessage({ id }, { name })` | `t("key", { name })` | `l.text("Hello, {name}", { context: "...", values: { name } })` |
15
+ | Rich text | `t.rich("key", { link })` | `<FormattedMessage components>` | `<Trans components>` | `l.rich("Click <link>here</link>", { context: "...", tags: { link } })` |
16
+ | Plurals | ICU in JSON | `<FormattedPlural>` | ICU in JSON | `l.plural(count, { one: "# item", other: "# items" }, { context: "..." })` |
17
+ | Provider | `NextIntlProvider` | `IntlProvider` | `I18nextProvider` | `withLingoApp` (wraps LingoProvider) |
18
+ | Hook | `useTranslations()` | `useIntl()` | `useTranslation()` | `useLingo()` |
17
19
 
18
20
  ## Steps
19
- 1) Install @lingo.dev/react (and @lingo.dev/react-next for Next.js). Keep old lib temporarily.
20
- 2) Wrap root with withLingoApp (and keep old provider during migration if needed).
21
- 3) Per file:
21
+
22
+ 1. Install @lingo.dev/react (and @lingo.dev/react-next for Next.js). Keep old lib temporarily.
23
+ 2. Wrap root with withLingoApp (and keep old provider during migration if needed).
24
+ 3. Per file:
22
25
  - Replace old hook with `useLingo()`.
23
26
  - Replace calls using mapping above.
24
27
  - Add `withLingoProps({ route })` to every page using translations.
25
- 4) Run `npx @lingo.dev/cli extract`.
26
- 5) Run `npx @lingo.dev/cli localize --target-locale <locale>`.
27
- 6) Run `npx @lingo.dev/cli check` (expect 0 issues).
28
- 7) Remove old provider, message files, and dependencies once all pages migrated.
28
+ 4. Run `npx @lingo.dev/cli extract`.
29
+ 5. Run `npx @lingo.dev/cli localize --target-locale <locale>`.
30
+ 6. Run `npx @lingo.dev/cli check` (expect 0 issues).
31
+ 7. Remove old provider, message files, and dependencies once all pages migrated.
29
32
 
30
33
  ## Gotchas
34
+
31
35
  - Old JSON translation files can’t be reused directly (keys differ). Re-translate via `localize`.
32
36
  - Keep contexts specific (e.g., "Toolbar Save", "Form Save").
33
37
  - Dynamic routes still need withLingoPaths for locale expansion.
38
+
39
+ ```
40
+
34
41
  ```
package/guides/setup.md CHANGED
@@ -3,14 +3,18 @@
3
3
  Philosophy: the agent writes i18n code; context is required on every call; route is required on every page loader. Finish with `npx @lingo.dev/cli check`.
4
4
 
5
5
  ## 1) Install packages
6
+
6
7
  Detect package manager from lockfile. Example (pnpm):
8
+
7
9
  ```bash
8
10
  pnpm add @lingo.dev/react @lingo.dev/react-next
9
11
  pnpm add -D @lingo.dev/cli
10
12
  ```
11
13
 
12
14
  ## 2) Configure Next.js i18n
15
+
13
16
  `next.config.ts`
17
+
14
18
  ```ts
15
19
  import type { NextConfig } from "next";
16
20
 
@@ -24,7 +28,8 @@ const nextConfig: NextConfig = {
24
28
  export default nextConfig;
25
29
  ```
26
30
 
27
- ## 3) Wrap _app.tsx
31
+ ## 3) Wrap \_app.tsx
32
+
28
33
  ```tsx
29
34
  // pages/_app.tsx
30
35
  import type { AppProps } from "next/app";
@@ -39,24 +44,30 @@ export default withLingoApp(function App({ Component, pageProps }: AppProps) {
39
44
  );
40
45
  });
41
46
  ```
47
+
42
48
  - withLingoApp wires LingoProvider, sets dir for RTL, warns if a page lacks withLingoProps.
43
49
  - LingoHead adds hreflang tags.
44
50
 
45
51
  ## 4) Add withLingoProps to every page
52
+
46
53
  Route = file path without extension (relative to repo root).
54
+
47
55
  ```tsx
48
56
  import { withLingoProps } from "@lingo.dev/react-next/pages/server";
49
57
  export const getStaticProps = withLingoProps({ route: "src/pages/index" });
50
58
  ```
59
+
51
60
  Dynamic routes also add withLingoPaths:
61
+
52
62
  ```tsx
53
63
  import { withLingoPaths } from "@lingo.dev/react-next/pages/server";
54
64
  export const getStaticPaths = withLingoPaths(async () =>
55
- getPosts().then((posts) => posts.map((p) => ({ params: { slug: p.slug } })))
65
+ getPosts().then((posts) => posts.map((p) => ({ params: { slug: p.slug } }))),
56
66
  );
57
67
  ```
58
68
 
59
69
  ## 5) Wrap strings with useLingo
70
+
60
71
  ```tsx
61
72
  import { useLingo } from "@lingo.dev/react";
62
73
 
@@ -65,9 +76,11 @@ const l = useLingo();
65
76
  <p>{l.text("The best tool for teams.", { context: "Hero subheading" })}</p>
66
77
  <button>{l.text("Get started", { context: "Hero CTA" })}</button>
67
78
  ```
79
+
68
80
  Context is REQUIRED on l.text/l.rich/l.plural/l.select.
69
81
 
70
82
  ### API cheatsheet
83
+
71
84
  - `l.text(source, { context, values? })`
72
85
  - `l.rich(source, { context, tags?, values? })`
73
86
  - `l.plural(count, forms, { context })`
@@ -75,6 +88,7 @@ Context is REQUIRED on l.text/l.rich/l.plural/l.select.
75
88
  - Formatting: `l.num`, `l.currency`, `l.percent`, `l.date`, `l.time`, `l.relative`, `l.list`
76
89
 
77
90
  ## 6) Locale switcher
91
+
78
92
  ```tsx
79
93
  import { useLocaleSwitch } from "@lingo.dev/react-next/pages/client";
80
94
  import { useRouter } from "next/router";
@@ -82,30 +96,42 @@ import { useRouter } from "next/router";
82
96
  const switchLocale = useLocaleSwitch();
83
97
  const { locale, locales } = useRouter();
84
98
  <select value={locale} onChange={(e) => switchLocale(e.target.value)}>
85
- {locales?.map((loc) => <option key={loc}>{loc}</option>)}
86
- </select>
99
+ {locales?.map((loc) => (
100
+ <option key={loc}>{loc}</option>
101
+ ))}
102
+ </select>;
87
103
  ```
88
104
 
89
105
  ## 7) Extract
106
+
90
107
  ```bash
91
108
  npx @lingo.dev/cli extract # or pnpm lingo extract
92
109
  ```
110
+
93
111
  Generates `locales/en.jsonc` + `lingo.d.ts`. After this, TS enforces context + values.
94
112
 
95
113
  ## 8) Translate
114
+
96
115
  ```bash
97
116
  npx @lingo.dev/cli localize --target-locale es
98
117
  ```
118
+
99
119
  Writes `locales/es.jsonc`.
100
120
 
101
121
  ## 9) Verify
122
+
102
123
  ```bash
103
124
  npx @lingo.dev/cli check
104
125
  ```
126
+
105
127
  0 issues = done.
106
128
 
107
129
  ## Rules to remember
130
+
108
131
  - Context required on every translation call.
109
132
  - Route required on every withLingoProps.
110
133
  - Always finish with `npx @lingo.dev/cli check`.
134
+
135
+ ```
136
+
111
137
  ```
package/package.json CHANGED
@@ -1,19 +1,26 @@
1
1
  {
2
2
  "name": "@lingo.dev/cli",
3
- "version": "1.0.2",
4
- "type": "module",
3
+ "version": "1.1.0",
5
4
  "bin": {
6
5
  "lingo": "./dist/bin.js"
7
6
  },
8
- "main": "./dist/index.js",
9
- "exports": {
10
- ".": "./dist/index.js"
11
- },
12
7
  "files": [
13
8
  "dist",
14
9
  "skills",
15
10
  "guides"
16
11
  ],
12
+ "type": "module",
13
+ "main": "./dist/index.js",
14
+ "exports": {
15
+ ".": {
16
+ "typescript": {
17
+ "types": "./src/index.ts",
18
+ "default": "./src/index.ts"
19
+ },
20
+ "import": "./dist/index.js",
21
+ "default": "./dist/index.js"
22
+ }
23
+ },
17
24
  "dependencies": {
18
25
  "@anthropic-ai/claude-agent-sdk": "^0.2.76",
19
26
  "@babel/parser": "^7.29.0",
@@ -23,23 +30,28 @@
23
30
  "@effect/platform": "^0.94.1",
24
31
  "@effect/platform-node": "^0.104.0",
25
32
  "@modelcontextprotocol/sdk": "^1.12.1",
33
+ "@openrouter/ai-sdk-provider": "^2.5.0",
26
34
  "@supabase/supabase-js": "^2.99.1",
35
+ "ai": "^6.0.152",
27
36
  "cfonts": "^3.3.1",
28
37
  "ci-info": "^4.4.0",
29
38
  "effect": "^3.19.0",
39
+ "jiti": "^2.6.1",
30
40
  "jsonc-parser": "^3.3.1",
31
41
  "log-update": "^7.2.0",
32
42
  "picocolors": "^1.1.1",
33
43
  "posthog-node": "^5.28.2",
34
44
  "semver": "^7.7.4",
35
45
  "zod": "^4.0.0",
36
- "@lingo.dev/spec": "1.0.2"
46
+ "@lingo.dev/scorer": "0.0.1",
47
+ "@lingo.dev/spec": "1.1.0"
37
48
  },
38
49
  "devDependencies": {
39
50
  "@babel/types": "^7.29.0",
40
51
  "@types/babel__traverse": "^7.28.0",
41
52
  "@types/node": "^25.5.0",
42
53
  "@types/semver": "^7.7.1",
54
+ "@typescript/native-preview": "7.0.0-dev.20260330.1",
43
55
  "tsdown": "^0.12.5",
44
56
  "tsx": "^4.19.0",
45
57
  "typescript": "^5.8.2",
@@ -47,8 +59,8 @@
47
59
  },
48
60
  "scripts": {
49
61
  "build": "tsdown",
50
- "typecheck": "tsc --noEmit",
62
+ "cli": "node --import tsx/esm src/bin.ts",
51
63
  "test": "vitest run",
52
- "cli": "node --import tsx/esm src/bin.ts"
64
+ "typecheck": "tsgo"
53
65
  }
54
66
  }
@@ -8,4 +8,4 @@ user-invocable: true
8
8
  argument-hint: "[add <locale>, migrate from <library>, or blank]"
9
9
  ---
10
10
 
11
- Run `npx @lingo.dev/cli guide` to read the guides (setup, api, migrate).
11
+ Run `npx @lingo.dev/cli guide` to read the guides (setup, api, migrate).
File without changes