@tolkbot/cli 0.1.0 → 0.2.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.
Files changed (2) hide show
  1. package/dist/index.js +823 -758
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5787,7 +5787,7 @@ var require_out4 = __commonJS({
5787
5787
  import { Command as Command6 } from "commander";
5788
5788
 
5789
5789
  // package.json
5790
- var version = "0.1.0";
5790
+ var version = "0.2.0";
5791
5791
 
5792
5792
  // src/commands/auth/index.ts
5793
5793
  import { Command } from "commander";
@@ -6001,6 +6001,26 @@ function isTokenExpired(resolved, nowSeconds = Math.floor(Date.now() / 1e3)) {
6001
6001
  if (resolved.expiresAt === null) return false;
6002
6002
  return resolved.expiresAt <= nowSeconds + EXPIRY_SKEW_SECONDS;
6003
6003
  }
6004
+ var EXPIRY_WARNING_SECONDS = 3 * 24 * 60 * 60;
6005
+ function expiresInSeconds(resolved, nowSeconds = Math.floor(Date.now() / 1e3)) {
6006
+ if (resolved.expiresAt === null) return null;
6007
+ return resolved.expiresAt - nowSeconds;
6008
+ }
6009
+ function describeTimeLeft(seconds) {
6010
+ const plural2 = (n, unit) => `in ${n} ${unit}${n === 1 ? "" : "s"}`;
6011
+ if (seconds >= 2 * 24 * 60 * 60) {
6012
+ return plural2(Math.floor(seconds / 86400), "day");
6013
+ }
6014
+ if (seconds >= 2 * 60 * 60) {
6015
+ return plural2(Math.floor(seconds / 3600), "hour");
6016
+ }
6017
+ return plural2(Math.max(0, Math.floor(seconds / 60)), "minute");
6018
+ }
6019
+ function expiryWarning(resolved, nowSeconds = Math.floor(Date.now() / 1e3)) {
6020
+ const left = expiresInSeconds(resolved, nowSeconds);
6021
+ if (left === null || left > EXPIRY_WARNING_SECONDS) return null;
6022
+ return `\u26A0 Your token (from ${describeTokenSource(resolved.source)}) expires ${describeTimeLeft(left)}. ${reauthHint(resolved.source)}`;
6023
+ }
6004
6024
  function describeTokenSource(source) {
6005
6025
  switch (source) {
6006
6026
  case "flag":
@@ -6242,8 +6262,8 @@ var env = createEnv({
6242
6262
  function requireConvexUrl() {
6243
6263
  if (!env.CONVEX_URL) {
6244
6264
  throw new CliError(
6245
- "CONVEX_URL is not set, so the CLI cannot reach the Tolkbot backend.",
6246
- "Set CONVEX_URL to your Tolkbot deployment URL."
6265
+ "CONVEX_URL is not set, so the CLI cannot reach the TolkBot backend.",
6266
+ "Set CONVEX_URL to your TolkBot deployment URL."
6247
6267
  );
6248
6268
  }
6249
6269
  return env.CONVEX_URL;
@@ -6355,6 +6375,10 @@ async function listenOnFreePort(server, ports) {
6355
6375
  "Free one of these ports and try again."
6356
6376
  );
6357
6377
  }
6378
+ function validityNote(expiresAt) {
6379
+ const until = expiresAt === null ? "" : `, valid until ${new Date(expiresAt * 1e3).toLocaleString()}`;
6380
+ return `It is a session for you${until}. For CI, copy it into $${TOKEN_ENV_VAR}. If it leaks, run 'tolkbot auth logout --all'.`;
6381
+ }
6358
6382
  function LoginUI({ state }) {
6359
6383
  if (state.type === "opening") {
6360
6384
  return /* @__PURE__ */ jsxs(Box, { children: [
@@ -6390,11 +6414,7 @@ function LoginUI({ state }) {
6390
6414
  "Token saved to ",
6391
6415
  getTokenPath()
6392
6416
  ] }) }),
6393
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
6394
- "For CI, copy it into $",
6395
- TOKEN_ENV_VAR,
6396
- " instead."
6397
- ] })
6417
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: validityNote(state.expiresAt) })
6398
6418
  ] });
6399
6419
  }
6400
6420
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
@@ -6402,13 +6422,43 @@ function LoginUI({ state }) {
6402
6422
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: state.message })
6403
6423
  ] });
6404
6424
  }
6425
+ async function runPlainLogin(opts, verificationCode) {
6426
+ let server = null;
6427
+ try {
6428
+ server = await startCallbackServer(verificationCode);
6429
+ const authUrl = loginUrl(server.port, verificationCode);
6430
+ if (!opts.noBrowser) await open(authUrl);
6431
+ console.log(`Open this URL in a browser to log in: ${authUrl}`);
6432
+ console.log(`Verification code: ${verificationCode}`);
6433
+ console.log(
6434
+ "The browser must reach this machine on localhost; forward the port if it runs elsewhere."
6435
+ );
6436
+ console.log("Waiting for authentication\u2026");
6437
+ const token = await server.token;
6438
+ await saveToken(token);
6439
+ console.log(`Authenticated. Token saved to ${getTokenPath()}.`);
6440
+ console.log(validityNote(decodeTokenExpiry(token)));
6441
+ } catch (error62) {
6442
+ server?.close();
6443
+ console.error(
6444
+ `Authentication failed: ${error62 instanceof Error ? error62.message : String(error62)}`
6445
+ );
6446
+ return exitAfterFlush(1);
6447
+ }
6448
+ }
6449
+ function loginUrl(port, verificationCode) {
6450
+ return `${env.TOLKBOT_WEB_URL}/cli/login/confirm?port=${port}&code=${verificationCode}`;
6451
+ }
6405
6452
  async function runAuthLogin(opts = {}) {
6406
6453
  const verificationCode = generateVerificationCode();
6454
+ if (!process.stdout.isTTY) {
6455
+ return runPlainLogin(opts, verificationCode);
6456
+ }
6407
6457
  let { unmount } = render(/* @__PURE__ */ jsx(LoginUI, { state: { type: "opening" } }));
6408
6458
  let server = null;
6409
6459
  try {
6410
6460
  server = await startCallbackServer(verificationCode);
6411
- const authUrl = `${env.TOLKBOT_WEB_URL}/cli/login/confirm?port=${server.port}&code=${verificationCode}`;
6461
+ const authUrl = loginUrl(server.port, verificationCode);
6412
6462
  if (!opts.noBrowser) {
6413
6463
  await open(authUrl);
6414
6464
  }
@@ -6430,7 +6480,14 @@ async function runAuthLogin(opts = {}) {
6430
6480
  const token = await server.token;
6431
6481
  await saveToken(token);
6432
6482
  unmount();
6433
- const successUi = render(/* @__PURE__ */ jsx(LoginUI, { state: { type: "success" } }));
6483
+ const successUi = render(
6484
+ /* @__PURE__ */ jsx(
6485
+ LoginUI,
6486
+ {
6487
+ state: { type: "success", expiresAt: decodeTokenExpiry(token) }
6488
+ }
6489
+ )
6490
+ );
6434
6491
  unmount = successUi.unmount;
6435
6492
  await successUi.waitUntilExit();
6436
6493
  } catch (error62) {
@@ -6452,9 +6509,107 @@ async function runAuthLogin(opts = {}) {
6452
6509
  }
6453
6510
  }
6454
6511
 
6512
+ // ../backend/convex/_generated/api.js
6513
+ import { anyApi, componentsGeneric } from "convex/server";
6514
+ var api = anyApi;
6515
+ var components = componentsGeneric();
6516
+
6455
6517
  // src/commands/auth/logout.tsx
6456
6518
  import { Box as Box2, render as render2, Text as Text2 } from "ink";
6457
6519
 
6520
+ // ../shared/src/auth/index.ts
6521
+ var CLI_SIGNED_OUT = "CLI_SIGNED_OUT";
6522
+ function findCliSignedOut(error62) {
6523
+ return readPayload(error62) ?? readPayload(readProperty(error62, "data"));
6524
+ }
6525
+ function readPayload(candidate) {
6526
+ const code = readProperty(candidate, "code");
6527
+ const message = readProperty(candidate, "message");
6528
+ const signedOutAt = readProperty(candidate, "signedOutAt");
6529
+ if (code !== CLI_SIGNED_OUT || typeof message !== "string" || typeof signedOutAt !== "number") {
6530
+ return null;
6531
+ }
6532
+ return { code: CLI_SIGNED_OUT, message, signedOutAt };
6533
+ }
6534
+ function readProperty(candidate, key) {
6535
+ if (typeof candidate !== "object" || candidate === null) {
6536
+ return void 0;
6537
+ }
6538
+ return candidate[key];
6539
+ }
6540
+
6541
+ // src/lib/convex-client.ts
6542
+ import { ConvexHttpClient } from "convex/browser";
6543
+ var BACKEND_METHODS = /* @__PURE__ */ new Set(["query", "mutation", "action"]);
6544
+ var client2 = null;
6545
+ function getConvexClient() {
6546
+ client2 ??= new ConvexHttpClient(requireConvexUrl());
6547
+ return client2;
6548
+ }
6549
+ function translateAuthError(error62, resolved) {
6550
+ const signedOut = findCliSignedOut(error62);
6551
+ if (signedOut) {
6552
+ const at = new Date(signedOut.signedOutAt).toLocaleString();
6553
+ return new AuthError(
6554
+ `These credentials were signed out on ${at} (from ${describeTokenSource(resolved.source)}).`,
6555
+ reauthHint(resolved.source)
6556
+ );
6557
+ }
6558
+ if (!isUnauthenticatedError(error62)) return error62;
6559
+ return new AuthError(
6560
+ `Your token was rejected (from ${describeTokenSource(resolved.source)}). It may be revoked, malformed, or issued for a different deployment.`,
6561
+ reauthHint(resolved.source)
6562
+ );
6563
+ }
6564
+ function withAuthErrors(target, resolved) {
6565
+ return new Proxy(target, {
6566
+ get(obj, prop, receiver) {
6567
+ const value = Reflect.get(obj, prop, receiver);
6568
+ if (typeof value !== "function" || !BACKEND_METHODS.has(String(prop))) {
6569
+ return value;
6570
+ }
6571
+ const method = value;
6572
+ return (...args) => {
6573
+ const result = method.apply(obj, args);
6574
+ if (result instanceof Promise) {
6575
+ return result.catch((error62) => {
6576
+ throw translateAuthError(error62, resolved);
6577
+ });
6578
+ }
6579
+ return result;
6580
+ };
6581
+ }
6582
+ });
6583
+ }
6584
+ var warnedAboutExpiry = false;
6585
+ function warnOnceIfExpiringSoon(resolved) {
6586
+ if (warnedAboutExpiry) return;
6587
+ const warning = expiryWarning(resolved);
6588
+ if (!warning) return;
6589
+ warnedAboutExpiry = true;
6590
+ console.error(warning);
6591
+ }
6592
+ async function getAuthenticatedConvexClient() {
6593
+ const convexClient = getConvexClient();
6594
+ const resolved = await resolveToken();
6595
+ if (!resolved) {
6596
+ throw new AuthError(
6597
+ "Not authenticated.",
6598
+ `Run 'tolkbot auth login', or set $${TOKEN_ENV_VAR} for CI and headless environments.`
6599
+ );
6600
+ }
6601
+ if (isTokenExpired(resolved)) {
6602
+ const expiredAt = resolved.expiresAt ? new Date(resolved.expiresAt * 1e3).toLocaleString() : "an unknown time";
6603
+ throw new AuthError(
6604
+ `Your token expired at ${expiredAt} (from ${describeTokenSource(resolved.source)}).`,
6605
+ reauthHint(resolved.source)
6606
+ );
6607
+ }
6608
+ warnOnceIfExpiringSoon(resolved);
6609
+ convexClient.setAuth(resolved.token);
6610
+ return withAuthErrors(convexClient, resolved);
6611
+ }
6612
+
6458
6613
  // src/utils/exit.ts
6459
6614
  var CliExit = class extends Error {
6460
6615
  constructor(code) {
@@ -6494,52 +6649,88 @@ function jsonResult(data, exitCode) {
6494
6649
 
6495
6650
  // src/commands/auth/logout.tsx
6496
6651
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
6652
+ function formatSeconds(seconds) {
6653
+ return new Date(seconds * 1e3).toLocaleString();
6654
+ }
6655
+ function stillValidNote(expiresAt) {
6656
+ const until = expiresAt === null ? "until it expires" : `until ${formatSeconds(expiresAt)}`;
6657
+ return `The token stays valid anywhere else it was copied ${until}. Run 'tolkbot auth logout --all' to sign out every CLI session.`;
6658
+ }
6497
6659
  function LogoutUI({ state }) {
6498
- if (state.type === "success") {
6660
+ if (state.type === "error") {
6499
6661
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
6500
- /* @__PURE__ */ jsxs2(Box2, { children: [
6501
- /* @__PURE__ */ jsx2(Text2, { color: "green", children: "\u2713" }),
6502
- /* @__PURE__ */ jsx2(Text2, { children: state.hadToken ? " Successfully logged out!" : " Already logged out." })
6503
- ] }),
6504
- state.envTokenStillSet && /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
6505
- /* @__PURE__ */ jsxs2(Text2, { color: "yellow", children: [
6506
- "\u26A0 $",
6507
- TOKEN_ENV_VAR,
6508
- " is still set in your environment"
6509
- ] }),
6510
- /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
6511
- "It takes precedence over ",
6512
- getTokenPath(),
6513
- ", so the CLI remains authenticated. Unset it to fully log out."
6514
- ] })
6515
- ] })
6662
+ /* @__PURE__ */ jsx2(Text2, { color: "red", children: "\u2717 Logout failed" }),
6663
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: state.message })
6516
6664
  ] });
6517
6665
  }
6666
+ const signedOutAll = state.signedOutAt !== null;
6667
+ let headline = state.hadToken ? " Successfully logged out!" : " Already logged out.";
6668
+ if (signedOutAll) headline = " Signed out every CLI session.";
6518
6669
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
6519
- /* @__PURE__ */ jsx2(Text2, { color: "red", children: "\u2717 Logout failed" }),
6520
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: state.message })
6670
+ /* @__PURE__ */ jsxs2(Box2, { children: [
6671
+ /* @__PURE__ */ jsx2(Text2, { color: "green", children: "\u2713" }),
6672
+ /* @__PURE__ */ jsx2(Text2, { children: headline })
6673
+ ] }),
6674
+ signedOutAll && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
6675
+ "Every CLI token issued before",
6676
+ " ",
6677
+ new Date(state.signedOutAt ?? 0).toLocaleString(),
6678
+ " is refused, on every machine. Your dashboard session is not affected."
6679
+ ] }),
6680
+ !signedOutAll && state.hadToken && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: stillValidNote(state.removedTokenExpiresAt) }),
6681
+ state.envTokenStillSet && /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
6682
+ /* @__PURE__ */ jsxs2(Text2, { color: "yellow", children: [
6683
+ "\u26A0 $",
6684
+ TOKEN_ENV_VAR,
6685
+ " is still set in your environment"
6686
+ ] }),
6687
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: signedOutAll ? "It was signed out too, so remove it." : `It takes precedence over ${getTokenPath()}, so the CLI remains authenticated. Unset it to fully log out.` })
6688
+ ] })
6521
6689
  ] });
6522
6690
  }
6691
+ async function signOutAllCliSessions() {
6692
+ const client3 = await getAuthenticatedConvexClient();
6693
+ const { signedOutAt } = await client3.action(
6694
+ api.actions.cliSessions.signOutAll,
6695
+ {}
6696
+ );
6697
+ return signedOutAt;
6698
+ }
6523
6699
  async function runAuthLogout(opts = {}) {
6524
6700
  try {
6525
- const hadToken = await loadToken() !== null;
6701
+ const signedOutAt = opts.all ? await signOutAllCliSessions() : null;
6702
+ const stored = await loadToken();
6703
+ const hadToken = stored !== null;
6704
+ const removedTokenExpiresAt = stored ? decodeTokenExpiry(stored) : null;
6526
6705
  await deleteToken();
6527
6706
  const envTokenStillSet = hasEnvToken();
6528
6707
  if (opts.json) {
6708
+ const warnings = [
6709
+ ...signedOutAt === null && hadToken ? [stillValidNote(removedTokenExpiresAt)] : [],
6710
+ ...envTokenStillSet ? [
6711
+ signedOutAt === null ? `$${TOKEN_ENV_VAR} is still set and takes precedence over ${getTokenPath()}.` : `$${TOKEN_ENV_VAR} is still set; it was signed out too, so remove it.`
6712
+ ] : []
6713
+ ];
6529
6714
  jsonSuccess({
6530
6715
  loggedOut: true,
6531
6716
  hadStoredToken: hadToken,
6532
6717
  envTokenStillSet,
6533
- ...envTokenStillSet ? {
6534
- warning: `$${TOKEN_ENV_VAR} is still set and takes precedence over ${getTokenPath()}.`
6535
- } : {}
6718
+ signedOutAllCliSessions: signedOutAt !== null,
6719
+ ...signedOutAt !== null ? { signedOutAt: new Date(signedOutAt).toISOString() } : {},
6720
+ ...warnings.length > 0 ? { warning: warnings.join(" ") } : {}
6536
6721
  });
6537
6722
  }
6538
6723
  const ui = render2(
6539
6724
  /* @__PURE__ */ jsx2(
6540
6725
  LogoutUI,
6541
6726
  {
6542
- state: { type: "success", hadToken, envTokenStillSet }
6727
+ state: {
6728
+ type: "success",
6729
+ hadToken,
6730
+ envTokenStillSet,
6731
+ removedTokenExpiresAt,
6732
+ signedOutAt
6733
+ }
6543
6734
  }
6544
6735
  )
6545
6736
  );
@@ -6557,76 +6748,17 @@ async function runAuthLogout(opts = {}) {
6557
6748
 
6558
6749
  // src/commands/auth/whoami.tsx
6559
6750
  import { Spinner as Spinner2 } from "@inkjs/ui";
6560
-
6561
- // ../backend/convex/_generated/api.js
6562
- import { anyApi, componentsGeneric } from "convex/server";
6563
- var api = anyApi;
6564
- var components = componentsGeneric();
6565
-
6566
- // src/commands/auth/whoami.tsx
6567
6751
  import { Box as Box3, render as render3, Text as Text3 } from "ink";
6568
-
6569
- // src/lib/convex-client.ts
6570
- import { ConvexHttpClient } from "convex/browser";
6571
- var BACKEND_METHODS = /* @__PURE__ */ new Set(["query", "mutation", "action"]);
6572
- var client2 = null;
6573
- function getConvexClient() {
6574
- client2 ??= new ConvexHttpClient(requireConvexUrl());
6575
- return client2;
6576
- }
6577
- function translateAuthError(error62, resolved) {
6578
- if (!isUnauthenticatedError(error62)) return error62;
6579
- return new AuthError(
6580
- `Your token was rejected (from ${describeTokenSource(resolved.source)}). It may be revoked, malformed, or issued for a different deployment.`,
6581
- reauthHint(resolved.source)
6582
- );
6583
- }
6584
- function withAuthErrors(target, resolved) {
6585
- return new Proxy(target, {
6586
- get(obj, prop, receiver) {
6587
- const value = Reflect.get(obj, prop, receiver);
6588
- if (typeof value !== "function" || !BACKEND_METHODS.has(String(prop))) {
6589
- return value;
6590
- }
6591
- const method = value;
6592
- return (...args) => {
6593
- const result = method.apply(obj, args);
6594
- if (result instanceof Promise) {
6595
- return result.catch((error62) => {
6596
- throw translateAuthError(error62, resolved);
6597
- });
6598
- }
6599
- return result;
6600
- };
6601
- }
6602
- });
6603
- }
6604
- async function getAuthenticatedConvexClient() {
6605
- const convexClient = getConvexClient();
6606
- const resolved = await resolveToken();
6607
- if (!resolved) {
6608
- throw new AuthError(
6609
- "Not authenticated.",
6610
- `Run 'tolkbot auth login', or set $${TOKEN_ENV_VAR} for CI and headless environments.`
6611
- );
6612
- }
6613
- if (isTokenExpired(resolved)) {
6614
- const expiredAt = resolved.expiresAt ? new Date(resolved.expiresAt * 1e3).toLocaleString() : "an unknown time";
6615
- throw new AuthError(
6616
- `Your token expired at ${expiredAt} (from ${describeTokenSource(resolved.source)}).`,
6617
- reauthHint(resolved.source)
6618
- );
6619
- }
6620
- convexClient.setAuth(resolved.token);
6621
- return withAuthErrors(convexClient, resolved);
6622
- }
6623
-
6624
- // src/commands/auth/whoami.tsx
6625
6752
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
6626
6753
  function formatExpiry(expiresAt) {
6627
6754
  if (expiresAt === null) return "unknown";
6628
6755
  return new Date(expiresAt * 1e3).toLocaleString();
6629
6756
  }
6757
+ function describeExpiry(resolved) {
6758
+ const left = expiresInSeconds(resolved);
6759
+ if (left === null) return "Expiry unknown";
6760
+ return `Expires ${describeTimeLeft(left)} (${formatExpiry(resolved.expiresAt)})`;
6761
+ }
6630
6762
  function WhoamiUI({ state }) {
6631
6763
  if (state.type === "loading") {
6632
6764
  return /* @__PURE__ */ jsxs3(Box3, { children: [
@@ -6651,8 +6783,7 @@ function WhoamiUI({ state }) {
6651
6783
  /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2502 " }),
6652
6784
  /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
6653
6785
  " ",
6654
- "Expires ",
6655
- formatExpiry(state.resolved.expiresAt)
6786
+ describeExpiry(state.resolved)
6656
6787
  ] })
6657
6788
  ] }),
6658
6789
  /* @__PURE__ */ jsxs3(Box3, { children: [
@@ -6731,7 +6862,8 @@ async function runAuthWhoami(opts = {}) {
6731
6862
  email: user.email,
6732
6863
  tokenSource: resolved.source,
6733
6864
  tokenPath: getTokenPath(),
6734
- expiresAt: resolved.expiresAt ? new Date(resolved.expiresAt * 1e3).toISOString() : null
6865
+ expiresAt: resolved.expiresAt ? new Date(resolved.expiresAt * 1e3).toISOString() : null,
6866
+ expiresInSeconds: expiresInSeconds(resolved)
6735
6867
  });
6736
6868
  }
6737
6869
  return renderAndExit(
@@ -6750,9 +6882,9 @@ async function runAuthWhoami(opts = {}) {
6750
6882
 
6751
6883
  // src/commands/auth/index.ts
6752
6884
  var authCommand = new Command("auth").description(
6753
- "Authenticate with Tolkbot"
6885
+ "Authenticate with TolkBot"
6754
6886
  );
6755
- authCommand.command("login").description("Log in to Tolkbot").option(
6887
+ authCommand.command("login").description("Log in to TolkBot").option(
6756
6888
  "--no-browser",
6757
6889
  "Print the login URL instead of opening a browser (for headless hosts)"
6758
6890
  ).action(async (opts) => {
@@ -6763,8 +6895,14 @@ authCommand.command("login").description("Log in to Tolkbot").option(
6763
6895
  const parsed = validateOrExit(schema, opts);
6764
6896
  await runAuthLogin({ noBrowser: !parsed.browser });
6765
6897
  });
6766
- authCommand.command("logout").description("Log out of Tolkbot").option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
6767
- const schema = z2.object({ json: z2.boolean().optional() });
6898
+ authCommand.command("logout").description("Log out of TolkBot").option(
6899
+ "--all",
6900
+ "Sign out every CLI session on every machine, then remove the local token"
6901
+ ).option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
6902
+ const schema = z2.object({
6903
+ json: z2.boolean().optional(),
6904
+ all: z2.boolean().optional()
6905
+ });
6768
6906
  const parsed = validateOrExit(schema, opts);
6769
6907
  await runAuthLogout(parsed);
6770
6908
  });
@@ -26990,8 +27128,11 @@ var DEFAULT_BEHAVIORAL_SETTINGS = {
26990
27128
  tonePreset: "formal",
26991
27129
  toneInstructions: "Use a formal, professional tone. Avoid slang.",
26992
27130
  workflow: {
27131
+ // What a project that never stored the field resolves to: every
27132
+ // project created before DUO-536. Those keep it off, so a project
27133
+ // nobody switched on does not start opening pull requests because a
27134
+ // default moved. New projects store `NEW_PROJECT_AUTO_CREATE_PR`.
26993
27135
  autoCreatePR: false,
26994
- // Opt-in feature - users must enable in dashboard
26995
27136
  branchPattern: "tolkbot/{{projectName}}-i18n-{{lng}}",
26996
27137
  baseBranches: void 0,
26997
27138
  prTargetBranch: void 0,
@@ -27606,210 +27747,13 @@ glossaryCommand.command("remove").description("Remove a glossary term").required
27606
27747
  await runGlossaryRemove(parsed);
27607
27748
  });
27608
27749
 
27609
- // src/commands/help.tsx
27610
- import { Box as Box10, render as render9, Text as Text10 } from "ink";
27611
-
27612
- // src/components/logo.tsx
27613
- import { Box as Box9, Text as Text9 } from "ink";
27614
- import { jsx as jsx9 } from "react/jsx-runtime";
27615
- var logoText = `
27616
- \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
27617
- \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557 \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D
27618
- \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
27619
- \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
27620
- \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551
27621
- `;
27622
- function Logo() {
27623
- return /* @__PURE__ */ jsx9(Box9, { marginBottom: 1, children: /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: logoText }) });
27624
- }
27625
- var logo_default = Logo;
27626
-
27627
- // src/commands/help.tsx
27628
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
27629
- function HelpUI() {
27630
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", paddingY: 1, children: [
27631
- /* @__PURE__ */ jsx10(logo_default, {}),
27632
- /* @__PURE__ */ jsx10(Box10, { children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "Localization workflow for your repo" }) }),
27633
- /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "column", children: [
27634
- /* @__PURE__ */ jsx10(Text10, { bold: true, children: "Usage:" }),
27635
- /* @__PURE__ */ jsxs9(Box10, { marginLeft: 2, children: [
27636
- /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "$ " }),
27637
- /* @__PURE__ */ jsx10(Text10, { children: "tolkbot [command] [options]" })
27638
- ] })
27639
- ] }),
27640
- /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "column", children: [
27641
- /* @__PURE__ */ jsx10(Text10, { bold: true, children: "Commands:" }),
27642
- /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, marginLeft: 2, flexDirection: "column", children: [
27643
- /* @__PURE__ */ jsxs9(Box10, { children: [
27644
- /* @__PURE__ */ jsx10(Text10, { color: "green", children: "init" }),
27645
- /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
27646
- " ",
27647
- " "
27648
- ] }),
27649
- /* @__PURE__ */ jsx10(Text10, { children: "Initialize Tolkbot in the current repo" })
27650
- ] }),
27651
- /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, children: [
27652
- /* @__PURE__ */ jsx10(Text10, { color: "green", children: "auth" }),
27653
- /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
27654
- " ",
27655
- " "
27656
- ] }),
27657
- /* @__PURE__ */ jsx10(Text10, { children: "Authenticate with Tolkbot" })
27658
- ] }),
27659
- /* @__PURE__ */ jsxs9(Box10, { marginLeft: 2, children: [
27660
- /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
27661
- "\u2022 login ",
27662
- " "
27663
- ] }),
27664
- /* @__PURE__ */ jsx10(Text10, { children: "Log in to Tolkbot" })
27665
- ] }),
27666
- /* @__PURE__ */ jsxs9(Box10, { marginLeft: 2, children: [
27667
- /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
27668
- "\u2022 logout ",
27669
- " "
27670
- ] }),
27671
- /* @__PURE__ */ jsx10(Text10, { children: "Log out of Tolkbot" })
27672
- ] }),
27673
- /* @__PURE__ */ jsxs9(Box10, { marginLeft: 2, children: [
27674
- /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
27675
- "\u2022 whoami ",
27676
- " "
27677
- ] }),
27678
- /* @__PURE__ */ jsx10(Text10, { children: "Show the current authenticated user" })
27679
- ] }),
27680
- /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, children: [
27681
- /* @__PURE__ */ jsx10(Text10, { color: "green", children: "validate" }),
27682
- /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
27683
- " ",
27684
- " "
27685
- ] }),
27686
- /* @__PURE__ */ jsx10(Text10, { children: "Validate translation files and configuration" })
27687
- ] }),
27688
- /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, children: [
27689
- /* @__PURE__ */ jsx10(Text10, { color: "green", children: "translate" }),
27690
- /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
27691
- " ",
27692
- " "
27693
- ] }),
27694
- /* @__PURE__ */ jsx10(Text10, { children: "Translate with Tolkbot" })
27695
- ] }),
27696
- /* @__PURE__ */ jsxs9(Box10, { marginLeft: 2, children: [
27697
- /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
27698
- "\u2022 plan ",
27699
- " "
27700
- ] }),
27701
- /* @__PURE__ */ jsx10(Text10, { children: "Generate translation plan" })
27702
- ] }),
27703
- /* @__PURE__ */ jsxs9(Box10, { marginLeft: 2, children: [
27704
- /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
27705
- "\u2022 run ",
27706
- " "
27707
- ] }),
27708
- /* @__PURE__ */ jsx10(Text10, { children: "Run translation job" })
27709
- ] }),
27710
- /* @__PURE__ */ jsxs9(Box10, { marginLeft: 2, children: [
27711
- /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
27712
- "\u2022 apply ",
27713
- " "
27714
- ] }),
27715
- /* @__PURE__ */ jsx10(Text10, { children: "Apply translation job results" })
27716
- ] }),
27717
- /* @__PURE__ */ jsxs9(Box10, { marginLeft: 2, children: [
27718
- /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
27719
- "\u2022 status ",
27720
- " "
27721
- ] }),
27722
- /* @__PURE__ */ jsx10(Text10, { children: "Check status of translation job" })
27723
- ] }),
27724
- /* @__PURE__ */ jsxs9(Box10, { marginLeft: 2, children: [
27725
- /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
27726
- "\u2022 cancel ",
27727
- " "
27728
- ] }),
27729
- /* @__PURE__ */ jsx10(Text10, { children: "Cancel translation job" })
27730
- ] }),
27731
- /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, children: [
27732
- /* @__PURE__ */ jsx10(Text10, { color: "green", children: "telemetry" }),
27733
- /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
27734
- " ",
27735
- " "
27736
- ] }),
27737
- /* @__PURE__ */ jsx10(Text10, { children: "Show or change anonymous usage reporting (status, on, off)" })
27738
- ] }),
27739
- /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, children: [
27740
- /* @__PURE__ */ jsx10(Text10, { color: "green", children: "help" }),
27741
- /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
27742
- " ",
27743
- " "
27744
- ] }),
27745
- /* @__PURE__ */ jsx10(Text10, { children: "Show this help message" })
27746
- ] })
27747
- ] })
27748
- ] }),
27749
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "Telemetry: one event per command with its name, outcome and duration; never file paths or content. Opt out with `tolkbot telemetry off` or TOLKBOT_TELEMETRY=0." }) }),
27750
- /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "column", children: [
27751
- /* @__PURE__ */ jsx10(Text10, { bold: true, children: "Options:" }),
27752
- /* @__PURE__ */ jsxs9(Box10, { marginLeft: 2, flexDirection: "column", children: [
27753
- /* @__PURE__ */ jsxs9(Box10, { children: [
27754
- /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "-h, --help" }),
27755
- /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
27756
- " ",
27757
- " "
27758
- ] }),
27759
- /* @__PURE__ */ jsx10(Text10, { children: "Show help for a command" })
27760
- ] }),
27761
- /* @__PURE__ */ jsxs9(Box10, { children: [
27762
- /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: "-v, --version" }),
27763
- /* @__PURE__ */ jsxs9(Text10, { dimColor: true, children: [
27764
- " ",
27765
- " "
27766
- ] }),
27767
- /* @__PURE__ */ jsx10(Text10, { children: "Show version number" })
27768
- ] })
27769
- ] })
27770
- ] }),
27771
- /* @__PURE__ */ jsxs9(Box10, { marginTop: 1, flexDirection: "column", children: [
27772
- /* @__PURE__ */ jsx10(Text10, { bold: true, children: "Examples:" }),
27773
- /* @__PURE__ */ jsxs9(Box10, { marginLeft: 2, flexDirection: "column", children: [
27774
- /* @__PURE__ */ jsxs9(Box10, { children: [
27775
- /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "$ " }),
27776
- /* @__PURE__ */ jsx10(Text10, { children: "tolkbot init" })
27777
- ] }),
27778
- /* @__PURE__ */ jsxs9(Box10, { children: [
27779
- /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "$ " }),
27780
- /* @__PURE__ */ jsx10(Text10, { children: "tolkbot auth login" })
27781
- ] }),
27782
- /* @__PURE__ */ jsxs9(Box10, { children: [
27783
- /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "$ " }),
27784
- /* @__PURE__ */ jsx10(Text10, { children: "tolkbot validate" })
27785
- ] }),
27786
- /* @__PURE__ */ jsxs9(Box10, { children: [
27787
- /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "$ " }),
27788
- /* @__PURE__ */ jsx10(Text10, { children: "tolkbot translate plan --src en --dst es" })
27789
- ] }),
27790
- /* @__PURE__ */ jsxs9(Box10, { children: [
27791
- /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "$ " }),
27792
- /* @__PURE__ */ jsx10(Text10, { children: "tolkbot translate run --dst es" })
27793
- ] }),
27794
- /* @__PURE__ */ jsxs9(Box10, { children: [
27795
- /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "$ " }),
27796
- /* @__PURE__ */ jsx10(Text10, { children: "tolkbot translate apply --job-id <id>" })
27797
- ] })
27798
- ] })
27799
- ] }),
27800
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "For more info on a specific command, use: tolkbot [command] --help" }) })
27801
- ] });
27802
- }
27803
- async function runHelp() {
27804
- const { waitUntilExit } = render9(/* @__PURE__ */ jsx10(HelpUI, {}));
27805
- await waitUntilExit();
27806
- }
27807
-
27808
27750
  // src/commands/init/index.tsx
27809
- import { Box as Box12, render as render10, Text as Text12 } from "ink";
27751
+ import { Box as Box10, render as render9, Text as Text10 } from "ink";
27810
27752
 
27811
27753
  // src/commands/init/core.ts
27812
27754
  import { execSync as execSync2 } from "child_process";
27755
+ var DETECTION_TIMEOUT_MS = 3 * 6e4;
27756
+ var DETECTION_POLL_MS = 1e3;
27813
27757
  function getRecommendedFramework(projectType) {
27814
27758
  const map2 = {
27815
27759
  "next-app": "next-intl",
@@ -27898,53 +27842,100 @@ async function linkRepository(cwd, plan) {
27898
27842
  }
27899
27843
  );
27900
27844
  const existing = existingProjects[0];
27845
+ let projectId;
27846
+ let createdProject = false;
27901
27847
  if (existing) {
27902
- return {
27903
- projectId: existing._id,
27904
- repositoryId: repository._id,
27905
- organizationId: repository.organizationId,
27906
- owner,
27907
- name,
27908
- createdProject: false
27909
- };
27848
+ projectId = existing._id;
27849
+ } else {
27850
+ const [baseLanguage2] = assertSupportedLocales(
27851
+ [plan.baseLanguage],
27852
+ "base locale"
27853
+ );
27854
+ const targetLanguages = assertSupportedLocales(
27855
+ plan.targetLanguages,
27856
+ "target locale(s)"
27857
+ );
27858
+ if (!baseLanguage2) {
27859
+ throw new CliError("Could not resolve a base locale.");
27860
+ }
27861
+ const created = await convexClient.mutation(
27862
+ api.projects.createWithInitialization,
27863
+ {
27864
+ name,
27865
+ organizationId: repository.organizationId,
27866
+ repositoryId: repository._id,
27867
+ subpath: "",
27868
+ baseLanguage: baseLanguage2,
27869
+ targetLanguages,
27870
+ sourcePattern: "",
27871
+ outputPattern: "",
27872
+ useOrgSettings: true,
27873
+ hasPatternDetection: false
27874
+ }
27875
+ );
27876
+ projectId = created.projectId;
27877
+ createdProject = true;
27910
27878
  }
27911
- const [baseLanguage2] = assertSupportedLocales(
27912
- [plan.baseLanguage],
27913
- "base locale"
27914
- );
27915
- const targetLanguages = assertSupportedLocales(
27916
- plan.targetLanguages,
27917
- "target locale(s)"
27879
+ const project = await waitForDetection(
27880
+ () => convexClient.query(api.projects.getProjectForCli, { projectId })
27918
27881
  );
27919
- if (!baseLanguage2) {
27920
- throw new CliError("Could not resolve a base locale.");
27921
- }
27922
- const projectId = await convexClient.mutation(api.projects.create, {
27923
- name,
27924
- organizationId: repository.organizationId,
27925
- repositoryId: repository._id,
27926
- subpath: "",
27927
- baseLanguage: baseLanguage2,
27928
- targetLanguages,
27929
- sourcePattern: "",
27930
- outputPattern: "",
27931
- useOrgSettings: true
27932
- });
27882
+ requireSourcePattern(project, `${owner}/${name}`);
27933
27883
  return {
27934
27884
  projectId,
27935
27885
  repositoryId: repository._id,
27936
27886
  organizationId: repository.organizationId,
27937
27887
  owner,
27938
27888
  name,
27939
- createdProject: true
27940
- };
27889
+ createdProject,
27890
+ baseLanguage: project.baseLanguage,
27891
+ targetLanguages: project.targetLanguages,
27892
+ sourcePattern: project.sourcePattern
27893
+ };
27894
+ }
27895
+ function isDetecting(project) {
27896
+ return project.initializationStatus === "pending" || project.initializationStatus === "initializing";
27897
+ }
27898
+ async function waitForDetection(read, opts = {}) {
27899
+ const timeoutMs = opts.timeoutMs ?? DETECTION_TIMEOUT_MS;
27900
+ const pollMs = opts.pollMs ?? DETECTION_POLL_MS;
27901
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
27902
+ const now = opts.now ?? Date.now;
27903
+ const deadline = now() + timeoutMs;
27904
+ for (; ; ) {
27905
+ const project = await read();
27906
+ if (!project) {
27907
+ throw new CliError(
27908
+ "The project disappeared while TolkBot was detecting its files.",
27909
+ "Run 'tolkbot init' again."
27910
+ );
27911
+ }
27912
+ if (!isDetecting(project)) return project;
27913
+ if (now() >= deadline) {
27914
+ throw new CliError(
27915
+ `TolkBot is still detecting the project's translation files after ${Math.round(timeoutMs / 1e3)} seconds.`,
27916
+ "Run 'tolkbot init' again in a minute; it picks up the same project."
27917
+ );
27918
+ }
27919
+ await sleep(pollMs);
27920
+ }
27921
+ }
27922
+ function patternSettingsUrl(slug) {
27923
+ return `${env.TOLKBOT_WEB_URL}/projects/${slug}/settings/languages`;
27924
+ }
27925
+ function requireSourcePattern(project, repository) {
27926
+ if (project.sourcePattern.trim() !== "") return;
27927
+ const reason = project.initializationStatus === "failed" && project.initializationError ? ` Detection failed: ${project.initializationError}` : "";
27928
+ throw new CliError(
27929
+ `TolkBot found no translation files in ${repository}.${reason}`,
27930
+ `Set the file pattern in the project's settings at ${patternSettingsUrl(project.slug)}, then run 'tolkbot init' again.`
27931
+ );
27941
27932
  }
27942
27933
 
27943
27934
  // src/commands/init/wizard.tsx
27944
27935
  import * as React from "react";
27945
27936
  import { Select, Spinner as Spinner8, TextInput } from "@inkjs/ui";
27946
- import { Box as Box11, Text as Text11 } from "ink";
27947
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
27937
+ import { Box as Box9, Text as Text9 } from "ink";
27938
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
27948
27939
  function friendlyFramework(f) {
27949
27940
  const map2 = {
27950
27941
  "next-intl": "Next.js with next-intl",
@@ -28006,6 +27997,9 @@ function InitWizard({
28006
27997
  });
28007
27998
  setSetupData((prev) => ({
28008
27999
  ...prev,
28000
+ // What detection settled on, for the next-step hint.
28001
+ baseLanguage: result.baseLanguage,
28002
+ targetLanguages: result.targetLanguages,
28009
28003
  projectId: result.projectId,
28010
28004
  repositoryId: result.repositoryId,
28011
28005
  owner: result.owner,
@@ -28071,30 +28065,30 @@ function InitWizard({
28071
28065
  }
28072
28066
  }, [step, error62, setupData.projectId, onFailure]);
28073
28067
  if (step === "detecting") {
28074
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
28075
- /* @__PURE__ */ jsxs10(Box11, { children: [
28076
- /* @__PURE__ */ jsx11(Spinner8, {}),
28077
- /* @__PURE__ */ jsx11(Text11, { children: " Detecting project setup..." })
28068
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
28069
+ /* @__PURE__ */ jsxs9(Box9, { children: [
28070
+ /* @__PURE__ */ jsx9(Spinner8, {}),
28071
+ /* @__PURE__ */ jsx9(Text9, { children: " Detecting project setup..." })
28078
28072
  ] }),
28079
- /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
28073
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
28080
28074
  "Project: ",
28081
28075
  friendlyProjectType(detected.projectType)
28082
28076
  ] }) }),
28083
- detected.framework && /* @__PURE__ */ jsx11(Box11, { children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
28077
+ detected.framework && /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
28084
28078
  "Framework: ",
28085
28079
  friendlyFramework(detected.framework)
28086
28080
  ] }) })
28087
28081
  ] });
28088
28082
  }
28089
28083
  if (step === "setupChoice") {
28090
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
28091
- /* @__PURE__ */ jsx11(Text11, { children: "\u26A0\uFE0F No i18n library detected" }),
28092
- /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
28084
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
28085
+ /* @__PURE__ */ jsx9(Text9, { children: "\u26A0\uFE0F No i18n library detected" }),
28086
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
28093
28087
  "Detected: ",
28094
28088
  friendlyProjectType(detected.projectType)
28095
28089
  ] }) }),
28096
- /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { children: "Would you like to set up internationalization?" }) }),
28097
- /* @__PURE__ */ jsx11(
28090
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { children: "Would you like to set up internationalization?" }) }),
28091
+ /* @__PURE__ */ jsx9(
28098
28092
  Select,
28099
28093
  {
28100
28094
  options: [
@@ -28120,9 +28114,9 @@ function InitWizard({
28120
28114
  ] });
28121
28115
  }
28122
28116
  if (step === "frameworkSelect") {
28123
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
28124
- /* @__PURE__ */ jsx11(Text11, { children: "Select i18n framework:" }),
28125
- /* @__PURE__ */ jsx11(
28117
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
28118
+ /* @__PURE__ */ jsx9(Text9, { children: "Select i18n framework:" }),
28119
+ /* @__PURE__ */ jsx9(
28126
28120
  Select,
28127
28121
  {
28128
28122
  options: [
@@ -28150,9 +28144,9 @@ function InitWizard({
28150
28144
  if (step === "languageInput") {
28151
28145
  const subStep = !setupData.baseLanguage ? "base" : setupData.targetLanguages.length === 0 ? "targets" : "confirm";
28152
28146
  if (subStep === "base") {
28153
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
28154
- /* @__PURE__ */ jsx11(Text11, { children: "Base language (source):" }),
28155
- /* @__PURE__ */ jsx11(
28147
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
28148
+ /* @__PURE__ */ jsx9(Text9, { children: "Base language (source):" }),
28149
+ /* @__PURE__ */ jsx9(
28156
28150
  TextInput,
28157
28151
  {
28158
28152
  placeholder: "en",
@@ -28167,9 +28161,9 @@ function InitWizard({
28167
28161
  ] });
28168
28162
  }
28169
28163
  if (subStep === "targets") {
28170
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
28171
- /* @__PURE__ */ jsx11(Text11, { children: "Target languages (comma-separated, e.g. no,sv,da):" }),
28172
- /* @__PURE__ */ jsx11(
28164
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
28165
+ /* @__PURE__ */ jsx9(Text9, { children: "Target languages (comma-separated, e.g. no,sv,da):" }),
28166
+ /* @__PURE__ */ jsx9(
28173
28167
  TextInput,
28174
28168
  {
28175
28169
  placeholder: "no,sv",
@@ -28185,26 +28179,26 @@ function InitWizard({
28185
28179
  ] });
28186
28180
  }
28187
28181
  const setup = getFrameworkSetup(setupData.framework);
28188
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
28189
- /* @__PURE__ */ jsx11(Text11, { children: "Ready to set up:" }),
28190
- /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
28182
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
28183
+ /* @__PURE__ */ jsx9(Text9, { children: "Ready to set up:" }),
28184
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
28191
28185
  "Framework: ",
28192
28186
  friendlyFramework(setupData.framework)
28193
28187
  ] }) }),
28194
- /* @__PURE__ */ jsx11(Box11, { children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
28188
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
28195
28189
  "Base: ",
28196
28190
  setupData.baseLanguage
28197
28191
  ] }) }),
28198
- /* @__PURE__ */ jsx11(Box11, { children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
28192
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
28199
28193
  "Targets: ",
28200
28194
  setupData.targetLanguages.join(", ")
28201
28195
  ] }) }),
28202
- /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Will install:" }) }),
28203
- setup.dependencies.map((dep) => /* @__PURE__ */ jsx11(Box11, { paddingLeft: 2, children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
28196
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Will install:" }) }),
28197
+ setup.dependencies.map((dep) => /* @__PURE__ */ jsx9(Box9, { paddingLeft: 2, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
28204
28198
  "- ",
28205
28199
  dep
28206
28200
  ] }) }, dep)),
28207
- /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(
28201
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(
28208
28202
  Select,
28209
28203
  {
28210
28204
  options: [
@@ -28223,56 +28217,59 @@ function InitWizard({
28223
28217
  ] });
28224
28218
  }
28225
28219
  if (step === "installing") {
28226
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
28227
- /* @__PURE__ */ jsxs10(Box11, { children: [
28228
- /* @__PURE__ */ jsx11(Spinner8, {}),
28229
- /* @__PURE__ */ jsx11(Text11, { children: " Installing dependencies..." })
28220
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
28221
+ /* @__PURE__ */ jsxs9(Box9, { children: [
28222
+ /* @__PURE__ */ jsx9(Spinner8, {}),
28223
+ /* @__PURE__ */ jsx9(Text9, { children: " Installing dependencies..." })
28230
28224
  ] }),
28231
- /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
28225
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
28232
28226
  "Using ",
28233
28227
  detected.packageManager
28234
28228
  ] }) })
28235
28229
  ] });
28236
28230
  }
28237
28231
  if (step === "linkingRepository") {
28238
- return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", children: /* @__PURE__ */ jsxs10(Box11, { children: [
28239
- /* @__PURE__ */ jsx11(Spinner8, {}),
28240
- /* @__PURE__ */ jsx11(Text11, { children: " Linking to TolkBot..." })
28232
+ return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", children: /* @__PURE__ */ jsxs9(Box9, { children: [
28233
+ /* @__PURE__ */ jsx9(Spinner8, {}),
28234
+ /* @__PURE__ */ jsxs9(Text9, { children: [
28235
+ " ",
28236
+ "Linking to TolkBot and finding your translation files\u2026"
28237
+ ] })
28241
28238
  ] }) });
28242
28239
  }
28243
28240
  if (step === "done") {
28244
28241
  if (error62) {
28245
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
28246
- /* @__PURE__ */ jsx11(Text11, { color: "red", children: "\u2717 Setup failed" }),
28247
- /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { children: error62 }) })
28242
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
28243
+ /* @__PURE__ */ jsx9(Text9, { color: "red", children: "\u2717 Setup failed" }),
28244
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { children: error62 }) })
28248
28245
  ] });
28249
28246
  }
28250
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", children: [
28251
- /* @__PURE__ */ jsx11(Text11, { color: "green", children: "\u2713 Setup complete!" }),
28252
- /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
28247
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
28248
+ /* @__PURE__ */ jsx9(Text9, { color: "green", children: "\u2713 Setup complete!" }),
28249
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
28253
28250
  "Repository: ",
28254
28251
  setupData.owner,
28255
28252
  "/",
28256
28253
  setupData.name
28257
28254
  ] }) }),
28258
- /* @__PURE__ */ jsx11(Box11, { children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
28255
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
28259
28256
  "Framework: ",
28260
28257
  friendlyFramework(setupData.framework)
28261
28258
  ] }) }),
28262
- /* @__PURE__ */ jsx11(Box11, { children: /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
28259
+ /* @__PURE__ */ jsx9(Box9, { children: /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
28263
28260
  "Project ID: ",
28264
28261
  setupData.projectId
28265
28262
  ] }) }),
28266
- /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { children: "Created tolkbot.json" }) }),
28267
- setupData.shouldInstall && /* @__PURE__ */ jsxs10(Box11, { marginTop: 1, flexDirection: "column", children: [
28268
- /* @__PURE__ */ jsx11(Text11, { children: "Next steps:" }),
28269
- /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "1. Import i18n in your app - see docs" }),
28270
- /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
28263
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { children: "Created tolkbot.json" }) }),
28264
+ setupData.shouldInstall && /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
28265
+ /* @__PURE__ */ jsx9(Text9, { children: "Next steps:" }),
28266
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "1. Import i18n in your app - see docs" }),
28267
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
28271
28268
  "2. Add translations to ",
28272
28269
  setupData.baseLanguage,
28273
28270
  ".json"
28274
28271
  ] }),
28275
- /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
28272
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
28276
28273
  "3. Run: tolkbot translate run --src",
28277
28274
  " ",
28278
28275
  setupData.baseLanguage,
@@ -28281,9 +28278,9 @@ function InitWizard({
28281
28278
  setupData.targetLanguages[0] ?? "no"
28282
28279
  ] })
28283
28280
  ] }),
28284
- !setupData.shouldInstall && /* @__PURE__ */ jsxs10(Box11, { marginTop: 1, flexDirection: "column", children: [
28285
- /* @__PURE__ */ jsx11(Text11, { children: "Next steps:" }),
28286
- /* @__PURE__ */ jsxs10(Text11, { dimColor: true, children: [
28281
+ !setupData.shouldInstall && /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
28282
+ /* @__PURE__ */ jsx9(Text9, { children: "Next steps:" }),
28283
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
28287
28284
  "Run: tolkbot translate plan --src",
28288
28285
  " ",
28289
28286
  setupData.baseLanguage,
@@ -28294,11 +28291,11 @@ function InitWizard({
28294
28291
  ] })
28295
28292
  ] });
28296
28293
  }
28297
- return /* @__PURE__ */ jsx11(Text11, { children: "Unknown step" });
28294
+ return /* @__PURE__ */ jsx9(Text9, { children: "Unknown step" });
28298
28295
  }
28299
28296
 
28300
28297
  // src/commands/init/index.tsx
28301
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
28298
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
28302
28299
  var FRAMEWORKS = [
28303
28300
  "next-intl",
28304
28301
  "react-i18next",
@@ -28340,38 +28337,42 @@ function SummaryUI({
28340
28337
  plan,
28341
28338
  result
28342
28339
  }) {
28343
- return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", children: [
28344
- /* @__PURE__ */ jsxs11(Box12, { children: [
28345
- /* @__PURE__ */ jsx12(Text12, { color: "green", children: "\u2713" }),
28346
- /* @__PURE__ */ jsx12(Text12, { children: " Setup complete" })
28340
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
28341
+ /* @__PURE__ */ jsxs10(Box10, { children: [
28342
+ /* @__PURE__ */ jsx10(Text10, { color: "green", children: "\u2713" }),
28343
+ /* @__PURE__ */ jsx10(Text10, { children: " Setup complete" })
28347
28344
  ] }),
28348
- /* @__PURE__ */ jsxs11(Box12, { marginTop: 1, flexDirection: "column", children: [
28349
- /* @__PURE__ */ jsxs11(Text12, { dimColor: true, children: [
28345
+ /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexDirection: "column", children: [
28346
+ /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
28350
28347
  "Repository: ",
28351
28348
  result.owner,
28352
28349
  "/",
28353
28350
  result.name
28354
28351
  ] }),
28355
- /* @__PURE__ */ jsxs11(Text12, { dimColor: true, children: [
28352
+ /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
28356
28353
  "Framework: ",
28357
28354
  plan.framework
28358
28355
  ] }),
28359
- /* @__PURE__ */ jsxs11(Text12, { dimColor: true, children: [
28356
+ /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
28357
+ "Files: ",
28358
+ result.sourcePattern
28359
+ ] }),
28360
+ /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
28360
28361
  "Base locale: ",
28361
- plan.baseLanguage
28362
+ result.baseLanguage
28362
28363
  ] }),
28363
- /* @__PURE__ */ jsxs11(Text12, { dimColor: true, children: [
28364
+ /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
28364
28365
  "Targets:",
28365
28366
  " ",
28366
- plan.targetLanguages.length > 0 ? plan.targetLanguages.join(", ") : "none configured"
28367
+ result.targetLanguages.length > 0 ? result.targetLanguages.join(", ") : "none configured"
28367
28368
  ] }),
28368
- /* @__PURE__ */ jsxs11(Text12, { dimColor: true, children: [
28369
+ /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
28369
28370
  "Project: ",
28370
28371
  result.projectId,
28371
28372
  result.createdProject ? " (created)" : " (existing)"
28372
28373
  ] })
28373
28374
  ] }),
28374
- /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text12, { children: "Wrote tolkbot.json" }) })
28375
+ /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { children: "Wrote tolkbot.json" }) })
28375
28376
  ] });
28376
28377
  }
28377
28378
  async function runHeadless(cwd, opts) {
@@ -28410,8 +28411,9 @@ async function runHeadless(cwd, opts) {
28410
28411
  repositoryId: result.repositoryId,
28411
28412
  repository: `${result.owner}/${result.name}`,
28412
28413
  framework: plan.framework,
28413
- baseLocale: plan.baseLanguage,
28414
- targets: plan.targetLanguages,
28414
+ sourcePattern: result.sourcePattern,
28415
+ baseLocale: result.baseLanguage,
28416
+ targets: result.targetLanguages,
28415
28417
  createdProject: result.createdProject,
28416
28418
  installed: plan.install
28417
28419
  },
@@ -28419,7 +28421,7 @@ async function runHeadless(cwd, opts) {
28419
28421
  );
28420
28422
  return;
28421
28423
  }
28422
- render10(/* @__PURE__ */ jsx12(SummaryUI, { plan, result })).unmount();
28424
+ render9(/* @__PURE__ */ jsx10(SummaryUI, { plan, result })).unmount();
28423
28425
  }
28424
28426
  function canPrompt() {
28425
28427
  return Boolean(process.stdin.isTTY && process.stdout.isTTY);
@@ -28449,8 +28451,8 @@ async function runInit(opts = {}) {
28449
28451
  }
28450
28452
  async function runInteractive(opts) {
28451
28453
  const outcome = { failed: false };
28452
- const { unmount, waitUntilExit } = render10(
28453
- /* @__PURE__ */ jsx12(
28454
+ const { unmount, waitUntilExit } = render9(
28455
+ /* @__PURE__ */ jsx10(
28454
28456
  InitWizard,
28455
28457
  {
28456
28458
  force: opts.force,
@@ -28485,24 +28487,24 @@ import { z as z5 } from "zod/v4";
28485
28487
 
28486
28488
  // src/commands/learnings/archive.tsx
28487
28489
  import { Spinner as Spinner9 } from "@inkjs/ui";
28488
- import { Box as Box13, render as render11, Text as Text13 } from "ink";
28489
- import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
28490
+ import { Box as Box11, render as render10, Text as Text11 } from "ink";
28491
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
28490
28492
  function ArchiveUI({ state }) {
28491
28493
  if (state.type === "archiving") {
28492
- return /* @__PURE__ */ jsxs12(Box13, { children: [
28493
- /* @__PURE__ */ jsx13(Spinner9, {}),
28494
- /* @__PURE__ */ jsx13(Text13, { children: " Archiving learning\u2026" })
28494
+ return /* @__PURE__ */ jsxs11(Box11, { children: [
28495
+ /* @__PURE__ */ jsx11(Spinner9, {}),
28496
+ /* @__PURE__ */ jsx11(Text11, { children: " Archiving learning\u2026" })
28495
28497
  ] });
28496
28498
  }
28497
28499
  if (state.type === "success") {
28498
- return /* @__PURE__ */ jsxs12(Box13, { children: [
28499
- /* @__PURE__ */ jsx13(Text13, { color: "green", children: "\u2713" }),
28500
- /* @__PURE__ */ jsx13(Text13, { children: " Learning archived" })
28500
+ return /* @__PURE__ */ jsxs11(Box11, { children: [
28501
+ /* @__PURE__ */ jsx11(Text11, { color: "green", children: "\u2713" }),
28502
+ /* @__PURE__ */ jsx11(Text11, { children: " Learning archived" })
28501
28503
  ] });
28502
28504
  }
28503
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
28504
- /* @__PURE__ */ jsx13(Text13, { color: "red", children: "\u2717 Failed to archive learning" }),
28505
- /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: state.message })
28505
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
28506
+ /* @__PURE__ */ jsx11(Text11, { color: "red", children: "\u2717 Failed to archive learning" }),
28507
+ /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: state.message })
28506
28508
  ] });
28507
28509
  }
28508
28510
  async function runLearningsArchive(opts) {
@@ -28519,7 +28521,7 @@ async function runLearningsArchive(opts) {
28519
28521
  jsonError(error62 instanceof Error ? error62.message : String(error62));
28520
28522
  }
28521
28523
  }
28522
- let { unmount } = render11(/* @__PURE__ */ jsx13(ArchiveUI, { state: { type: "archiving" } }));
28524
+ let { unmount } = render10(/* @__PURE__ */ jsx11(ArchiveUI, { state: { type: "archiving" } }));
28523
28525
  try {
28524
28526
  const convexClient = await getAuthenticatedConvexClient();
28525
28527
  const { organizationId: organizationId2 } = await getProjectContext(convexClient);
@@ -28528,13 +28530,13 @@ async function runLearningsArchive(opts) {
28528
28530
  learningId: opts.id
28529
28531
  });
28530
28532
  unmount();
28531
- const ui = render11(/* @__PURE__ */ jsx13(ArchiveUI, { state: { type: "success" } }));
28533
+ const ui = render10(/* @__PURE__ */ jsx11(ArchiveUI, { state: { type: "success" } }));
28532
28534
  unmount = ui.unmount;
28533
28535
  await ui.waitUntilExit();
28534
28536
  } catch (error62) {
28535
28537
  unmount();
28536
- const ui = render11(
28537
- /* @__PURE__ */ jsx13(
28538
+ const ui = render10(
28539
+ /* @__PURE__ */ jsx11(
28538
28540
  ArchiveUI,
28539
28541
  {
28540
28542
  state: {
@@ -28551,8 +28553,8 @@ async function runLearningsArchive(opts) {
28551
28553
 
28552
28554
  // src/commands/learnings/list.tsx
28553
28555
  import { Spinner as Spinner10 } from "@inkjs/ui";
28554
- import { Box as Box14, render as render12, Text as Text14 } from "ink";
28555
- import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
28556
+ import { Box as Box12, render as render11, Text as Text12 } from "ink";
28557
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
28556
28558
  var feedbackLabels = {
28557
28559
  "incorrect-term": "Incorrect term",
28558
28560
  "wrong-tone": "Wrong tone",
@@ -28562,53 +28564,53 @@ var feedbackLabels = {
28562
28564
  };
28563
28565
  function LearningsListUI({ state }) {
28564
28566
  if (state.type === "loading") {
28565
- return /* @__PURE__ */ jsxs13(Box14, { children: [
28566
- /* @__PURE__ */ jsx14(Spinner10, {}),
28567
- /* @__PURE__ */ jsx14(Text14, { children: " Loading learnings\u2026" })
28567
+ return /* @__PURE__ */ jsxs12(Box12, { children: [
28568
+ /* @__PURE__ */ jsx12(Spinner10, {}),
28569
+ /* @__PURE__ */ jsx12(Text12, { children: " Loading learnings\u2026" })
28568
28570
  ] });
28569
28571
  }
28570
28572
  if (state.type === "error") {
28571
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
28572
- /* @__PURE__ */ jsx14(Text14, { color: "red", children: "\u2717 Error" }),
28573
- /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: state.message })
28573
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
28574
+ /* @__PURE__ */ jsx12(Text12, { color: "red", children: "\u2717 Error" }),
28575
+ /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: state.message })
28574
28576
  ] });
28575
28577
  }
28576
28578
  const { learnings } = state;
28577
28579
  if (learnings.length === 0) {
28578
- return /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: "No learnings found." }) });
28580
+ return /* @__PURE__ */ jsx12(Box12, { children: /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "No learnings found." }) });
28579
28581
  }
28580
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
28581
- /* @__PURE__ */ jsx14(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsxs13(Text14, { bold: true, children: [
28582
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
28583
+ /* @__PURE__ */ jsx12(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsxs12(Text12, { bold: true, children: [
28582
28584
  learnings.length,
28583
28585
  " learning",
28584
28586
  learnings.length === 1 ? "" : "s"
28585
28587
  ] }) }),
28586
- learnings.map((l) => /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", marginBottom: 1, children: [
28587
- /* @__PURE__ */ jsxs13(Box14, { gap: 1, children: [
28588
- /* @__PURE__ */ jsxs13(Text14, { bold: true, children: [
28588
+ learnings.map((l) => /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", marginBottom: 1, children: [
28589
+ /* @__PURE__ */ jsxs12(Box12, { gap: 1, children: [
28590
+ /* @__PURE__ */ jsxs12(Text12, { bold: true, children: [
28589
28591
  l.srcLocale,
28590
28592
  " \u2192 ",
28591
28593
  l.dstLocale
28592
28594
  ] }),
28593
- /* @__PURE__ */ jsxs13(Text14, { dimColor: true, children: [
28595
+ /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
28594
28596
  "[",
28595
28597
  feedbackLabels[l.feedbackType] ?? l.feedbackType,
28596
28598
  "]"
28597
28599
  ] }),
28598
- l.status === "archived" && /* @__PURE__ */ jsx14(Text14, { color: "yellow", children: "[archived]" })
28600
+ l.status === "archived" && /* @__PURE__ */ jsx12(Text12, { color: "yellow", children: "[archived]" })
28599
28601
  ] }),
28600
- /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsxs13(Text14, { children: [
28602
+ /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, children: /* @__PURE__ */ jsxs12(Text12, { children: [
28601
28603
  '"',
28602
28604
  l.sourceText,
28603
28605
  '" \u2192 "',
28604
28606
  l.suggestedTranslation,
28605
28607
  '"'
28606
28608
  ] }) }),
28607
- l.reasoning && /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsxs13(Text14, { dimColor: true, children: [
28609
+ l.reasoning && /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, children: /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
28608
28610
  "Reason: ",
28609
28611
  l.reasoning
28610
28612
  ] }) }),
28611
- /* @__PURE__ */ jsx14(Box14, { paddingLeft: 2, children: /* @__PURE__ */ jsxs13(Text14, { dimColor: true, children: [
28613
+ /* @__PURE__ */ jsx12(Box12, { paddingLeft: 2, children: /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
28612
28614
  "ID: ",
28613
28615
  l._id
28614
28616
  ] }) })
@@ -28646,7 +28648,7 @@ async function runLearningsList(opts) {
28646
28648
  jsonError(error62 instanceof Error ? error62.message : String(error62));
28647
28649
  }
28648
28650
  }
28649
- let { unmount } = render12(/* @__PURE__ */ jsx14(LearningsListUI, { state: { type: "loading" } }));
28651
+ let { unmount } = render11(/* @__PURE__ */ jsx12(LearningsListUI, { state: { type: "loading" } }));
28650
28652
  try {
28651
28653
  const convexClient = await getAuthenticatedConvexClient();
28652
28654
  const { projectId, organizationId: organizationId2 } = await getProjectContext(convexClient);
@@ -28660,15 +28662,15 @@ async function runLearningsList(opts) {
28660
28662
  }
28661
28663
  );
28662
28664
  unmount();
28663
- const ui = render12(
28664
- /* @__PURE__ */ jsx14(LearningsListUI, { state: { type: "success", learnings } })
28665
+ const ui = render11(
28666
+ /* @__PURE__ */ jsx12(LearningsListUI, { state: { type: "success", learnings } })
28665
28667
  );
28666
28668
  unmount = ui.unmount;
28667
28669
  await ui.waitUntilExit();
28668
28670
  } catch (error62) {
28669
28671
  unmount();
28670
- const ui = render12(
28671
- /* @__PURE__ */ jsx14(
28672
+ const ui = render11(
28673
+ /* @__PURE__ */ jsx12(
28672
28674
  LearningsListUI,
28673
28675
  {
28674
28676
  state: {
@@ -28685,24 +28687,24 @@ async function runLearningsList(opts) {
28685
28687
 
28686
28688
  // src/commands/learnings/remove.tsx
28687
28689
  import { Spinner as Spinner11 } from "@inkjs/ui";
28688
- import { Box as Box15, render as render13, Text as Text15 } from "ink";
28689
- import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
28690
+ import { Box as Box13, render as render12, Text as Text13 } from "ink";
28691
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
28690
28692
  function RemoveUI2({ state }) {
28691
28693
  if (state.type === "removing") {
28692
- return /* @__PURE__ */ jsxs14(Box15, { children: [
28693
- /* @__PURE__ */ jsx15(Spinner11, {}),
28694
- /* @__PURE__ */ jsx15(Text15, { children: " Removing learning\u2026" })
28694
+ return /* @__PURE__ */ jsxs13(Box13, { children: [
28695
+ /* @__PURE__ */ jsx13(Spinner11, {}),
28696
+ /* @__PURE__ */ jsx13(Text13, { children: " Removing learning\u2026" })
28695
28697
  ] });
28696
28698
  }
28697
28699
  if (state.type === "success") {
28698
- return /* @__PURE__ */ jsxs14(Box15, { children: [
28699
- /* @__PURE__ */ jsx15(Text15, { color: "green", children: "\u2713" }),
28700
- /* @__PURE__ */ jsx15(Text15, { children: " Learning removed" })
28700
+ return /* @__PURE__ */ jsxs13(Box13, { children: [
28701
+ /* @__PURE__ */ jsx13(Text13, { color: "green", children: "\u2713" }),
28702
+ /* @__PURE__ */ jsx13(Text13, { children: " Learning removed" })
28701
28703
  ] });
28702
28704
  }
28703
- return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
28704
- /* @__PURE__ */ jsx15(Text15, { color: "red", children: "\u2717 Failed to remove learning" }),
28705
- /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: state.message })
28705
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
28706
+ /* @__PURE__ */ jsx13(Text13, { color: "red", children: "\u2717 Failed to remove learning" }),
28707
+ /* @__PURE__ */ jsx13(Text13, { dimColor: true, children: state.message })
28706
28708
  ] });
28707
28709
  }
28708
28710
  async function runLearningsRemove(opts) {
@@ -28719,7 +28721,7 @@ async function runLearningsRemove(opts) {
28719
28721
  jsonError(error62 instanceof Error ? error62.message : String(error62));
28720
28722
  }
28721
28723
  }
28722
- let { unmount } = render13(/* @__PURE__ */ jsx15(RemoveUI2, { state: { type: "removing" } }));
28724
+ let { unmount } = render12(/* @__PURE__ */ jsx13(RemoveUI2, { state: { type: "removing" } }));
28723
28725
  try {
28724
28726
  const convexClient = await getAuthenticatedConvexClient();
28725
28727
  const { organizationId: organizationId2 } = await getProjectContext(convexClient);
@@ -28728,13 +28730,13 @@ async function runLearningsRemove(opts) {
28728
28730
  learningId: opts.id
28729
28731
  });
28730
28732
  unmount();
28731
- const ui = render13(/* @__PURE__ */ jsx15(RemoveUI2, { state: { type: "success" } }));
28733
+ const ui = render12(/* @__PURE__ */ jsx13(RemoveUI2, { state: { type: "success" } }));
28732
28734
  unmount = ui.unmount;
28733
28735
  await ui.waitUntilExit();
28734
28736
  } catch (error62) {
28735
28737
  unmount();
28736
- const ui = render13(
28737
- /* @__PURE__ */ jsx15(
28738
+ const ui = render12(
28739
+ /* @__PURE__ */ jsx13(
28738
28740
  RemoveUI2,
28739
28741
  {
28740
28742
  state: {
@@ -28751,59 +28753,59 @@ async function runLearningsRemove(opts) {
28751
28753
 
28752
28754
  // src/commands/learnings/show.tsx
28753
28755
  import { Spinner as Spinner12 } from "@inkjs/ui";
28754
- import { Box as Box16, render as render14, Text as Text16 } from "ink";
28755
- import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
28756
+ import { Box as Box14, render as render13, Text as Text14 } from "ink";
28757
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
28756
28758
  function LearningShowUI({ state }) {
28757
28759
  if (state.type === "loading") {
28758
- return /* @__PURE__ */ jsxs15(Box16, { children: [
28759
- /* @__PURE__ */ jsx16(Spinner12, {}),
28760
- /* @__PURE__ */ jsx16(Text16, { children: " Loading learning\u2026" })
28760
+ return /* @__PURE__ */ jsxs14(Box14, { children: [
28761
+ /* @__PURE__ */ jsx14(Spinner12, {}),
28762
+ /* @__PURE__ */ jsx14(Text14, { children: " Loading learning\u2026" })
28761
28763
  ] });
28762
28764
  }
28763
28765
  if (state.type === "not-found") {
28764
- return /* @__PURE__ */ jsx16(Box16, { children: /* @__PURE__ */ jsx16(Text16, { color: "red", children: "\u2717 Learning not found" }) });
28766
+ return /* @__PURE__ */ jsx14(Box14, { children: /* @__PURE__ */ jsx14(Text14, { color: "red", children: "\u2717 Learning not found" }) });
28765
28767
  }
28766
28768
  if (state.type === "error") {
28767
- return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", children: [
28768
- /* @__PURE__ */ jsx16(Text16, { color: "red", children: "\u2717 Error" }),
28769
- /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: state.message })
28769
+ return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
28770
+ /* @__PURE__ */ jsx14(Text14, { color: "red", children: "\u2717 Error" }),
28771
+ /* @__PURE__ */ jsx14(Text14, { dimColor: true, children: state.message })
28770
28772
  ] });
28771
28773
  }
28772
28774
  const { learning } = state;
28773
- return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", children: [
28774
- /* @__PURE__ */ jsxs15(Box16, { gap: 1, children: [
28775
- /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Source text:" }),
28776
- /* @__PURE__ */ jsx16(Text16, { children: learning.sourceText })
28775
+ return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
28776
+ /* @__PURE__ */ jsxs14(Box14, { gap: 1, children: [
28777
+ /* @__PURE__ */ jsx14(Text14, { bold: true, children: "Source text:" }),
28778
+ /* @__PURE__ */ jsx14(Text14, { children: learning.sourceText })
28777
28779
  ] }),
28778
- /* @__PURE__ */ jsxs15(Box16, { gap: 1, children: [
28779
- /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Suggested translation:" }),
28780
- /* @__PURE__ */ jsx16(Text16, { children: learning.suggestedTranslation })
28780
+ /* @__PURE__ */ jsxs14(Box14, { gap: 1, children: [
28781
+ /* @__PURE__ */ jsx14(Text14, { bold: true, children: "Suggested translation:" }),
28782
+ /* @__PURE__ */ jsx14(Text14, { children: learning.suggestedTranslation })
28781
28783
  ] }),
28782
- /* @__PURE__ */ jsxs15(Box16, { gap: 1, children: [
28783
- /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Locales:" }),
28784
- /* @__PURE__ */ jsxs15(Text16, { children: [
28784
+ /* @__PURE__ */ jsxs14(Box14, { gap: 1, children: [
28785
+ /* @__PURE__ */ jsx14(Text14, { bold: true, children: "Locales:" }),
28786
+ /* @__PURE__ */ jsxs14(Text14, { children: [
28785
28787
  learning.srcLocale,
28786
28788
  " \u2192 ",
28787
28789
  learning.dstLocale
28788
28790
  ] })
28789
28791
  ] }),
28790
- /* @__PURE__ */ jsxs15(Box16, { gap: 1, children: [
28791
- /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Feedback type:" }),
28792
- /* @__PURE__ */ jsx16(Text16, { children: learning.feedbackType })
28792
+ /* @__PURE__ */ jsxs14(Box14, { gap: 1, children: [
28793
+ /* @__PURE__ */ jsx14(Text14, { bold: true, children: "Feedback type:" }),
28794
+ /* @__PURE__ */ jsx14(Text14, { children: learning.feedbackType })
28793
28795
  ] }),
28794
- /* @__PURE__ */ jsxs15(Box16, { gap: 1, children: [
28795
- /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Reasoning:" }),
28796
- /* @__PURE__ */ jsx16(Text16, { children: learning.reasoning })
28796
+ /* @__PURE__ */ jsxs14(Box14, { gap: 1, children: [
28797
+ /* @__PURE__ */ jsx14(Text14, { bold: true, children: "Reasoning:" }),
28798
+ /* @__PURE__ */ jsx14(Text14, { children: learning.reasoning })
28797
28799
  ] }),
28798
- /* @__PURE__ */ jsxs15(Box16, { gap: 1, children: [
28799
- /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Status:" }),
28800
- /* @__PURE__ */ jsx16(Text16, { color: learning.status === "active" ? "green" : "yellow", children: learning.status })
28800
+ /* @__PURE__ */ jsxs14(Box14, { gap: 1, children: [
28801
+ /* @__PURE__ */ jsx14(Text14, { bold: true, children: "Status:" }),
28802
+ /* @__PURE__ */ jsx14(Text14, { color: learning.status === "active" ? "green" : "yellow", children: learning.status })
28801
28803
  ] }),
28802
- /* @__PURE__ */ jsxs15(Box16, { gap: 1, children: [
28803
- /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Source:" }),
28804
- /* @__PURE__ */ jsx16(Text16, { children: learning.source })
28804
+ /* @__PURE__ */ jsxs14(Box14, { gap: 1, children: [
28805
+ /* @__PURE__ */ jsx14(Text14, { bold: true, children: "Source:" }),
28806
+ /* @__PURE__ */ jsx14(Text14, { children: learning.source })
28805
28807
  ] }),
28806
- /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text16, { dimColor: true, children: [
28808
+ /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text14, { dimColor: true, children: [
28807
28809
  "ID: ",
28808
28810
  learning._id
28809
28811
  ] }) })
@@ -28836,7 +28838,7 @@ async function runLearningsShow(opts) {
28836
28838
  jsonError(error62 instanceof Error ? error62.message : String(error62));
28837
28839
  }
28838
28840
  }
28839
- let { unmount } = render14(/* @__PURE__ */ jsx16(LearningShowUI, { state: { type: "loading" } }));
28841
+ let { unmount } = render13(/* @__PURE__ */ jsx14(LearningShowUI, { state: { type: "loading" } }));
28840
28842
  try {
28841
28843
  const convexClient = await getAuthenticatedConvexClient();
28842
28844
  const { organizationId: organizationId2 } = await getProjectContext(convexClient);
@@ -28846,21 +28848,21 @@ async function runLearningsShow(opts) {
28846
28848
  });
28847
28849
  if (!learning) {
28848
28850
  unmount();
28849
- const ui2 = render14(/* @__PURE__ */ jsx16(LearningShowUI, { state: { type: "not-found" } }));
28851
+ const ui2 = render13(/* @__PURE__ */ jsx14(LearningShowUI, { state: { type: "not-found" } }));
28850
28852
  unmount = ui2.unmount;
28851
28853
  await ui2.waitUntilExit();
28852
28854
  return exitAfterFlush(1);
28853
28855
  }
28854
28856
  unmount();
28855
- const ui = render14(
28856
- /* @__PURE__ */ jsx16(LearningShowUI, { state: { type: "success", learning } })
28857
+ const ui = render13(
28858
+ /* @__PURE__ */ jsx14(LearningShowUI, { state: { type: "success", learning } })
28857
28859
  );
28858
28860
  unmount = ui.unmount;
28859
28861
  await ui.waitUntilExit();
28860
28862
  } catch (error62) {
28861
28863
  unmount();
28862
- const ui = render14(
28863
- /* @__PURE__ */ jsx16(
28864
+ const ui = render13(
28865
+ /* @__PURE__ */ jsx14(
28864
28866
  LearningShowUI,
28865
28867
  {
28866
28868
  state: {
@@ -28965,7 +28967,7 @@ import { z as z7 } from "zod/v4";
28965
28967
  import fs7 from "fs/promises";
28966
28968
  import path8 from "path";
28967
28969
  import { Spinner as Spinner13 } from "@inkjs/ui";
28968
- import { Box as Box17, render as render15, Text as Text17 } from "ink";
28970
+ import { Box as Box15, render as render14, Text as Text15 } from "ink";
28969
28971
 
28970
28972
  // src/commands/translate/task-generator.ts
28971
28973
  import fs6 from "fs/promises";
@@ -31448,7 +31450,7 @@ async function generateTasks(ctx, srcLocale, dstLocale, opts = {}) {
31448
31450
  }
31449
31451
 
31450
31452
  // src/commands/translate/plan.tsx
31451
- import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
31453
+ import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
31452
31454
  async function loadPlanContext(cwd) {
31453
31455
  const config3 = readConfig(cwd);
31454
31456
  if (!config3) {
@@ -31505,18 +31507,18 @@ function PlanUI({
31505
31507
  dst
31506
31508
  }) {
31507
31509
  if (state.type === "loading") {
31508
- return /* @__PURE__ */ jsxs16(Box17, { children: [
31509
- /* @__PURE__ */ jsx17(Spinner13, {}),
31510
- /* @__PURE__ */ jsx17(Text17, { children: " Loading configuration\u2026" })
31510
+ return /* @__PURE__ */ jsxs15(Box15, { children: [
31511
+ /* @__PURE__ */ jsx15(Spinner13, {}),
31512
+ /* @__PURE__ */ jsx15(Text15, { children: " Loading configuration\u2026" })
31511
31513
  ] });
31512
31514
  }
31513
31515
  if (state.type === "planning") {
31514
- return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", children: [
31515
- /* @__PURE__ */ jsxs16(Box17, { children: [
31516
- /* @__PURE__ */ jsx17(Spinner13, {}),
31517
- /* @__PURE__ */ jsx17(Text17, { children: " Analyzing translation needs\u2026" })
31516
+ return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", children: [
31517
+ /* @__PURE__ */ jsxs15(Box15, { children: [
31518
+ /* @__PURE__ */ jsx15(Spinner13, {}),
31519
+ /* @__PURE__ */ jsx15(Text15, { children: " Analyzing translation needs\u2026" })
31518
31520
  ] }),
31519
- /* @__PURE__ */ jsx17(Box17, { marginTop: 1, children: /* @__PURE__ */ jsxs16(Text17, { dimColor: true, children: [
31521
+ /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text15, { dimColor: true, children: [
31520
31522
  src,
31521
31523
  " \u2192 ",
31522
31524
  dst
@@ -31525,12 +31527,12 @@ function PlanUI({
31525
31527
  }
31526
31528
  if (state.type === "success") {
31527
31529
  const { plan } = state;
31528
- return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", children: [
31529
- /* @__PURE__ */ jsxs16(Box17, { children: [
31530
- /* @__PURE__ */ jsx17(Text17, { color: "green", children: "\u2713" }),
31531
- /* @__PURE__ */ jsx17(Text17, { children: " Translation plan generated" })
31530
+ return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", children: [
31531
+ /* @__PURE__ */ jsxs15(Box15, { children: [
31532
+ /* @__PURE__ */ jsx15(Text15, { color: "green", children: "\u2713" }),
31533
+ /* @__PURE__ */ jsx15(Text15, { children: " Translation plan generated" })
31532
31534
  ] }),
31533
- /* @__PURE__ */ jsx17(Box17, { marginTop: 1, children: /* @__PURE__ */ jsxs16(Text17, { dimColor: true, children: [
31535
+ /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text15, { dimColor: true, children: [
31534
31536
  src,
31535
31537
  " \u2192 ",
31536
31538
  dst,
@@ -31538,17 +31540,17 @@ function PlanUI({
31538
31540
  plan.tasks.length,
31539
31541
  " task(s)"
31540
31542
  ] }) }),
31541
- describePlan(plan).map((line) => /* @__PURE__ */ jsx17(Box17, { children: /* @__PURE__ */ jsx17(Text17, { dimColor: true, children: line }) }, line)),
31542
- plan.tasks.length === 0 && /* @__PURE__ */ jsx17(Box17, { marginTop: 1, children: /* @__PURE__ */ jsxs16(Text17, { color: "yellow", children: [
31543
+ describePlan(plan).map((line) => /* @__PURE__ */ jsx15(Box15, { children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: line }) }, line)),
31544
+ plan.tasks.length === 0 && /* @__PURE__ */ jsx15(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text15, { color: "yellow", children: [
31543
31545
  "No translations needed - ",
31544
31546
  dst,
31545
31547
  " is up to date."
31546
31548
  ] }) })
31547
31549
  ] });
31548
31550
  }
31549
- return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", children: [
31550
- /* @__PURE__ */ jsx17(Text17, { color: "red", children: "\u2717 Planning failed" }),
31551
- /* @__PURE__ */ jsx17(Text17, { dimColor: true, children: state.message })
31551
+ return /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", children: [
31552
+ /* @__PURE__ */ jsx15(Text15, { color: "red", children: "\u2717 Planning failed" }),
31553
+ /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: state.message })
31552
31554
  ] });
31553
31555
  }
31554
31556
  function planOutput(plan) {
@@ -31582,23 +31584,23 @@ async function runPlan(opts) {
31582
31584
  }
31583
31585
  }
31584
31586
  let src = opts.src ?? "";
31585
- let { unmount } = render15(
31586
- /* @__PURE__ */ jsx17(PlanUI, { state: { type: "loading" }, src, dst: opts.dst })
31587
+ let { unmount } = render14(
31588
+ /* @__PURE__ */ jsx15(PlanUI, { state: { type: "loading" }, src, dst: opts.dst })
31587
31589
  );
31588
31590
  try {
31589
31591
  const ctx = await loadPlanContext(cwd);
31590
31592
  src = opts.src ?? ctx.project.baseLanguage;
31591
31593
  unmount();
31592
- const planningUi = render15(
31593
- /* @__PURE__ */ jsx17(PlanUI, { state: { type: "planning" }, src, dst: opts.dst })
31594
+ const planningUi = render14(
31595
+ /* @__PURE__ */ jsx15(PlanUI, { state: { type: "planning" }, src, dst: opts.dst })
31594
31596
  );
31595
31597
  unmount = planningUi.unmount;
31596
31598
  const plan = await planForLocale(ctx, src, opts.dst, {
31597
31599
  retranslateUntracked: opts.retranslateUntracked
31598
31600
  });
31599
31601
  unmount();
31600
- const successUi = render15(
31601
- /* @__PURE__ */ jsx17(
31602
+ const successUi = render14(
31603
+ /* @__PURE__ */ jsx15(
31602
31604
  PlanUI,
31603
31605
  {
31604
31606
  state: { type: "success", plan },
@@ -31628,8 +31630,8 @@ Tasks written to: ${outputPath}`);
31628
31630
  }
31629
31631
  } catch (error62) {
31630
31632
  unmount();
31631
- const errorUi = render15(
31632
- /* @__PURE__ */ jsx17(
31633
+ const errorUi = render14(
31634
+ /* @__PURE__ */ jsx15(
31633
31635
  PlanUI,
31634
31636
  {
31635
31637
  state: {
@@ -31733,7 +31735,7 @@ async function runAdopt(opts) {
31733
31735
  // src/commands/translate/apply.tsx
31734
31736
  import { useState as useState2 } from "react";
31735
31737
  import { MultiSelect, Spinner as Spinner14 } from "@inkjs/ui";
31736
- import { Box as Box18, render as render16, Text as Text18 } from "ink";
31738
+ import { Box as Box16, render as render15, Text as Text16 } from "ink";
31737
31739
 
31738
31740
  // src/commands/translate/apply-core.ts
31739
31741
  import fs8 from "fs/promises";
@@ -31839,7 +31841,7 @@ async function writeTranslationFile(input2) {
31839
31841
  }
31840
31842
 
31841
31843
  // src/commands/translate/apply.tsx
31842
- import { jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
31844
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
31843
31845
  function ApplyUI({
31844
31846
  jobs,
31845
31847
  state,
@@ -31847,10 +31849,10 @@ function ApplyUI({
31847
31849
  }) {
31848
31850
  const [selectedValues, setSelectedValues] = useState2([]);
31849
31851
  if (state.status === "selecting") {
31850
- return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", children: [
31851
- /* @__PURE__ */ jsx18(Text18, { bold: true, children: "Select translation jobs to apply:" }),
31852
- /* @__PURE__ */ jsx18(Text18, { dimColor: true, children: "Space to select, Enter to confirm, Ctrl+C to cancel" }),
31853
- /* @__PURE__ */ jsx18(Box18, { marginTop: 1, children: /* @__PURE__ */ jsx18(
31852
+ return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
31853
+ /* @__PURE__ */ jsx16(Text16, { bold: true, children: "Select translation jobs to apply:" }),
31854
+ /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: "Space to select, Enter to confirm, Ctrl+C to cancel" }),
31855
+ /* @__PURE__ */ jsx16(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx16(
31854
31856
  MultiSelect,
31855
31857
  {
31856
31858
  options: jobs,
@@ -31861,19 +31863,19 @@ function ApplyUI({
31861
31863
  ] });
31862
31864
  }
31863
31865
  if (state.status === "applying") {
31864
- return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", children: [
31865
- /* @__PURE__ */ jsx18(Spinner14, {}),
31866
- /* @__PURE__ */ jsxs17(Text18, { children: [
31866
+ return /* @__PURE__ */ jsxs16(Box16, { flexDirection: "column", children: [
31867
+ /* @__PURE__ */ jsx16(Spinner14, {}),
31868
+ /* @__PURE__ */ jsxs16(Text16, { children: [
31867
31869
  "Applying job ",
31868
31870
  state.currentJobIndex,
31869
31871
  "/",
31870
31872
  state.totalJobs
31871
31873
  ] }),
31872
- state.currentJob && /* @__PURE__ */ jsx18(Text18, { dimColor: true, children: state.currentJob })
31874
+ state.currentJob && /* @__PURE__ */ jsx16(Text16, { dimColor: true, children: state.currentJob })
31873
31875
  ] });
31874
31876
  }
31875
31877
  if (state.status === "success") {
31876
- return /* @__PURE__ */ jsx18(Box18, { flexDirection: "column", children: /* @__PURE__ */ jsxs17(Text18, { color: "green", children: [
31878
+ return /* @__PURE__ */ jsx16(Box16, { flexDirection: "column", children: /* @__PURE__ */ jsxs16(Text16, { color: "green", children: [
31877
31879
  "\u2713 Applied ",
31878
31880
  state.keys,
31879
31881
  " keys to ",
@@ -31885,7 +31887,7 @@ function ApplyUI({
31885
31887
  state.refused ? `, ${state.refused} not applied` : ""
31886
31888
  ] }) });
31887
31889
  }
31888
- return /* @__PURE__ */ jsx18(Box18, { flexDirection: "column", children: /* @__PURE__ */ jsxs17(Text18, { color: "red", children: [
31890
+ return /* @__PURE__ */ jsx16(Box16, { flexDirection: "column", children: /* @__PURE__ */ jsxs16(Text16, { color: "red", children: [
31889
31891
  "\u2717 Error: ",
31890
31892
  state.message
31891
31893
  ] }) });
@@ -32030,8 +32032,8 @@ async function runApply(opts) {
32030
32032
  }));
32031
32033
  let state = { status: "selecting" };
32032
32034
  let selectedJobIds = [];
32033
- const { rerender, unmount } = render16(
32034
- /* @__PURE__ */ jsx18(
32035
+ const { rerender, unmount } = render15(
32036
+ /* @__PURE__ */ jsx16(
32035
32037
  ApplyUI,
32036
32038
  {
32037
32039
  jobs: jobOptions,
@@ -32045,7 +32047,7 @@ async function runApply(opts) {
32045
32047
  const updateState = (newState) => {
32046
32048
  state = { ...state, ...newState };
32047
32049
  rerender(
32048
- /* @__PURE__ */ jsx18(
32050
+ /* @__PURE__ */ jsx16(
32049
32051
  ApplyUI,
32050
32052
  {
32051
32053
  jobs: jobOptions,
@@ -32121,30 +32123,30 @@ async function runApply(opts) {
32121
32123
 
32122
32124
  // src/commands/translate/cancel.tsx
32123
32125
  import { Spinner as Spinner15 } from "@inkjs/ui";
32124
- import { Box as Box19, render as render17, Text as Text19 } from "ink";
32125
- import { jsx as jsx19, jsxs as jsxs18 } from "react/jsx-runtime";
32126
+ import { Box as Box17, render as render16, Text as Text17 } from "ink";
32127
+ import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
32126
32128
  function CancelUI({ state }) {
32127
32129
  if (state.type === "cancelling") {
32128
- return /* @__PURE__ */ jsxs18(Box19, { children: [
32129
- /* @__PURE__ */ jsx19(Spinner15, {}),
32130
- /* @__PURE__ */ jsx19(Text19, { children: " Cancelling job\u2026" })
32130
+ return /* @__PURE__ */ jsxs17(Box17, { children: [
32131
+ /* @__PURE__ */ jsx17(Spinner15, {}),
32132
+ /* @__PURE__ */ jsx17(Text17, { children: " Cancelling job\u2026" })
32131
32133
  ] });
32132
32134
  }
32133
32135
  if (state.type === "success") {
32134
- return /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", children: [
32135
- /* @__PURE__ */ jsxs18(Box19, { children: [
32136
- /* @__PURE__ */ jsx19(Text19, { color: "green", children: "\u2713" }),
32137
- /* @__PURE__ */ jsx19(Text19, { children: " Job cancelled successfully" })
32136
+ return /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", children: [
32137
+ /* @__PURE__ */ jsxs17(Box17, { children: [
32138
+ /* @__PURE__ */ jsx17(Text17, { color: "green", children: "\u2713" }),
32139
+ /* @__PURE__ */ jsx17(Text17, { children: " Job cancelled successfully" })
32138
32140
  ] }),
32139
- /* @__PURE__ */ jsx19(Box19, { marginTop: 1, children: /* @__PURE__ */ jsxs18(Text19, { dimColor: true, children: [
32141
+ /* @__PURE__ */ jsx17(Box17, { marginTop: 1, children: /* @__PURE__ */ jsxs17(Text17, { dimColor: true, children: [
32140
32142
  "Job ID: ",
32141
32143
  state.jobId
32142
32144
  ] }) })
32143
32145
  ] });
32144
32146
  }
32145
- return /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", children: [
32146
- /* @__PURE__ */ jsx19(Text19, { color: "red", children: "\u2717 Failed to cancel job" }),
32147
- /* @__PURE__ */ jsx19(Text19, { dimColor: true, children: state.message })
32147
+ return /* @__PURE__ */ jsxs17(Box17, { flexDirection: "column", children: [
32148
+ /* @__PURE__ */ jsx17(Text17, { color: "red", children: "\u2717 Failed to cancel job" }),
32149
+ /* @__PURE__ */ jsx17(Text17, { dimColor: true, children: state.message })
32148
32150
  ] });
32149
32151
  }
32150
32152
  async function runCancel(opts) {
@@ -32180,7 +32182,7 @@ async function runCancel(opts) {
32180
32182
  jsonError(error62 instanceof Error ? error62.message : String(error62));
32181
32183
  }
32182
32184
  }
32183
- let { unmount } = render17(/* @__PURE__ */ jsx19(CancelUI, { state: { type: "cancelling" } }));
32185
+ let { unmount } = render16(/* @__PURE__ */ jsx17(CancelUI, { state: { type: "cancelling" } }));
32184
32186
  try {
32185
32187
  if (!opts.jobId) {
32186
32188
  throw new Error("--job-id is required");
@@ -32210,15 +32212,15 @@ async function runCancel(opts) {
32210
32212
  jobId: opts.jobId
32211
32213
  });
32212
32214
  unmount();
32213
- const successUi = render17(
32214
- /* @__PURE__ */ jsx19(CancelUI, { state: { type: "success", jobId: opts.jobId } })
32215
+ const successUi = render16(
32216
+ /* @__PURE__ */ jsx17(CancelUI, { state: { type: "success", jobId: opts.jobId } })
32215
32217
  );
32216
32218
  unmount = successUi.unmount;
32217
32219
  await successUi.waitUntilExit();
32218
32220
  } catch (error62) {
32219
32221
  unmount();
32220
- const errorUi = render17(
32221
- /* @__PURE__ */ jsx19(
32222
+ const errorUi = render16(
32223
+ /* @__PURE__ */ jsx17(
32222
32224
  CancelUI,
32223
32225
  {
32224
32226
  state: {
@@ -32236,7 +32238,7 @@ async function runCancel(opts) {
32236
32238
  // src/commands/translate/edit.tsx
32237
32239
  import path11 from "path";
32238
32240
  import { Spinner as Spinner16 } from "@inkjs/ui";
32239
- import { Box as Box20, render as render18, Text as Text20 } from "ink";
32241
+ import { Box as Box18, render as render17, Text as Text18 } from "ink";
32240
32242
 
32241
32243
  // src/utils/locale-files.ts
32242
32244
  import path10 from "path";
@@ -32274,34 +32276,34 @@ function resolveKeyInFile(key, targetFile, fileCount, namespace) {
32274
32276
  }
32275
32277
 
32276
32278
  // src/commands/translate/edit.tsx
32277
- import { jsx as jsx20, jsxs as jsxs19 } from "react/jsx-runtime";
32279
+ import { jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
32278
32280
  function EditUI({ state }) {
32279
32281
  if (state.type === "editing") {
32280
- return /* @__PURE__ */ jsxs19(Box20, { children: [
32281
- /* @__PURE__ */ jsx20(Spinner16, {}),
32282
- /* @__PURE__ */ jsx20(Text20, { children: " Editing translation\u2026" })
32282
+ return /* @__PURE__ */ jsxs18(Box18, { children: [
32283
+ /* @__PURE__ */ jsx18(Spinner16, {}),
32284
+ /* @__PURE__ */ jsx18(Text18, { children: " Editing translation\u2026" })
32283
32285
  ] });
32284
32286
  }
32285
32287
  if (state.type === "success") {
32286
- return /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", children: [
32287
- /* @__PURE__ */ jsxs19(Box20, { children: [
32288
- /* @__PURE__ */ jsx20(Text20, { color: "green", children: "\u2713" }),
32289
- /* @__PURE__ */ jsxs19(Text20, { children: [
32288
+ return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", children: [
32289
+ /* @__PURE__ */ jsxs18(Box18, { children: [
32290
+ /* @__PURE__ */ jsx18(Text18, { color: "green", children: "\u2713" }),
32291
+ /* @__PURE__ */ jsxs18(Text18, { children: [
32290
32292
  " ",
32291
32293
  "Updated ",
32292
- /* @__PURE__ */ jsx20(Text20, { bold: true, children: state.key })
32294
+ /* @__PURE__ */ jsx18(Text18, { bold: true, children: state.key })
32293
32295
  ] })
32294
32296
  ] }),
32295
- /* @__PURE__ */ jsx20(Box20, { paddingLeft: 2, children: /* @__PURE__ */ jsx20(Text20, { dimColor: true, children: state.value }) }),
32296
- /* @__PURE__ */ jsx20(Box20, { marginTop: 1, children: /* @__PURE__ */ jsxs19(Text20, { dimColor: true, children: [
32297
+ /* @__PURE__ */ jsx18(Box18, { paddingLeft: 2, children: /* @__PURE__ */ jsx18(Text18, { dimColor: true, children: state.value }) }),
32298
+ /* @__PURE__ */ jsx18(Box18, { marginTop: 1, children: /* @__PURE__ */ jsxs18(Text18, { dimColor: true, children: [
32297
32299
  "File: ",
32298
32300
  state.filePath
32299
32301
  ] }) })
32300
32302
  ] });
32301
32303
  }
32302
- return /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", children: [
32303
- /* @__PURE__ */ jsx20(Text20, { color: "red", children: "\u2717 Failed to edit translation" }),
32304
- /* @__PURE__ */ jsx20(Text20, { dimColor: true, children: state.message })
32304
+ return /* @__PURE__ */ jsxs18(Box18, { flexDirection: "column", children: [
32305
+ /* @__PURE__ */ jsx18(Text18, { color: "red", children: "\u2717 Failed to edit translation" }),
32306
+ /* @__PURE__ */ jsx18(Text18, { dimColor: true, children: state.message })
32305
32307
  ] });
32306
32308
  }
32307
32309
  async function performEdit(opts) {
@@ -32360,17 +32362,17 @@ async function runEdit(opts) {
32360
32362
  jsonError(error62 instanceof Error ? error62.message : String(error62));
32361
32363
  }
32362
32364
  }
32363
- let { unmount } = render18(/* @__PURE__ */ jsx20(EditUI, { state: { type: "editing" } }));
32365
+ let { unmount } = render17(/* @__PURE__ */ jsx18(EditUI, { state: { type: "editing" } }));
32364
32366
  try {
32365
32367
  const result = await performEdit(opts);
32366
32368
  unmount();
32367
- const ui = render18(/* @__PURE__ */ jsx20(EditUI, { state: { type: "success", ...result } }));
32369
+ const ui = render17(/* @__PURE__ */ jsx18(EditUI, { state: { type: "success", ...result } }));
32368
32370
  unmount = ui.unmount;
32369
32371
  await ui.waitUntilExit();
32370
32372
  } catch (error62) {
32371
32373
  unmount();
32372
- const ui = render18(
32373
- /* @__PURE__ */ jsx20(
32374
+ const ui = render17(
32375
+ /* @__PURE__ */ jsx18(
32374
32376
  EditUI,
32375
32377
  {
32376
32378
  state: {
@@ -32386,30 +32388,30 @@ async function runEdit(opts) {
32386
32388
  }
32387
32389
 
32388
32390
  // src/commands/translate/run.tsx
32389
- import { render as render19 } from "ink";
32391
+ import { render as render18 } from "ink";
32390
32392
 
32391
32393
  // src/commands/translate/run-ui.tsx
32392
32394
  import { Spinner as Spinner17 } from "@inkjs/ui";
32393
- import { Box as Box21, Text as Text21 } from "ink";
32394
- import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
32395
+ import { Box as Box19, Text as Text19 } from "ink";
32396
+ import { jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
32395
32397
  function JobLink({ jobId }) {
32396
32398
  const url2 = `${env.TOLKBOT_WEB_URL}/translation-jobs/${jobId}`;
32397
- return /* @__PURE__ */ jsxs20(Text21, { dimColor: true, children: [
32399
+ return /* @__PURE__ */ jsxs19(Text19, { dimColor: true, children: [
32398
32400
  "Job:",
32399
32401
  " ",
32400
- /* @__PURE__ */ jsx21(Text21, { color: "blue", underline: true, children: url2 })
32402
+ /* @__PURE__ */ jsx19(Text19, { color: "blue", underline: true, children: url2 })
32401
32403
  ] });
32402
32404
  }
32403
32405
  function PlanNotes({ notes }) {
32404
32406
  if (!notes || notes.length === 0) return null;
32405
- return /* @__PURE__ */ jsx21(Box21, { flexDirection: "column", paddingLeft: 2, children: notes.map((note) => /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: note }, note)) });
32407
+ return /* @__PURE__ */ jsx19(Box19, { flexDirection: "column", paddingLeft: 2, children: notes.map((note) => /* @__PURE__ */ jsx19(Text19, { dimColor: true, children: note }, note)) });
32406
32408
  }
32407
32409
  function JobRow({ srcLocale, job }) {
32408
32410
  const { dstLocale, status, taskCount, progress, jobId, error: error62, notes } = job;
32409
32411
  if (status === "waiting") {
32410
- return /* @__PURE__ */ jsxs20(Box21, { children: [
32411
- /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: "\u23F8 " }),
32412
- /* @__PURE__ */ jsxs20(Text21, { dimColor: true, children: [
32412
+ return /* @__PURE__ */ jsxs19(Box19, { children: [
32413
+ /* @__PURE__ */ jsx19(Text19, { dimColor: true, children: "\u23F8 " }),
32414
+ /* @__PURE__ */ jsxs19(Text19, { dimColor: true, children: [
32413
32415
  srcLocale,
32414
32416
  " \u2192 ",
32415
32417
  dstLocale,
@@ -32418,9 +32420,9 @@ function JobRow({ srcLocale, job }) {
32418
32420
  ] });
32419
32421
  }
32420
32422
  if (status === "generating") {
32421
- return /* @__PURE__ */ jsxs20(Box21, { children: [
32422
- /* @__PURE__ */ jsx21(Spinner17, {}),
32423
- /* @__PURE__ */ jsxs20(Text21, { children: [
32423
+ return /* @__PURE__ */ jsxs19(Box19, { children: [
32424
+ /* @__PURE__ */ jsx19(Spinner17, {}),
32425
+ /* @__PURE__ */ jsxs19(Text19, { children: [
32424
32426
  " ",
32425
32427
  srcLocale,
32426
32428
  " \u2192 ",
@@ -32432,10 +32434,10 @@ function JobRow({ srcLocale, job }) {
32432
32434
  if (status === "translating" && progress) {
32433
32435
  const processed = progress.completed + progress.failed;
32434
32436
  const percent = progress.total > 0 ? Math.round(processed / progress.total * 100) : 0;
32435
- return /* @__PURE__ */ jsxs20(Box21, { flexDirection: "column", children: [
32436
- /* @__PURE__ */ jsxs20(Box21, { children: [
32437
- /* @__PURE__ */ jsx21(Spinner17, {}),
32438
- /* @__PURE__ */ jsxs20(Text21, { children: [
32437
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", children: [
32438
+ /* @__PURE__ */ jsxs19(Box19, { children: [
32439
+ /* @__PURE__ */ jsx19(Spinner17, {}),
32440
+ /* @__PURE__ */ jsxs19(Text19, { children: [
32439
32441
  " ",
32440
32442
  srcLocale,
32441
32443
  " \u2192 ",
@@ -32449,16 +32451,16 @@ function JobRow({ srcLocale, job }) {
32449
32451
  "%)"
32450
32452
  ] })
32451
32453
  ] }),
32452
- jobId && /* @__PURE__ */ jsx21(Box21, { paddingLeft: 2, children: /* @__PURE__ */ jsx21(JobLink, { jobId }) })
32454
+ jobId && /* @__PURE__ */ jsx19(Box19, { paddingLeft: 2, children: /* @__PURE__ */ jsx19(JobLink, { jobId }) })
32453
32455
  ] });
32454
32456
  }
32455
32457
  if (status === "completed" && progress) {
32456
32458
  const icon = progress.status === "succeeded" ? "\u2713" : progress.status === "partial" ? "\u26A0" : "\u2717";
32457
32459
  const color = progress.status === "succeeded" ? "green" : progress.status === "partial" ? "yellow" : "red";
32458
- return /* @__PURE__ */ jsxs20(Box21, { flexDirection: "column", children: [
32459
- /* @__PURE__ */ jsxs20(Box21, { children: [
32460
- /* @__PURE__ */ jsx21(Text21, { color, children: icon }),
32461
- /* @__PURE__ */ jsxs20(Text21, { children: [
32460
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", children: [
32461
+ /* @__PURE__ */ jsxs19(Box19, { children: [
32462
+ /* @__PURE__ */ jsx19(Text19, { color, children: icon }),
32463
+ /* @__PURE__ */ jsxs19(Text19, { children: [
32462
32464
  " ",
32463
32465
  srcLocale,
32464
32466
  " \u2192 ",
@@ -32471,36 +32473,37 @@ function JobRow({ srcLocale, job }) {
32471
32473
  progress.failed > 0 && ` (${progress.failed} failed)`
32472
32474
  ] })
32473
32475
  ] }),
32474
- jobId && /* @__PURE__ */ jsx21(Box21, { paddingLeft: 2, children: /* @__PURE__ */ jsx21(JobLink, { jobId }) }),
32475
- /* @__PURE__ */ jsx21(PlanNotes, { notes })
32476
+ jobId && /* @__PURE__ */ jsx19(Box19, { paddingLeft: 2, children: /* @__PURE__ */ jsx19(JobLink, { jobId }) }),
32477
+ /* @__PURE__ */ jsx19(PlanNotes, { notes })
32476
32478
  ] });
32477
32479
  }
32478
32480
  if (status === "error") {
32479
- return /* @__PURE__ */ jsxs20(Box21, { flexDirection: "column", children: [
32480
- /* @__PURE__ */ jsxs20(Box21, { children: [
32481
- /* @__PURE__ */ jsx21(Text21, { color: "red", children: "\u2717 " }),
32482
- /* @__PURE__ */ jsxs20(Text21, { children: [
32481
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", children: [
32482
+ /* @__PURE__ */ jsxs19(Box19, { children: [
32483
+ /* @__PURE__ */ jsx19(Text19, { color: "red", children: "\u2717 " }),
32484
+ /* @__PURE__ */ jsxs19(Text19, { children: [
32483
32485
  srcLocale,
32484
32486
  " \u2192 ",
32485
32487
  dstLocale,
32486
32488
  ": Failed"
32487
32489
  ] })
32488
32490
  ] }),
32489
- error62 && /* @__PURE__ */ jsx21(Box21, { paddingLeft: 2, children: /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: error62 }) })
32491
+ error62 && /* @__PURE__ */ jsx19(Box19, { paddingLeft: 2, children: /* @__PURE__ */ jsx19(Text19, { dimColor: true, children: error62 }) }),
32492
+ jobId && /* @__PURE__ */ jsx19(Box19, { paddingLeft: 2, children: /* @__PURE__ */ jsx19(JobLink, { jobId }) })
32490
32493
  ] });
32491
32494
  }
32492
32495
  if (status === "completed" && !progress && taskCount === 0) {
32493
- return /* @__PURE__ */ jsxs20(Box21, { flexDirection: "column", children: [
32494
- /* @__PURE__ */ jsxs20(Box21, { children: [
32495
- /* @__PURE__ */ jsx21(Text21, { color: "green", children: "\u2713 " }),
32496
- /* @__PURE__ */ jsxs20(Text21, { children: [
32496
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", children: [
32497
+ /* @__PURE__ */ jsxs19(Box19, { children: [
32498
+ /* @__PURE__ */ jsx19(Text19, { color: "green", children: "\u2713 " }),
32499
+ /* @__PURE__ */ jsxs19(Text19, { children: [
32497
32500
  srcLocale,
32498
32501
  " \u2192 ",
32499
32502
  dstLocale,
32500
32503
  ": Up to date (no translations needed)"
32501
32504
  ] })
32502
32505
  ] }),
32503
- /* @__PURE__ */ jsx21(PlanNotes, { notes })
32506
+ /* @__PURE__ */ jsx19(PlanNotes, { notes })
32504
32507
  ] });
32505
32508
  }
32506
32509
  return null;
@@ -32510,8 +32513,8 @@ function RunUI({ srcLocale, jobs }) {
32510
32513
  const allCompleted = jobs.every(
32511
32514
  (j) => j.status === "completed" || j.status === "error"
32512
32515
  );
32513
- return /* @__PURE__ */ jsxs20(Box21, { flexDirection: "column", children: [
32514
- hasMultiple && /* @__PURE__ */ jsx21(Box21, { marginBottom: 1, children: /* @__PURE__ */ jsxs20(Text21, { bold: true, children: [
32516
+ return /* @__PURE__ */ jsxs19(Box19, { flexDirection: "column", children: [
32517
+ hasMultiple && /* @__PURE__ */ jsx19(Box19, { marginBottom: 1, children: /* @__PURE__ */ jsxs19(Text19, { bold: true, children: [
32515
32518
  "\u{1F30D} Translating from ",
32516
32519
  srcLocale,
32517
32520
  " to ",
@@ -32519,14 +32522,14 @@ function RunUI({ srcLocale, jobs }) {
32519
32522
  " ",
32520
32523
  "languages"
32521
32524
  ] }) }),
32522
- jobs.map((job) => /* @__PURE__ */ jsx21(JobRow, { srcLocale, job }, job.dstLocale)),
32523
- hasMultiple && allCompleted && /* @__PURE__ */ jsxs20(Box21, { marginTop: 1, flexDirection: "column", children: [
32524
- /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: "\u2500".repeat(60) }),
32525
- /* @__PURE__ */ jsx21(Box21, { children: /* @__PURE__ */ jsx21(Text21, { bold: true, children: "\u{1F4CA} Summary" }) }),
32525
+ jobs.map((job) => /* @__PURE__ */ jsx19(JobRow, { srcLocale, job }, job.dstLocale)),
32526
+ hasMultiple && allCompleted && /* @__PURE__ */ jsxs19(Box19, { marginTop: 1, flexDirection: "column", children: [
32527
+ /* @__PURE__ */ jsx19(Text19, { dimColor: true, children: "\u2500".repeat(60) }),
32528
+ /* @__PURE__ */ jsx19(Box19, { children: /* @__PURE__ */ jsx19(Text19, { bold: true, children: "\u{1F4CA} Summary" }) }),
32526
32529
  jobs.map((job) => {
32527
32530
  if (!job.progress) return null;
32528
32531
  const icon = job.progress.status === "succeeded" ? "\u2713" : job.progress.status === "partial" ? "\u26A0" : "\u2717";
32529
- return /* @__PURE__ */ jsx21(Box21, { children: /* @__PURE__ */ jsxs20(Text21, { children: [
32532
+ return /* @__PURE__ */ jsx19(Box19, { children: /* @__PURE__ */ jsxs19(Text19, { children: [
32530
32533
  icon,
32531
32534
  " ",
32532
32535
  srcLocale,
@@ -32540,14 +32543,55 @@ function RunUI({ srcLocale, jobs }) {
32540
32543
  job.progress.failed > 0 && ` (${job.progress.failed} failed)`
32541
32544
  ] }) }, job.dstLocale);
32542
32545
  }),
32543
- /* @__PURE__ */ jsx21(Text21, { dimColor: true, children: "\u2500".repeat(60) })
32546
+ /* @__PURE__ */ jsx19(Text19, { dimColor: true, children: "\u2500".repeat(60) })
32544
32547
  ] })
32545
32548
  ] });
32546
32549
  }
32547
32550
 
32548
32551
  // src/commands/translate/translate-job.ts
32549
- function sleep(ms) {
32550
- return new Promise((resolve) => setTimeout(resolve, ms));
32552
+ var FINISHED_STATUSES = /* @__PURE__ */ new Set([
32553
+ "succeeded",
32554
+ "partial",
32555
+ "failed",
32556
+ "cancelled"
32557
+ ]);
32558
+ function isJobFinished(status) {
32559
+ return FINISHED_STATUSES.has(status);
32560
+ }
32561
+ function isJobUnsuccessful(status) {
32562
+ return status === "failed" || status === "cancelled";
32563
+ }
32564
+ var JOB_WAIT_TIMEOUT_MS = 60 * 6e4;
32565
+ var JOB_POLL_MS = 500;
32566
+ var JobWaitTimeoutError = class extends CliError {
32567
+ constructor(jobId, timeoutMs) {
32568
+ super(
32569
+ `Translation job ${jobId} was still running after ${Math.round(timeoutMs / 6e4)} minutes, so the CLI stopped waiting.`,
32570
+ `The job keeps running. Check it with 'tolkbot translate status --job-id ${jobId}' and apply it with 'tolkbot translate apply --job-id ${jobId}' once it finishes.`
32571
+ );
32572
+ this.jobId = jobId;
32573
+ this.name = "JobWaitTimeoutError";
32574
+ }
32575
+ jobId;
32576
+ };
32577
+ async function waitForJob(jobId, read, opts = {}) {
32578
+ const timeoutMs = opts.timeoutMs ?? JOB_WAIT_TIMEOUT_MS;
32579
+ const pollMs = opts.pollMs ?? JOB_POLL_MS;
32580
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
32581
+ const now = opts.now ?? Date.now;
32582
+ const deadline = now() + timeoutMs;
32583
+ for (; ; ) {
32584
+ const summary = await read();
32585
+ if (!summary) {
32586
+ throw new CliError(`Translation job ${jobId} was not found.`);
32587
+ }
32588
+ opts.onProgress?.(summary);
32589
+ if (isJobFinished(summary.status)) return summary;
32590
+ if (now() >= deadline) {
32591
+ throw new JobWaitTimeoutError(jobId, timeoutMs);
32592
+ }
32593
+ await sleep(pollMs);
32594
+ }
32551
32595
  }
32552
32596
  async function runTranslationJob(params) {
32553
32597
  const {
@@ -32575,35 +32619,20 @@ async function runTranslationJob(params) {
32575
32619
  context: t.context
32576
32620
  }))
32577
32621
  });
32578
- let summary = null;
32579
- while (!summary) {
32580
- const result = await convexClient.query(
32581
- api.translations.getJobSummary,
32582
- {
32583
- organizationId: organizationId2,
32584
- jobId
32585
- }
32586
- );
32587
- if (!result) {
32588
- throw new Error("Job not found");
32589
- }
32590
- if (onProgress) {
32591
- onProgress(result);
32592
- }
32593
- if (result.status === "succeeded" || result.status === "partial" || result.status === "failed") {
32594
- summary = result;
32595
- break;
32596
- }
32597
- await sleep(500);
32598
- }
32622
+ const summary = await waitForJob(
32623
+ jobId,
32624
+ () => convexClient.query(api.translations.getJobSummary, {
32625
+ organizationId: organizationId2,
32626
+ jobId
32627
+ }),
32628
+ { onProgress }
32629
+ );
32599
32630
  return { jobId, summary };
32600
32631
  }
32601
32632
 
32602
32633
  // src/commands/translate/run.tsx
32603
- import { jsx as jsx22 } from "react/jsx-runtime";
32634
+ import { jsx as jsx20 } from "react/jsx-runtime";
32604
32635
  async function runTranslate(opts) {
32605
- const batchSize = opts.batchSize ?? 10;
32606
- const concurrency = opts.concurrency ?? 3;
32607
32636
  const ctx = await loadPlanContext(process.cwd());
32608
32637
  const { project, framework } = ctx;
32609
32638
  const projectId = project._id;
@@ -32626,8 +32655,8 @@ ${env.TOLKBOT_WEB_URL}/projects/${project.slug}/settings`
32626
32655
  const planOpts = { retranslateUntracked: opts.retranslateUntracked };
32627
32656
  const tasksFor = async (dstLocale) => opts.tasks ? { tasks: await loadTasksFromFile(opts.tasks), plan: void 0 } : await generateTasks(ctx, srcLocale, dstLocale, planOpts);
32628
32657
  if (opts.json) {
32658
+ const results = [];
32629
32659
  try {
32630
- const results = [];
32631
32660
  for (const dstLocale of dstLocales) {
32632
32661
  const { tasks, plan } = await tasksFor(dstLocale);
32633
32662
  const summary = plan ? summarize(plan) : void 0;
@@ -32639,28 +32668,15 @@ ${env.TOLKBOT_WEB_URL}/projects/${project.slug}/settings`
32639
32668
  });
32640
32669
  continue;
32641
32670
  }
32642
- const { jobId, summary: job } = await runTranslationJob({
32643
- projectId,
32644
- organizationId: project.organizationId,
32645
- framework,
32646
- srcLocale,
32647
- dstLocale,
32648
- tasks,
32649
- batchSize,
32650
- concurrency,
32651
- // eslint-disable-next-line @typescript-eslint/no-empty-function
32652
- onProgress: () => {
32653
- }
32654
- });
32655
- if (!job) {
32656
- results.push({
32671
+ try {
32672
+ const { jobId, summary: job } = await runTranslationJob({
32673
+ projectId,
32674
+ organizationId: project.organizationId,
32675
+ framework,
32676
+ srcLocale,
32657
32677
  dstLocale,
32658
- jobId,
32659
- status: "error",
32660
- error: "Job not found",
32661
- plan: summary
32678
+ tasks
32662
32679
  });
32663
- } else {
32664
32680
  results.push({
32665
32681
  dstLocale,
32666
32682
  jobId,
@@ -32668,28 +32684,45 @@ ${env.TOLKBOT_WEB_URL}/projects/${project.slug}/settings`
32668
32684
  completed: job.completed,
32669
32685
  failed: job.failed,
32670
32686
  total: job.total,
32687
+ error: job.status === "cancelled" ? "The job was cancelled." : void 0,
32688
+ plan: summary
32689
+ });
32690
+ } catch (error62) {
32691
+ if (!(error62 instanceof JobWaitTimeoutError)) throw error62;
32692
+ results.push({
32693
+ dstLocale,
32694
+ jobId: error62.jobId,
32695
+ status: "timed-out",
32696
+ error: error62.message,
32671
32697
  plan: summary
32672
32698
  });
32699
+ break;
32673
32700
  }
32701
+ if (results.at(-1)?.status === "cancelled") break;
32674
32702
  }
32675
- track("cli_translate_completed", {
32676
- mode: "run",
32677
- key_count: results.reduce((n, r) => n + (r.total ?? 0), 0),
32678
- locale_count: results.length,
32679
- job_count: results.filter((r) => r.jobId).length,
32680
- json: true
32681
- });
32682
- jsonSuccess({ jobs: results });
32683
32703
  } catch (error62) {
32684
32704
  jsonError(error62 instanceof Error ? error62.message : String(error62));
32685
32705
  }
32706
+ track("cli_translate_completed", {
32707
+ mode: "run",
32708
+ key_count: results.reduce((n, r) => n + (r.total ?? 0), 0),
32709
+ locale_count: results.length,
32710
+ job_count: results.filter((r) => r.jobId).length,
32711
+ json: true
32712
+ });
32713
+ const ok = results.every(
32714
+ (r) => r.status !== "timed-out" && !isJobUnsuccessful(r.status)
32715
+ );
32716
+ if (ok) jsonSuccess({ jobs: results });
32717
+ jsonResult({ jobs: results }, 1);
32718
+ return;
32686
32719
  }
32687
32720
  const jobs = dstLocales.map((locale) => ({
32688
32721
  dstLocale: locale,
32689
32722
  status: "waiting"
32690
32723
  }));
32691
- const { rerender, unmount } = render19(
32692
- /* @__PURE__ */ jsx22(RunUI, { srcLocale, jobs })
32724
+ const { rerender, unmount } = render18(
32725
+ /* @__PURE__ */ jsx20(RunUI, { srcLocale, jobs })
32693
32726
  );
32694
32727
  const updateJob = (locale, update) => {
32695
32728
  const index = jobs.findIndex((j) => j.dstLocale === locale);
@@ -32701,7 +32734,15 @@ ${env.TOLKBOT_WEB_URL}/projects/${project.slug}/settings`
32701
32734
  dstLocale: currentJob.dstLocale,
32702
32735
  status: update.status ?? currentJob.status
32703
32736
  };
32704
- rerender(/* @__PURE__ */ jsx22(RunUI, { srcLocale, jobs }));
32737
+ rerender(/* @__PURE__ */ jsx20(RunUI, { srcLocale, jobs }));
32738
+ }
32739
+ };
32740
+ const skipRemaining = (locale, why) => {
32741
+ for (const later of dstLocales.slice(dstLocales.indexOf(locale) + 1)) {
32742
+ updateJob(later, {
32743
+ status: "error",
32744
+ error: `Not started, because ${why} the ${locale} job.`
32745
+ });
32705
32746
  }
32706
32747
  };
32707
32748
  try {
@@ -32722,38 +32763,49 @@ ${env.TOLKBOT_WEB_URL}/projects/${project.slug}/settings`
32722
32763
  taskCount: tasks.length,
32723
32764
  notes
32724
32765
  });
32725
- const { jobId, summary } = await runTranslationJob({
32726
- projectId,
32727
- organizationId: project.organizationId,
32728
- framework,
32729
- srcLocale,
32730
- dstLocale,
32731
- tasks,
32732
- batchSize,
32733
- concurrency,
32734
- onProgress: (progress) => {
32735
- updateJob(dstLocale, { progress });
32736
- }
32737
- });
32738
- if (!summary) {
32766
+ let outcome;
32767
+ try {
32768
+ outcome = await runTranslationJob({
32769
+ projectId,
32770
+ organizationId: project.organizationId,
32771
+ framework,
32772
+ srcLocale,
32773
+ dstLocale,
32774
+ tasks,
32775
+ onProgress: (progress) => {
32776
+ updateJob(dstLocale, { progress });
32777
+ }
32778
+ });
32779
+ } catch (error62) {
32780
+ if (!(error62 instanceof JobWaitTimeoutError)) throw error62;
32739
32781
  updateJob(dstLocale, {
32740
32782
  status: "error",
32741
- error: "Job not found"
32783
+ jobId: error62.jobId,
32784
+ error: error62.message
32742
32785
  });
32743
- } else if (summary.status === "failed") {
32786
+ process.exitCode = 1;
32787
+ skipRemaining(dstLocale, "the CLI stopped waiting for");
32788
+ break;
32789
+ }
32790
+ const { jobId, summary } = outcome;
32791
+ if (summary.status === "failed") {
32744
32792
  updateJob(dstLocale, {
32745
32793
  status: "error",
32746
32794
  progress: summary,
32747
32795
  jobId,
32748
32796
  error: "Translation failed"
32749
32797
  });
32798
+ process.exitCode = 1;
32750
32799
  } else if (summary.status === "cancelled") {
32751
32800
  updateJob(dstLocale, {
32752
32801
  status: "error",
32753
32802
  progress: summary,
32754
32803
  jobId,
32755
- error: "Job cancelled"
32804
+ error: "The job was cancelled."
32756
32805
  });
32806
+ process.exitCode = 1;
32807
+ skipRemaining(dstLocale, "someone cancelled");
32808
+ break;
32757
32809
  } else {
32758
32810
  updateJob(dstLocale, {
32759
32811
  status: "completed",
@@ -32785,34 +32837,34 @@ function summarize(plan) {
32785
32837
 
32786
32838
  // src/commands/translate/status.tsx
32787
32839
  import { Spinner as Spinner18 } from "@inkjs/ui";
32788
- import { Box as Box22, render as render20, Text as Text22 } from "ink";
32789
- import { jsx as jsx23, jsxs as jsxs21 } from "react/jsx-runtime";
32840
+ import { Box as Box20, render as render19, Text as Text20 } from "ink";
32841
+ import { jsx as jsx21, jsxs as jsxs20 } from "react/jsx-runtime";
32790
32842
  function StatusUI({ state }) {
32791
32843
  if (state.type === "loading") {
32792
- return /* @__PURE__ */ jsxs21(Box22, { children: [
32793
- /* @__PURE__ */ jsx23(Spinner18, {}),
32794
- /* @__PURE__ */ jsx23(Text22, { children: " Loading job status\u2026" })
32844
+ return /* @__PURE__ */ jsxs20(Box20, { children: [
32845
+ /* @__PURE__ */ jsx21(Spinner18, {}),
32846
+ /* @__PURE__ */ jsx21(Text20, { children: " Loading job status\u2026" })
32795
32847
  ] });
32796
32848
  }
32797
32849
  if (state.type === "not-found") {
32798
- return /* @__PURE__ */ jsx23(Box22, { children: /* @__PURE__ */ jsx23(Text22, { color: "red", children: "\u2717 Job not found" }) });
32850
+ return /* @__PURE__ */ jsx21(Box20, { children: /* @__PURE__ */ jsx21(Text20, { color: "red", children: "\u2717 Job not found" }) });
32799
32851
  }
32800
32852
  if (state.type === "error") {
32801
- return /* @__PURE__ */ jsxs21(Box22, { flexDirection: "column", children: [
32802
- /* @__PURE__ */ jsx23(Text22, { color: "red", children: "\u2717 Error fetching job status" }),
32803
- /* @__PURE__ */ jsx23(Text22, { dimColor: true, children: state.message })
32853
+ return /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", children: [
32854
+ /* @__PURE__ */ jsx21(Text20, { color: "red", children: "\u2717 Error fetching job status" }),
32855
+ /* @__PURE__ */ jsx21(Text20, { dimColor: true, children: state.message })
32804
32856
  ] });
32805
32857
  }
32806
32858
  const { summary } = state;
32807
32859
  const processed = summary.completed + summary.failed;
32808
32860
  const progress = summary.total > 0 ? Math.round(processed / summary.total * 100) : 0;
32809
32861
  if (state.type === "running") {
32810
- return /* @__PURE__ */ jsxs21(Box22, { flexDirection: "column", children: [
32811
- /* @__PURE__ */ jsxs21(Box22, { children: [
32812
- /* @__PURE__ */ jsx23(Spinner18, {}),
32813
- /* @__PURE__ */ jsx23(Text22, { children: " Job is running\u2026" })
32862
+ return /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", children: [
32863
+ /* @__PURE__ */ jsxs20(Box20, { children: [
32864
+ /* @__PURE__ */ jsx21(Spinner18, {}),
32865
+ /* @__PURE__ */ jsx21(Text20, { children: " Job is running\u2026" })
32814
32866
  ] }),
32815
- /* @__PURE__ */ jsx23(Box22, { marginTop: 1, children: /* @__PURE__ */ jsxs21(Text22, { children: [
32867
+ /* @__PURE__ */ jsx21(Box20, { marginTop: 1, children: /* @__PURE__ */ jsxs20(Text20, { children: [
32816
32868
  "Progress: ",
32817
32869
  processed,
32818
32870
  "/",
@@ -32821,14 +32873,14 @@ function StatusUI({ state }) {
32821
32873
  progress,
32822
32874
  "%)"
32823
32875
  ] }) }),
32824
- /* @__PURE__ */ jsx23(Box22, { children: /* @__PURE__ */ jsxs21(Text22, { dimColor: true, children: [
32876
+ /* @__PURE__ */ jsx21(Box20, { children: /* @__PURE__ */ jsxs20(Text20, { dimColor: true, children: [
32825
32877
  "Completed: ",
32826
32878
  summary.completed,
32827
32879
  " | Failed:",
32828
32880
  " ",
32829
32881
  summary.failed
32830
32882
  ] }) }),
32831
- /* @__PURE__ */ jsx23(Box22, { marginTop: 1, children: /* @__PURE__ */ jsxs21(Text22, { dimColor: true, children: [
32883
+ /* @__PURE__ */ jsx21(Box20, { marginTop: 1, children: /* @__PURE__ */ jsxs20(Text20, { dimColor: true, children: [
32832
32884
  "Status: ",
32833
32885
  summary.status
32834
32886
  ] }) })
@@ -32836,16 +32888,16 @@ function StatusUI({ state }) {
32836
32888
  }
32837
32889
  const statusColor = summary.status === "succeeded" ? "green" : summary.status === "partial" ? "yellow" : "red";
32838
32890
  const statusIcon = summary.status === "succeeded" ? "\u2713" : summary.status === "partial" ? "\u26A0" : "\u2717";
32839
- const statusText = summary.status === "succeeded" ? "completed successfully" : summary.status === "partial" ? "completed with some failures" : "failed";
32840
- return /* @__PURE__ */ jsxs21(Box22, { flexDirection: "column", children: [
32841
- /* @__PURE__ */ jsxs21(Box22, { children: [
32842
- /* @__PURE__ */ jsx23(Text22, { color: statusColor, children: statusIcon }),
32843
- /* @__PURE__ */ jsxs21(Text22, { children: [
32891
+ const statusText = summary.status === "succeeded" ? "completed successfully" : summary.status === "partial" ? "completed with some failures" : summary.status === "cancelled" ? "was cancelled" : "failed";
32892
+ return /* @__PURE__ */ jsxs20(Box20, { flexDirection: "column", children: [
32893
+ /* @__PURE__ */ jsxs20(Box20, { children: [
32894
+ /* @__PURE__ */ jsx21(Text20, { color: statusColor, children: statusIcon }),
32895
+ /* @__PURE__ */ jsxs20(Text20, { children: [
32844
32896
  " Job ",
32845
32897
  statusText
32846
32898
  ] })
32847
32899
  ] }),
32848
- /* @__PURE__ */ jsx23(Box22, { marginTop: 1, children: /* @__PURE__ */ jsxs21(Text22, { children: [
32900
+ /* @__PURE__ */ jsx21(Box20, { marginTop: 1, children: /* @__PURE__ */ jsxs20(Text20, { children: [
32849
32901
  "Completed: ",
32850
32902
  summary.completed,
32851
32903
  " | Failed: ",
@@ -32853,7 +32905,7 @@ function StatusUI({ state }) {
32853
32905
  " | Total: ",
32854
32906
  summary.total
32855
32907
  ] }) }),
32856
- /* @__PURE__ */ jsx23(Box22, { marginTop: 1, children: /* @__PURE__ */ jsxs21(Text22, { dimColor: true, children: [
32908
+ /* @__PURE__ */ jsx21(Box20, { marginTop: 1, children: /* @__PURE__ */ jsxs20(Text20, { dimColor: true, children: [
32857
32909
  "Status: ",
32858
32910
  summary.status
32859
32911
  ] }) })
@@ -32902,7 +32954,7 @@ async function runStatus(opts) {
32902
32954
  jsonError(error62 instanceof Error ? error62.message : String(error62));
32903
32955
  }
32904
32956
  }
32905
- let { unmount } = render20(/* @__PURE__ */ jsx23(StatusUI, { state: { type: "loading" } }));
32957
+ let { unmount } = render19(/* @__PURE__ */ jsx21(StatusUI, { state: { type: "loading" } }));
32906
32958
  try {
32907
32959
  if (!opts.jobId) {
32908
32960
  throw new Error("--job-id is required");
@@ -32910,17 +32962,17 @@ async function runStatus(opts) {
32910
32962
  const summary = await fetchJobSummary(opts.jobId);
32911
32963
  if (!summary) {
32912
32964
  unmount();
32913
- const notFoundUi = render20(
32914
- /* @__PURE__ */ jsx23(StatusUI, { state: { type: "not-found" } })
32965
+ const notFoundUi = render19(
32966
+ /* @__PURE__ */ jsx21(StatusUI, { state: { type: "not-found" } })
32915
32967
  );
32916
32968
  unmount = notFoundUi.unmount;
32917
32969
  await notFoundUi.waitUntilExit();
32918
32970
  return exitAfterFlush(1);
32919
32971
  }
32920
- const isCompleted = summary.status === "succeeded" || summary.status === "partial" || summary.status === "failed";
32972
+ const isCompleted = isJobFinished(summary.status);
32921
32973
  unmount();
32922
- const finalUi = render20(
32923
- /* @__PURE__ */ jsx23(
32974
+ const finalUi = render19(
32975
+ /* @__PURE__ */ jsx21(
32924
32976
  StatusUI,
32925
32977
  {
32926
32978
  state: {
@@ -32932,13 +32984,13 @@ async function runStatus(opts) {
32932
32984
  );
32933
32985
  unmount = finalUi.unmount;
32934
32986
  await finalUi.waitUntilExit();
32935
- if (summary.status === "failed") {
32987
+ if (isJobUnsuccessful(summary.status)) {
32936
32988
  return exitAfterFlush(1);
32937
32989
  }
32938
32990
  } catch (error62) {
32939
32991
  unmount();
32940
- const errorUi = render20(
32941
- /* @__PURE__ */ jsx23(
32992
+ const errorUi = render19(
32993
+ /* @__PURE__ */ jsx21(
32942
32994
  StatusUI,
32943
32995
  {
32944
32996
  state: {
@@ -32955,7 +33007,7 @@ async function runStatus(opts) {
32955
33007
 
32956
33008
  // src/commands/translate/index.ts
32957
33009
  var translateCommand = new Command5("translate").description(
32958
- "Translate with Tolkbot"
33010
+ "Translate with TolkBot"
32959
33011
  );
32960
33012
  var RETRANSLATE_UNTRACKED_HELP = "Also retranslate translations TolkBot has no record of (kept by default)";
32961
33013
  translateCommand.command("plan").description(
@@ -32977,14 +33029,12 @@ translateCommand.command("plan").description(
32977
33029
  translateCommand.command("run").description("Run translation job (auto-generates tasks if not provided)").option(
32978
33030
  "--src <locale>",
32979
33031
  "Source locale (defaults to repository base language)"
32980
- ).option("--dst <locale>", "Destination locale").option("--all", "Translate to all target languages").option("--tasks <path>", "Path to tasks JSON array (optional)").option("--batch-size <n>", "Batch size", (v) => Number(v), 10).option("--concurrency <n>", "Concurrency", (v) => Number(v), 3).option("--retranslate-untracked", RETRANSLATE_UNTRACKED_HELP).option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
33032
+ ).option("--dst <locale>", "Destination locale").option("--all", "Translate to all target languages").option("--tasks <path>", "Path to tasks JSON array (optional)").option("--retranslate-untracked", RETRANSLATE_UNTRACKED_HELP).option("--json", "Output as JSON (machine-readable)").action(async (opts) => {
32981
33033
  const schema = z7.object({
32982
33034
  src: z7.string().optional(),
32983
33035
  dst: z7.string().optional(),
32984
33036
  all: z7.boolean().optional(),
32985
33037
  tasks: z7.string().optional(),
32986
- batchSize: z7.coerce.number().int().positive().default(10),
32987
- concurrency: z7.coerce.number().int().positive().default(3),
32988
33038
  retranslateUntracked: z7.boolean().optional(),
32989
33039
  json: z7.boolean().optional()
32990
33040
  });
@@ -33059,7 +33109,7 @@ translateCommand.command("status").description("Check status of translation job
33059
33109
  });
33060
33110
 
33061
33111
  // src/commands/validate.tsx
33062
- import { Box as Box23, render as render21, Text as Text23 } from "ink";
33112
+ import { Box as Box21, render as render20, Text as Text21 } from "ink";
33063
33113
 
33064
33114
  // ../ai/src/validators/placeholders.ts
33065
33115
  var IDENTIFIER = /^[A-Za-z0-9_$][A-Za-z0-9_$.[\]-]*$/;
@@ -34152,7 +34202,7 @@ function finalize2(issues, stats, strict) {
34152
34202
  }
34153
34203
 
34154
34204
  // src/commands/validate.tsx
34155
- import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
34205
+ import { jsx as jsx22, jsxs as jsxs21 } from "react/jsx-runtime";
34156
34206
  var LEVEL_COLOR = {
34157
34207
  error: "red",
34158
34208
  warning: "yellow",
@@ -34161,12 +34211,12 @@ var LEVEL_COLOR = {
34161
34211
  var GUTTER = 9;
34162
34212
  function IssueLine({ issue: issue2 }) {
34163
34213
  const location = [issue2.file, issue2.key].filter(Boolean).join(" \xB7 ");
34164
- return /* @__PURE__ */ jsxs22(Box23, { flexDirection: "column", children: [
34165
- /* @__PURE__ */ jsxs22(Box23, { children: [
34166
- /* @__PURE__ */ jsx24(Box23, { width: GUTTER, flexShrink: 0, children: /* @__PURE__ */ jsx24(Text23, { color: LEVEL_COLOR[issue2.level], children: issue2.level }) }),
34167
- /* @__PURE__ */ jsx24(Box23, { flexGrow: 1, children: /* @__PURE__ */ jsx24(Text23, { children: issue2.message }) })
34214
+ return /* @__PURE__ */ jsxs21(Box21, { flexDirection: "column", children: [
34215
+ /* @__PURE__ */ jsxs21(Box21, { children: [
34216
+ /* @__PURE__ */ jsx22(Box21, { width: GUTTER, flexShrink: 0, children: /* @__PURE__ */ jsx22(Text21, { color: LEVEL_COLOR[issue2.level], children: issue2.level }) }),
34217
+ /* @__PURE__ */ jsx22(Box21, { flexGrow: 1, children: /* @__PURE__ */ jsx22(Text21, { children: issue2.message }) })
34168
34218
  ] }),
34169
- /* @__PURE__ */ jsx24(Box23, { marginLeft: GUTTER, children: /* @__PURE__ */ jsxs22(Text23, { dimColor: true, children: [
34219
+ /* @__PURE__ */ jsx22(Box21, { marginLeft: GUTTER, children: /* @__PURE__ */ jsxs21(Text21, { dimColor: true, children: [
34170
34220
  issue2.rule,
34171
34221
  location ? ` \u2014 ${location}` : ""
34172
34222
  ] }) })
@@ -34175,20 +34225,20 @@ function IssueLine({ issue: issue2 }) {
34175
34225
  function ReportUI({ report }) {
34176
34226
  const { counts, stats } = report;
34177
34227
  const summary = ["error", "warning", "info"].filter((level) => counts[level] > 0).map((level) => `${counts[level]} ${level}(s)`).join(", ");
34178
- return /* @__PURE__ */ jsxs22(Box23, { flexDirection: "column", children: [
34179
- /* @__PURE__ */ jsxs22(Box23, { children: [
34180
- /* @__PURE__ */ jsx24(Text23, { color: report.valid ? "green" : "red", children: report.valid ? "\u2713" : "\u2717" }),
34181
- /* @__PURE__ */ jsxs22(Text23, { children: [
34228
+ return /* @__PURE__ */ jsxs21(Box21, { flexDirection: "column", children: [
34229
+ /* @__PURE__ */ jsxs21(Box21, { children: [
34230
+ /* @__PURE__ */ jsx22(Text21, { color: report.valid ? "green" : "red", children: report.valid ? "\u2713" : "\u2717" }),
34231
+ /* @__PURE__ */ jsxs21(Text21, { children: [
34182
34232
  " Validation ",
34183
34233
  report.valid ? "passed" : "failed"
34184
34234
  ] }),
34185
- summary ? /* @__PURE__ */ jsxs22(Text23, { dimColor: true, children: [
34235
+ summary ? /* @__PURE__ */ jsxs21(Text21, { dimColor: true, children: [
34186
34236
  " \u2014 ",
34187
34237
  summary
34188
34238
  ] }) : null
34189
34239
  ] }),
34190
- stats.locales.length > 0 && /* @__PURE__ */ jsxs22(Box23, { marginTop: 1, flexDirection: "column", children: [
34191
- /* @__PURE__ */ jsxs22(Text23, { dimColor: true, children: [
34240
+ stats.locales.length > 0 && /* @__PURE__ */ jsxs21(Box21, { marginTop: 1, flexDirection: "column", children: [
34241
+ /* @__PURE__ */ jsxs21(Text21, { dimColor: true, children: [
34192
34242
  stats.filesChecked,
34193
34243
  " ",
34194
34244
  stats.format,
@@ -34197,18 +34247,18 @@ function ReportUI({ report }) {
34197
34247
  stats.totalKeys,
34198
34248
  " keys"
34199
34249
  ] }),
34200
- /* @__PURE__ */ jsxs22(Text23, { dimColor: true, children: [
34250
+ /* @__PURE__ */ jsxs21(Text21, { dimColor: true, children: [
34201
34251
  "Locales: ",
34202
34252
  stats.locales.join(", "),
34203
34253
  stats.baseLocale ? ` (base: ${stats.baseLocale})` : ""
34204
34254
  ] }),
34205
- stats.pattern && /* @__PURE__ */ jsxs22(Text23, { dimColor: true, children: [
34255
+ stats.pattern && /* @__PURE__ */ jsxs21(Text21, { dimColor: true, children: [
34206
34256
  "Pattern: ",
34207
34257
  stats.pattern
34208
34258
  ] })
34209
34259
  ] }),
34210
- report.issues.length > 0 && /* @__PURE__ */ jsx24(Box23, { marginTop: 1, flexDirection: "column", gap: 1, children: report.issues.map((issue2, idx) => /* @__PURE__ */ jsx24(IssueLine, { issue: issue2 }, idx)) }),
34211
- !report.strict && counts.warning > 0 && report.valid && /* @__PURE__ */ jsx24(Box23, { marginTop: 1, children: /* @__PURE__ */ jsx24(Text23, { dimColor: true, children: "Warnings did not fail this run. Use --strict to treat them as errors." }) })
34260
+ report.issues.length > 0 && /* @__PURE__ */ jsx22(Box21, { marginTop: 1, flexDirection: "column", gap: 1, children: report.issues.map((issue2, idx) => /* @__PURE__ */ jsx22(IssueLine, { issue: issue2 }, idx)) }),
34261
+ !report.strict && counts.warning > 0 && report.valid && /* @__PURE__ */ jsx22(Box21, { marginTop: 1, children: /* @__PURE__ */ jsx22(Text21, { dimColor: true, children: "Warnings did not fail this run. Use --strict to treat them as errors." }) })
34212
34262
  ] });
34213
34263
  }
34214
34264
  function emit(report, json2) {
@@ -34227,7 +34277,7 @@ function emit(report, json2) {
34227
34277
  jsonResult(report, exitCode);
34228
34278
  return;
34229
34279
  }
34230
- render21(/* @__PURE__ */ jsx24(ReportUI, { report })).unmount();
34280
+ render20(/* @__PURE__ */ jsx22(ReportUI, { report })).unmount();
34231
34281
  process.exitCode = exitCode;
34232
34282
  }
34233
34283
  async function runValidate(opts = {}) {
@@ -34274,7 +34324,7 @@ function commandPath(command) {
34274
34324
  return names.join(" ");
34275
34325
  }
34276
34326
  function buildProgram() {
34277
- const program2 = new Command6().name("tolkbot").description("Localization workflow for your repo").version(version, "-v, --version", "Output the version number").option(
34327
+ const program2 = new Command6().name("tolkbot").description("Localization workflow for your repo").version(version, "-v, --version", "Output the version number").helpOption("-h, --help", "Show help").helpCommand("help [command]", "Show help for a command").option(
34278
34328
  "--token <token>",
34279
34329
  `Auth token to use for this invocation (overrides $${TOKEN_ENV_VAR} and the stored token)`
34280
34330
  );
@@ -34283,7 +34333,7 @@ function buildProgram() {
34283
34333
  setTokenOverride(token);
34284
34334
  beginCommand(commandPath(actionCommand), version);
34285
34335
  });
34286
- program2.command("init").description("Initialize Tolkbot in the current repo").option("--force", "Overwrite existing config").option(
34336
+ program2.command("init").description("Initialize TolkBot in the current repo").option("--force", "Overwrite existing config").option(
34287
34337
  "-y, --yes",
34288
34338
  "Skip all prompts, accepting detected settings. Required outside a terminal."
34289
34339
  ).option("--json", "Output the result as JSON (implies --yes)").option("--base <locale>", "Base locale (default: detected, else en)").option(
@@ -34296,7 +34346,6 @@ function buildProgram() {
34296
34346
  "--install",
34297
34347
  "Also install i18n dependencies and scaffold files. Off by default when non-interactive."
34298
34348
  ).action((opts) => runInit(opts));
34299
- program2.command("help").description("Show help message").action(() => runHelp());
34300
34349
  program2.command("validate").description("Validate translation files and configuration").option("--json", "Output the report as JSON").option(
34301
34350
  "--strict",
34302
34351
  "Treat warnings (incomplete locales) as errors. Off by default, since a locale is normally behind the base while a translation job is in flight."
@@ -34308,8 +34357,24 @@ function buildProgram() {
34308
34357
  program2.addCommand(learningsCommand);
34309
34358
  program2.addCommand(telemetryCommand);
34310
34359
  program2.addCommand(translateCommand);
34360
+ program2.addHelpText("after", PROGRAM_HELP_FOOTER);
34311
34361
  return program2;
34312
34362
  }
34363
+ var PROGRAM_HELP_FOOTER = `
34364
+ Examples:
34365
+ $ tolkbot auth login
34366
+ $ tolkbot init --yes
34367
+ $ tolkbot translate plan --dst nb --json
34368
+ $ tolkbot translate run --all
34369
+ $ tolkbot translate apply --job-id <id>
34370
+ $ tolkbot validate --strict
34371
+
34372
+ Environment variables:
34373
+ ${TOKEN_ENV_VAR} Auth token for CI and headless environments (--token overrides it)
34374
+ ${TELEMETRY_ENV_VAR} Set to 0 to stop reporting usage
34375
+ ${DO_NOT_TRACK_ENV_VAR} Set to 1 to stop reporting usage
34376
+
34377
+ Run 'tolkbot <command> --help' for a command's options.`;
34313
34378
  function currentExitCode() {
34314
34379
  return typeof process.exitCode === "number" ? process.exitCode : 0;
34315
34380
  }