@intlpullhq/cli 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ ContributorsApi,
4
+ SnapshotsApi,
5
+ TMApi,
6
+ WebhooksApi,
3
7
  createApiClient
4
- } from "./chunk-2MCKJWPD.js";
8
+ } from "./chunk-WVCVQFBI.js";
9
+ import {
10
+ ProjectsApi
11
+ } from "./chunk-KCZQUMQP.js";
12
+ import "./chunk-WSY27J6N.js";
13
+ import {
14
+ KeysApi
15
+ } from "./chunk-BULIQM4U.js";
16
+ import "./chunk-KIDP7N6D.js";
5
17
  import {
6
18
  detectFramework,
7
19
  getAuthErrorMessage,
@@ -15,10 +27,10 @@ import {
15
27
  saveAuthConfig,
16
28
  saveProjectConfig,
17
29
  setCustomEnvFile
18
- } from "./chunk-GQADIM7O.js";
30
+ } from "./chunk-IWYURZV2.js";
19
31
 
20
32
  // src/index.tsx
21
- import { Command as Command2 } from "commander";
33
+ import { Command as Command7 } from "commander";
22
34
 
23
35
  // src/commands/init.tsx
24
36
  import { useState, useEffect } from "react";
@@ -1059,10 +1071,10 @@ function runStatus() {
1059
1071
  render3(/* @__PURE__ */ jsx8(StatusCommand, {}));
1060
1072
  }
1061
1073
 
1062
- // src/commands/pull.tsx
1074
+ // src/commands/download.tsx
1063
1075
  import { render as render4 } from "ink";
1064
1076
 
1065
- // src/commands/pull/components/interactive-pull.tsx
1077
+ // src/commands/download/components/interactive-pull.tsx
1066
1078
  import { useState as useState6, useEffect as useEffect5, useCallback as useCallback2 } from "react";
1067
1079
  import { Box as Box9, Text as Text10, useApp as useApp2 } from "ink";
1068
1080
  import SelectInput2 from "ink-select-input";
@@ -1070,10 +1082,13 @@ import Spinner3 from "ink-spinner";
1070
1082
  import { mkdirSync as mkdirSync3, existsSync as existsSync5 } from "fs";
1071
1083
  import { join as join5, dirname as dirname3 } from "path";
1072
1084
 
1073
- // src/commands/pull/api.ts
1085
+ // src/lib/api/fetch-utils.ts
1074
1086
  var DEFAULT_TIMEOUT = 3e4;
1075
1087
  var MAX_RETRIES = 3;
1076
1088
  var RETRY_DELAYS = [1e3, 2e3, 4e3];
1089
+ function sleep(ms) {
1090
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
1091
+ }
1077
1092
  async function fetchWithRetry(url, options = {}, retries = MAX_RETRIES) {
1078
1093
  const { timeout = DEFAULT_TIMEOUT, ...fetchOptions } = options;
1079
1094
  for (let attempt = 0; attempt <= retries; attempt++) {
@@ -1085,6 +1100,24 @@ async function fetchWithRetry(url, options = {}, retries = MAX_RETRIES) {
1085
1100
  signal: controller.signal
1086
1101
  });
1087
1102
  clearTimeout(timeoutId);
1103
+ if (response.status === 429 && attempt < retries) {
1104
+ const retryAfter = response.headers.get("Retry-After");
1105
+ let delayMs = RETRY_DELAYS[attempt] || 4e3;
1106
+ if (retryAfter) {
1107
+ const retryAfterSeconds = parseInt(retryAfter, 10);
1108
+ if (!isNaN(retryAfterSeconds)) {
1109
+ delayMs = retryAfterSeconds * 1e3;
1110
+ } else {
1111
+ const retryDate = new Date(retryAfter);
1112
+ if (!isNaN(retryDate.getTime())) {
1113
+ delayMs = Math.max(0, retryDate.getTime() - Date.now());
1114
+ }
1115
+ }
1116
+ }
1117
+ delayMs = Math.min(delayMs, 3e4);
1118
+ await sleep(delayMs);
1119
+ continue;
1120
+ }
1088
1121
  if (response.status >= 500 && attempt < retries) {
1089
1122
  await sleep(RETRY_DELAYS[attempt] || 4e3);
1090
1123
  continue;
@@ -1108,9 +1141,8 @@ async function fetchWithRetry(url, options = {}, retries = MAX_RETRIES) {
1108
1141
  }
1109
1142
  throw new Error("Max retries exceeded");
1110
1143
  }
1111
- function sleep(ms) {
1112
- return new Promise((resolve) => setTimeout(resolve, ms));
1113
- }
1144
+
1145
+ // src/commands/download/api.ts
1114
1146
  async function fetchProjects2(apiUrl, apiKey) {
1115
1147
  const headers = { Accept: "application/json" };
1116
1148
  if (apiKey) headers["X-API-Key"] = apiKey;
@@ -1327,7 +1359,7 @@ async function fetchProjectInfo(projectId, apiUrl, apiKey) {
1327
1359
  };
1328
1360
  }
1329
1361
 
1330
- // src/commands/pull/formatters.ts
1362
+ // src/commands/download/formatters.ts
1331
1363
  function formatAsJson(translations) {
1332
1364
  return JSON.stringify(translations, null, 2);
1333
1365
  }
@@ -1377,7 +1409,7 @@ function getFileExtension(format) {
1377
1409
  }
1378
1410
  }
1379
1411
 
1380
- // src/commands/pull/components/multi-select.tsx
1412
+ // src/commands/download/components/multi-select.tsx
1381
1413
  import { useState as useState5 } from "react";
1382
1414
  import { Box as Box8, Text as Text9, useInput as useInput2 } from "ink";
1383
1415
  import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
@@ -1411,7 +1443,7 @@ function MultiSelect({ items, selected, onToggle, onSubmit, isActive = true }) {
1411
1443
  ] });
1412
1444
  }
1413
1445
 
1414
- // src/commands/pull/hooks/useInteractivePull.ts
1446
+ // src/commands/download/hooks/useInteractivePull.ts
1415
1447
  import { useReducer, useCallback } from "react";
1416
1448
  var initialState = {
1417
1449
  step: "project",
@@ -1732,7 +1764,7 @@ function cleanupStaleTempFiles(dir, maxAgeMs = TEMP_FILE_MAX_AGE_MS) {
1732
1764
  return result;
1733
1765
  }
1734
1766
 
1735
- // src/commands/pull/components/interactive-pull.tsx
1767
+ // src/commands/download/components/interactive-pull.tsx
1736
1768
  import { Fragment, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1737
1769
  function InteractivePull({ initialOptions }) {
1738
1770
  const { exit } = useApp2();
@@ -2206,7 +2238,7 @@ ${writeErrors.join("\n")}`);
2206
2238
  ] });
2207
2239
  }
2208
2240
 
2209
- // src/commands/pull/components/smart-pull.tsx
2241
+ // src/commands/download/components/smart-pull.tsx
2210
2242
  import { useState as useState7, useEffect as useEffect6 } from "react";
2211
2243
  import { Box as Box10, Text as Text11 } from "ink";
2212
2244
  import Spinner4 from "ink-spinner";
@@ -2342,7 +2374,140 @@ var LIBRARIES2 = {
2342
2374
  }
2343
2375
  };
2344
2376
 
2345
- // src/commands/pull/components/smart-pull.tsx
2377
+ // src/lib/key-scanner.ts
2378
+ import { glob } from "glob";
2379
+ import { readFile } from "fs/promises";
2380
+ var DEFAULT_EXTRACTORS = [
2381
+ // i18next: t('key.name') or t("key.name")
2382
+ {
2383
+ name: "i18next",
2384
+ pattern: /\bt\s*\(\s*['"]([^'"]+)['"]/g,
2385
+ extract: (m) => m[1]
2386
+ },
2387
+ // react-i18next: t('key.name')
2388
+ {
2389
+ name: "react-i18next",
2390
+ pattern: /\bt\s*\(\s*['"]([^'"]+)['"]/g,
2391
+ extract: (m) => m[1]
2392
+ },
2393
+ // vue-i18n: $t('key.name') or {{ $t('key.name') }}
2394
+ {
2395
+ name: "vue-i18n",
2396
+ pattern: /\$t\s*\(\s*['"]([^'"]+)['"]/g,
2397
+ extract: (m) => m[1]
2398
+ },
2399
+ // react-intl: formatMessage({ id: 'key.name' })
2400
+ {
2401
+ name: "react-intl-formatMessage",
2402
+ pattern: /formatMessage\s*\(\s*\{\s*id:\s*['"]([^'"]+)['"]/g,
2403
+ extract: (m) => m[1]
2404
+ },
2405
+ // react-intl: <FormattedMessage id="key.name" />
2406
+ {
2407
+ name: "react-intl-component",
2408
+ pattern: /<FormattedMessage\s+id=["']([^"']+)["']/g,
2409
+ extract: (m) => m[1]
2410
+ },
2411
+ // next-intl: t('key.name')
2412
+ {
2413
+ name: "next-intl",
2414
+ pattern: /\bt\s*\(\s*['"]([^'"]+)['"]/g,
2415
+ extract: (m) => m[1]
2416
+ },
2417
+ // lingui: t`key.name` or t({ id: 'key.name' })
2418
+ {
2419
+ name: "lingui-macro",
2420
+ pattern: /\bt\s*`([^`]+)`/g,
2421
+ extract: (m) => m[1]
2422
+ },
2423
+ {
2424
+ name: "lingui-object",
2425
+ pattern: /\bt\s*\(\s*\{\s*id:\s*['"]([^'"]+)['"]/g,
2426
+ extract: (m) => m[1]
2427
+ },
2428
+ // svelte-i18n: $t('key.name') or {$t('key.name')}
2429
+ {
2430
+ name: "svelte-i18n",
2431
+ pattern: /\$t\s*\(\s*['"]([^'"]+)['"]/g,
2432
+ extract: (m) => m[1]
2433
+ },
2434
+ // astro-i18n: t('key.name')
2435
+ {
2436
+ name: "astro-i18n",
2437
+ pattern: /\bt\s*\(\s*['"]([^'"]+)['"]/g,
2438
+ extract: (m) => m[1]
2439
+ }
2440
+ ];
2441
+ async function scanForKeys(rootDir, options = {}) {
2442
+ const patterns = options.patterns || ["**/*.{ts,tsx,js,jsx,vue,svelte,astro}"];
2443
+ const exclude = options.exclude || ["node_modules/**", "dist/**", ".next/**", "build/**", ".svelte-kit/**", "out/**"];
2444
+ const extractors = options.extractors || DEFAULT_EXTRACTORS;
2445
+ const usedKeys = /* @__PURE__ */ new Set();
2446
+ try {
2447
+ const files = await glob(patterns, { cwd: rootDir, ignore: exclude });
2448
+ const batchSize = 50;
2449
+ for (let i = 0; i < files.length; i += batchSize) {
2450
+ const batch = files.slice(i, i + batchSize);
2451
+ await Promise.all(
2452
+ batch.map(async (file) => {
2453
+ try {
2454
+ const content = await readFile(`${rootDir}/${file}`, "utf-8");
2455
+ for (const extractor of extractors) {
2456
+ const matches = content.matchAll(extractor.pattern);
2457
+ for (const match of matches) {
2458
+ const key = extractor.extract(match);
2459
+ if (key) {
2460
+ usedKeys.add(key);
2461
+ }
2462
+ }
2463
+ }
2464
+ } catch (err) {
2465
+ if (err instanceof Error && !err.message.includes("ENOENT")) {
2466
+ }
2467
+ }
2468
+ })
2469
+ );
2470
+ }
2471
+ } catch (err) {
2472
+ throw new Error(`Failed to scan files: ${err instanceof Error ? err.message : "Unknown error"}`);
2473
+ }
2474
+ return usedKeys;
2475
+ }
2476
+ function validatePrefix(prefix) {
2477
+ return /^[a-zA-Z0-9.]+$/.test(prefix);
2478
+ }
2479
+ function applyPrefix(keys, prefix) {
2480
+ const result = {};
2481
+ const prefixWithDot = `${prefix}.`;
2482
+ for (const [key, value] of Object.entries(keys)) {
2483
+ result[`${prefixWithDot}${key}`] = value;
2484
+ }
2485
+ return result;
2486
+ }
2487
+ function stripPrefix(keys, prefix) {
2488
+ const result = {};
2489
+ const prefixWithDot = `${prefix}.`;
2490
+ for (const [key, value] of Object.entries(keys)) {
2491
+ if (key.startsWith(prefixWithDot)) {
2492
+ result[key.substring(prefixWithDot.length)] = value;
2493
+ } else {
2494
+ result[key] = value;
2495
+ }
2496
+ }
2497
+ return result;
2498
+ }
2499
+ function filterByPrefix(keys, prefix) {
2500
+ const result = {};
2501
+ const prefixWithDot = `${prefix}.`;
2502
+ for (const [key, value] of Object.entries(keys)) {
2503
+ if (key.startsWith(prefixWithDot) || key === prefix) {
2504
+ result[key] = value;
2505
+ }
2506
+ }
2507
+ return result;
2508
+ }
2509
+
2510
+ // src/commands/download/components/smart-pull.tsx
2346
2511
  import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
2347
2512
  function SmartPull({ options }) {
2348
2513
  const [state, setState] = useState7({
@@ -2361,6 +2526,12 @@ function SmartPull({ options }) {
2361
2526
  if (!apiKey) {
2362
2527
  throw new Error("Not authenticated. Run `npx @intlpullhq/cli login` or set INTLPULL_API_KEY.");
2363
2528
  }
2529
+ if (options.translationKeyPrefix && !validatePrefix(options.translationKeyPrefix)) {
2530
+ throw new Error('Invalid prefix format. Use alphanumeric characters and dots only (e.g., "checkout" or "app.checkout")');
2531
+ }
2532
+ if (options.filterByPrefix && !validatePrefix(options.filterByPrefix)) {
2533
+ throw new Error("Invalid filter prefix format. Use alphanumeric characters and dots only");
2534
+ }
2364
2535
  setState((s) => ({ ...s, status: "detecting", message: "Fetching project info..." }));
2365
2536
  let projectId = options.project || projectConfig?.projectId;
2366
2537
  let projectInfo = null;
@@ -2474,6 +2645,38 @@ Or use a project-scoped API key for automatic selection.`
2474
2645
  bundle = result.bundle;
2475
2646
  version = result.version;
2476
2647
  }
2648
+ if (options.filterByPrefix) {
2649
+ setState((s) => ({ ...s, message: `Filtering keys by prefix "${options.filterByPrefix}"...` }));
2650
+ for (const locale in bundle) {
2651
+ bundle[locale] = filterByPrefix(bundle[locale], options.filterByPrefix);
2652
+ }
2653
+ if (namespacedBundle) {
2654
+ for (const locale in namespacedBundle) {
2655
+ for (const namespace in namespacedBundle[locale]) {
2656
+ namespacedBundle[locale][namespace] = filterByPrefix(
2657
+ namespacedBundle[locale][namespace],
2658
+ options.filterByPrefix
2659
+ );
2660
+ }
2661
+ }
2662
+ }
2663
+ }
2664
+ if (options.translationKeyPrefix) {
2665
+ setState((s) => ({ ...s, message: `Stripping prefix "${options.translationKeyPrefix}"...` }));
2666
+ for (const locale in bundle) {
2667
+ bundle[locale] = stripPrefix(bundle[locale], options.translationKeyPrefix);
2668
+ }
2669
+ if (namespacedBundle) {
2670
+ for (const locale in namespacedBundle) {
2671
+ for (const namespace in namespacedBundle[locale]) {
2672
+ namespacedBundle[locale][namespace] = stripPrefix(
2673
+ namespacedBundle[locale][namespace],
2674
+ options.translationKeyPrefix
2675
+ );
2676
+ }
2677
+ }
2678
+ }
2679
+ }
2477
2680
  setState((s) => ({ ...s, message: "Writing translation files..." }));
2478
2681
  const ext = getFileExtension(format);
2479
2682
  const writtenFiles = [];
@@ -2662,9 +2865,9 @@ ${writeErrors.join("\n")}`);
2662
2865
  ] });
2663
2866
  }
2664
2867
 
2665
- // src/commands/pull.tsx
2868
+ // src/commands/download.tsx
2666
2869
  import { jsx as jsx12 } from "react/jsx-runtime";
2667
- function runPull(options) {
2870
+ function runDownload(options) {
2668
2871
  if (options.interactive) {
2669
2872
  render4(/* @__PURE__ */ jsx12(InteractivePull, { initialOptions: options }));
2670
2873
  } else {
@@ -2672,16 +2875,16 @@ function runPull(options) {
2672
2875
  }
2673
2876
  }
2674
2877
 
2675
- // src/commands/push.tsx
2878
+ // src/commands/upload.tsx
2676
2879
  import { render as render5 } from "ink";
2677
2880
 
2678
- // src/commands/push/components/PushComponent.tsx
2881
+ // src/commands/upload/components/PushComponent.tsx
2679
2882
  import { useEffect as useEffect7, useCallback as useCallback5 } from "react";
2680
2883
  import { Box as Box14, Text as Text15 } from "ink";
2681
2884
  import { existsSync as existsSync8, readFileSync as readFileSync4 } from "fs";
2682
- import { join as join8, basename as basename3, extname as extname3 } from "path";
2885
+ import { basename as basename3, extname as extname3, isAbsolute, resolve } from "path";
2683
2886
 
2684
- // src/commands/push/hooks/usePushState.ts
2887
+ // src/commands/upload/hooks/usePushState.ts
2685
2888
  import { useReducer as useReducer2, useCallback as useCallback3 } from "react";
2686
2889
  var initialState2 = {
2687
2890
  status: "checking",
@@ -2836,50 +3039,10 @@ function usePushState() {
2836
3039
  };
2837
3040
  }
2838
3041
 
2839
- // src/commands/push/hooks/useLimitCheck.ts
3042
+ // src/commands/upload/hooks/useLimitCheck.ts
2840
3043
  import { useState as useState8, useCallback as useCallback4 } from "react";
2841
3044
 
2842
- // src/commands/push/api.ts
2843
- var DEFAULT_TIMEOUT2 = 3e4;
2844
- var MAX_RETRIES2 = 3;
2845
- var RETRY_DELAYS2 = [1e3, 2e3, 4e3];
2846
- async function fetchWithRetry2(url, options = {}, retries = MAX_RETRIES2) {
2847
- const { timeout = DEFAULT_TIMEOUT2, ...fetchOptions } = options;
2848
- for (let attempt = 0; attempt <= retries; attempt++) {
2849
- const controller = new AbortController();
2850
- const timeoutId = setTimeout(() => controller.abort(), timeout);
2851
- try {
2852
- const response = await fetch(url, {
2853
- ...fetchOptions,
2854
- signal: controller.signal
2855
- });
2856
- clearTimeout(timeoutId);
2857
- if (response.status >= 500 && attempt < retries) {
2858
- await sleep2(RETRY_DELAYS2[attempt] || 4e3);
2859
- continue;
2860
- }
2861
- return response;
2862
- } catch (error) {
2863
- clearTimeout(timeoutId);
2864
- if (error instanceof Error && error.name === "AbortError") {
2865
- if (attempt < retries) {
2866
- await sleep2(RETRY_DELAYS2[attempt] || 4e3);
2867
- continue;
2868
- }
2869
- throw new Error(`Request timed out after ${timeout}ms`);
2870
- }
2871
- if (attempt < retries) {
2872
- await sleep2(RETRY_DELAYS2[attempt] || 4e3);
2873
- continue;
2874
- }
2875
- throw error;
2876
- }
2877
- }
2878
- throw new Error("Max retries exceeded");
2879
- }
2880
- function sleep2(ms) {
2881
- return new Promise((resolve) => setTimeout(resolve, ms));
2882
- }
3045
+ // src/commands/upload/api.ts
2883
3046
  function getApiHeaders() {
2884
3047
  const resolved = getResolvedApiKey();
2885
3048
  const headers = {
@@ -2898,7 +3061,7 @@ function getApiUrl() {
2898
3061
  async function fetchProjects3() {
2899
3062
  const apiUrl = getApiUrl();
2900
3063
  const headers = getApiHeaders();
2901
- const response = await fetchWithRetry2(`${apiUrl}/api/v1/projects`, { headers });
3064
+ const response = await fetchWithRetry(`${apiUrl}/api/v1/projects`, { headers });
2902
3065
  if (!response.ok) {
2903
3066
  if (response.status === 401) {
2904
3067
  throw new Error("Authentication failed. Run `npx @intlpullhq/cli login`");
@@ -2916,7 +3079,7 @@ async function fetchProjects3() {
2916
3079
  async function fetchProject(projectId) {
2917
3080
  const apiUrl = getApiUrl();
2918
3081
  const headers = getApiHeaders();
2919
- const response = await fetchWithRetry2(`${apiUrl}/api/v1/projects/${projectId}`, { headers });
3082
+ const response = await fetchWithRetry(`${apiUrl}/api/v1/projects/${projectId}`, { headers });
2920
3083
  if (!response.ok) {
2921
3084
  if (response.status === 404) {
2922
3085
  throw new Error("Project not found. Check your project ID.");
@@ -2928,7 +3091,7 @@ async function fetchProject(projectId) {
2928
3091
  async function fetchBranches(projectId) {
2929
3092
  const apiUrl = getApiUrl();
2930
3093
  const headers = getApiHeaders();
2931
- const response = await fetchWithRetry2(`${apiUrl}/api/v1/projects/${projectId}/branches`, {
3094
+ const response = await fetchWithRetry(`${apiUrl}/api/v1/projects/${projectId}/branches`, {
2932
3095
  headers
2933
3096
  });
2934
3097
  if (!response.ok) {
@@ -2940,7 +3103,7 @@ async function fetchBranches(projectId) {
2940
3103
  async function checkUsageLimit(keyCount) {
2941
3104
  const apiUrl = getApiUrl();
2942
3105
  const headers = getApiHeaders();
2943
- const response = await fetchWithRetry2(`${apiUrl}/api/v1/billing/usage/check`, {
3106
+ const response = await fetchWithRetry(`${apiUrl}/api/v1/billing/usage/check`, {
2944
3107
  method: "POST",
2945
3108
  headers,
2946
3109
  body: JSON.stringify({ key_count: keyCount })
@@ -2958,28 +3121,40 @@ async function checkUsageLimit(keyCount) {
2958
3121
  }
2959
3122
  async function pushKeys(projectId, keys, language, namespace, options) {
2960
3123
  const apiUrl = getApiUrl();
2961
- const headers = getApiHeaders();
2962
- const body = {
3124
+ const resolved = getResolvedApiKey();
3125
+ const requestBody = {
2963
3126
  keys,
2964
3127
  language,
2965
3128
  namespace
2966
3129
  };
2967
3130
  if (options?.branchId) {
2968
- body.branch_id = options.branchId;
3131
+ requestBody.branch_id = options.branchId;
2969
3132
  }
2970
3133
  if (options?.platforms?.length) {
2971
- body.platforms = options.platforms;
3134
+ requestBody.platforms = options.platforms;
3135
+ }
3136
+ const headers = {
3137
+ "Accept": "application/json",
3138
+ "Content-Type": "application/json"
3139
+ };
3140
+ if (resolved?.key) {
3141
+ headers["X-API-Key"] = resolved.key;
2972
3142
  }
2973
- const response = await fetchWithRetry2(`${apiUrl}/api/v1/projects/${projectId}/keys/bulk`, {
3143
+ const response = await fetchWithRetry(`${apiUrl}/api/v1/projects/${projectId}/keys/bulk`, {
2974
3144
  method: "POST",
2975
3145
  headers,
2976
- body: JSON.stringify(body)
3146
+ body: JSON.stringify(requestBody)
2977
3147
  });
2978
3148
  if (!response.ok) {
2979
3149
  const error = await response.json().catch(() => ({ error: `HTTP ${response.status}` }));
2980
3150
  throw new Error(error.error || `Push failed: ${response.status}`);
2981
3151
  }
2982
- return response.json();
3152
+ const result = await response.json();
3153
+ return {
3154
+ keys_inserted: result.keys_inserted || 0,
3155
+ keys_updated: result.keys_updated || 0,
3156
+ keys_skipped: result.keys_skipped || 0
3157
+ };
2983
3158
  }
2984
3159
  async function pushKeysParallel(projectId, namespaces, language, options) {
2985
3160
  const concurrency = options?.concurrency || 3;
@@ -3020,7 +3195,7 @@ async function pushKeysParallel(projectId, namespaces, language, options) {
3020
3195
  async function addLanguagesBulk(projectId, languages, autoTranslate = false) {
3021
3196
  const apiUrl = getApiUrl();
3022
3197
  const headers = getApiHeaders();
3023
- const response = await fetchWithRetry2(
3198
+ const response = await fetchWithRetry(
3024
3199
  `${apiUrl}/api/v1/projects/${projectId}/language-settings/bulk`,
3025
3200
  {
3026
3201
  method: "POST",
@@ -3037,7 +3212,7 @@ async function addLanguagesBulk(projectId, languages, autoTranslate = false) {
3037
3212
  }
3038
3213
  }
3039
3214
 
3040
- // src/commands/push/hooks/useLimitCheck.ts
3215
+ // src/commands/upload/hooks/useLimitCheck.ts
3041
3216
  function useLimitCheck(verbose = false) {
3042
3217
  const [state, setState] = useState8({
3043
3218
  checking: false
@@ -3143,7 +3318,7 @@ function calculateUploadableKeys(namespaces, availableSlots) {
3143
3318
  return { limited, totalKeys };
3144
3319
  }
3145
3320
 
3146
- // src/commands/push/components/LimitConfirmation.tsx
3321
+ // src/commands/upload/components/LimitConfirmation.tsx
3147
3322
  import { Box as Box11, Text as Text12, useInput as useInput3 } from "ink";
3148
3323
  import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
3149
3324
  function LimitConfirmation({ limitExceeded, onConfirm, onCancel }) {
@@ -3211,7 +3386,7 @@ function LimitConfirmation({ limitExceeded, onConfirm, onCancel }) {
3211
3386
  ] });
3212
3387
  }
3213
3388
 
3214
- // src/commands/push/components/PushProgress.tsx
3389
+ // src/commands/upload/components/PushProgress.tsx
3215
3390
  import { Box as Box12, Text as Text13 } from "ink";
3216
3391
  import Spinner5 from "ink-spinner";
3217
3392
  import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
@@ -3284,7 +3459,7 @@ function PushProgress({
3284
3459
  ] });
3285
3460
  }
3286
3461
 
3287
- // src/commands/push/components/PushResult.tsx
3462
+ // src/commands/upload/components/PushResult.tsx
3288
3463
  import { Box as Box13, Text as Text14 } from "ink";
3289
3464
  import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
3290
3465
  function calculateTotals(allLanguages, languageResults, namespaceResults) {
@@ -4068,7 +4243,7 @@ function readAllTranslationsByLanguage(projectRoot = process.cwd()) {
4068
4243
  };
4069
4244
  }
4070
4245
 
4071
- // src/commands/push/components/PushComponent.tsx
4246
+ // src/commands/upload/components/PushComponent.tsx
4072
4247
  import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
4073
4248
  var isInteractiveTerminal2 = process.stdin.isTTY && process.stdout.isTTY;
4074
4249
  function PushComponent({ options }) {
@@ -4098,12 +4273,16 @@ function PushComponent({ options }) {
4098
4273
  cancel: cancelLimit,
4099
4274
  userConfirmed
4100
4275
  } = useLimitCheck(options.verbose);
4101
- const runPush2 = useCallback5(async () => {
4276
+ const runPush = useCallback5(async () => {
4102
4277
  const resolved = getResolvedApiKey();
4103
4278
  if (!resolved?.key) {
4104
4279
  setError("Not authenticated. Run `npx @intlpullhq/cli login` or set INTLPULL_API_KEY in .env");
4105
4280
  return;
4106
4281
  }
4282
+ if (options.translationKeyPrefix && !validatePrefix(options.translationKeyPrefix)) {
4283
+ setError('Invalid prefix format. Use alphanumeric characters and dots only (e.g., "checkout" or "app.checkout")');
4284
+ return;
4285
+ }
4107
4286
  const config = getProjectConfig();
4108
4287
  let projectId = options.project || config?.projectId;
4109
4288
  if (!projectId) {
@@ -4179,9 +4358,9 @@ To create this branch, go to the IntlPull dashboard.`
4179
4358
  let sourceLanguage = config?.sourceLanguage || "en";
4180
4359
  if (options.file) {
4181
4360
  setStatus("reading", `Reading ${options.file}...`);
4182
- const filePath = join8(cwd, options.file);
4361
+ const filePath = isAbsolute(options.file) ? options.file : resolve(cwd, options.file);
4183
4362
  if (!existsSync8(filePath)) {
4184
- setError(`File not found: ${options.file}`);
4363
+ setError(`File not found: ${filePath}`);
4185
4364
  return;
4186
4365
  }
4187
4366
  try {
@@ -4278,9 +4457,16 @@ To create this branch, go to the IntlPull dashboard.`
4278
4457
  for (let langIdx = 0; langIdx < allLangResult.languages.length; langIdx++) {
4279
4458
  const langData = allLangResult.languages[langIdx];
4280
4459
  updateLanguageResult(langIdx, { status: "pushing" });
4460
+ let namespacesToPush2 = langData.namespaces;
4461
+ if (options.translationKeyPrefix) {
4462
+ namespacesToPush2 = langData.namespaces.map((ns) => ({
4463
+ ...ns,
4464
+ keys: applyPrefix(ns.keys, options.translationKeyPrefix)
4465
+ }));
4466
+ }
4281
4467
  const { results: results2, totals: totals2 } = await pushKeysParallel(
4282
4468
  projectId,
4283
- langData.namespaces,
4469
+ namespacesToPush2,
4284
4470
  langData.language,
4285
4471
  {
4286
4472
  branchId,
@@ -4377,7 +4563,14 @@ To create this branch, go to the IntlPull dashboard.`
4377
4563
  return;
4378
4564
  }
4379
4565
  setStatus("pushing", `Pushing ${totalKeys} keys to IntlPull...`);
4380
- const { results, totals } = await pushKeysParallel(projectId, namespaces, sourceLanguage, {
4566
+ let namespacesToPush = namespaces;
4567
+ if (options.translationKeyPrefix) {
4568
+ namespacesToPush = namespaces.map((ns) => ({
4569
+ ...ns,
4570
+ keys: applyPrefix(ns.keys, options.translationKeyPrefix)
4571
+ }));
4572
+ }
4573
+ const { results, totals } = await pushKeysParallel(projectId, namespacesToPush, sourceLanguage, {
4381
4574
  branchId,
4382
4575
  platforms: options.platform && options.platform !== "all" ? [options.platform] : void 0
4383
4576
  });
@@ -4405,7 +4598,7 @@ To create this branch, go to the IntlPull dashboard.`
4405
4598
  complete(results.some((r) => r.error));
4406
4599
  }, [options, limitState]);
4407
4600
  useEffect7(() => {
4408
- runPush2();
4601
+ runPush();
4409
4602
  }, []);
4410
4603
  useEffect7(() => {
4411
4604
  if (userConfirmed && state.status === "waiting_confirmation" && state.pushContext) {
@@ -4427,13 +4620,27 @@ To create this branch, go to the IntlPull dashboard.`
4427
4620
  const sourceLanguageData = allLangResult.languages.find((l) => l.language === sourceLanguage);
4428
4621
  if (sourceLanguageData) {
4429
4622
  const { limited } = calculateUploadableKeys(sourceLanguageData.namespaces, availableSlots);
4430
- const { results, totals } = await pushKeysParallel(projectId, limited, sourceLanguage, {
4623
+ let limitedToPost = limited;
4624
+ if (options.translationKeyPrefix) {
4625
+ limitedToPost = limited.map((ns) => ({
4626
+ ...ns,
4627
+ keys: applyPrefix(ns.keys, options.translationKeyPrefix)
4628
+ }));
4629
+ }
4630
+ const { results, totals } = await pushKeysParallel(projectId, limitedToPost, sourceLanguage, {
4431
4631
  branchId,
4432
4632
  platforms: platform && platform !== "all" ? [platform] : void 0
4433
4633
  });
4434
4634
  for (const langData of allLangResult.languages) {
4435
4635
  if (langData.language === sourceLanguage) continue;
4436
- await pushKeysParallel(projectId, langData.namespaces, langData.language, {
4636
+ let namespacesToPush = langData.namespaces;
4637
+ if (options.translationKeyPrefix) {
4638
+ namespacesToPush = langData.namespaces.map((ns) => ({
4639
+ ...ns,
4640
+ keys: applyPrefix(ns.keys, options.translationKeyPrefix)
4641
+ }));
4642
+ }
4643
+ await pushKeysParallel(projectId, namespacesToPush, langData.language, {
4437
4644
  branchId,
4438
4645
  platforms: platform && platform !== "all" ? [platform] : void 0
4439
4646
  });
@@ -4442,7 +4649,14 @@ To create this branch, go to the IntlPull dashboard.`
4442
4649
  }
4443
4650
  } else if (namespaces) {
4444
4651
  const { limited, totalKeys } = calculateUploadableKeys(namespaces, availableSlots);
4445
- const { results } = await pushKeysParallel(projectId, limited, sourceLanguage, {
4652
+ let limitedToPost = limited;
4653
+ if (options.translationKeyPrefix) {
4654
+ limitedToPost = limited.map((ns) => ({
4655
+ ...ns,
4656
+ keys: applyPrefix(ns.keys, options.translationKeyPrefix)
4657
+ }));
4658
+ }
4659
+ const { results } = await pushKeysParallel(projectId, limitedToPost, sourceLanguage, {
4446
4660
  branchId,
4447
4661
  platforms: platform && platform !== "all" ? [platform] : void 0
4448
4662
  });
@@ -4510,9 +4724,9 @@ To create this branch, go to the IntlPull dashboard.`
4510
4724
  );
4511
4725
  }
4512
4726
 
4513
- // src/commands/push.tsx
4727
+ // src/commands/upload.tsx
4514
4728
  import { jsx as jsx17 } from "react/jsx-runtime";
4515
- function runPush(options) {
4729
+ function runUpload(options) {
4516
4730
  render5(/* @__PURE__ */ jsx17(PushComponent, { options }));
4517
4731
  }
4518
4732
 
@@ -4589,7 +4803,20 @@ function ExportComponent({ options }) {
4589
4803
  platform: options.platform
4590
4804
  };
4591
4805
  const blob = await api.exportTranslations(projectId, exportOptions);
4592
- const extension = format === "android" ? "zip" : format === "ios" ? "zip" : format;
4806
+ const extensionMap = {
4807
+ json: "json",
4808
+ yaml: "yaml",
4809
+ ts: "ts",
4810
+ android: "zip",
4811
+ ios: "zip",
4812
+ xliff: "xliff",
4813
+ po: "po",
4814
+ arb: "arb",
4815
+ stringsdict: "stringsdict",
4816
+ xcstrings: "xcstrings",
4817
+ csv: "csv"
4818
+ };
4819
+ const extension = extensionMap[format] || format;
4593
4820
  const outputFile = options.output || `translations.${extension}`;
4594
4821
  setState({
4595
4822
  status: "saving",
@@ -4684,11 +4911,44 @@ function detectFormatFromExtension(filename) {
4684
4911
  case ".yaml":
4685
4912
  case ".yml":
4686
4913
  return "yaml";
4914
+ case ".xliff":
4915
+ case ".xlf":
4916
+ return "xliff";
4917
+ case ".po":
4918
+ case ".pot":
4919
+ return "po";
4920
+ case ".arb":
4921
+ return "arb";
4922
+ case ".stringsdict":
4923
+ return "stringsdict";
4924
+ case ".xcstrings":
4925
+ return "xcstrings";
4926
+ case ".csv":
4927
+ return "csv";
4928
+ case ".strings":
4929
+ return "ios";
4930
+ case ".xml":
4931
+ return "android";
4687
4932
  case ".json":
4688
4933
  default:
4689
4934
  return "json";
4690
4935
  }
4691
4936
  }
4937
+ var SUPPORTED_EXTENSIONS = [
4938
+ ".json",
4939
+ ".yaml",
4940
+ ".yml",
4941
+ ".xliff",
4942
+ ".xlf",
4943
+ ".po",
4944
+ ".pot",
4945
+ ".arb",
4946
+ ".stringsdict",
4947
+ ".xcstrings",
4948
+ ".csv",
4949
+ ".strings",
4950
+ ".xml"
4951
+ ];
4692
4952
  function findTranslationFiles(dir, pattern) {
4693
4953
  const files = [];
4694
4954
  try {
@@ -4696,7 +4956,7 @@ function findTranslationFiles(dir, pattern) {
4696
4956
  for (const entry of entries) {
4697
4957
  if (entry.isFile()) {
4698
4958
  const ext = extname4(entry.name).toLowerCase();
4699
- if ([".json", ".yaml", ".yml"].includes(ext)) {
4959
+ if (SUPPORTED_EXTENSIONS.includes(ext)) {
4700
4960
  if (pattern) {
4701
4961
  const regex = new RegExp(pattern.replace(/\*/g, ".*").replace(/\?/g, "."));
4702
4962
  if (!regex.test(entry.name)) continue;
@@ -5812,7 +6072,7 @@ var LokaliseMigrator = class {
5812
6072
  hasMore = keys.length === limit;
5813
6073
  page++;
5814
6074
  if (hasMore) {
5815
- await new Promise((resolve) => setTimeout(resolve, 200));
6075
+ await new Promise((resolve2) => setTimeout(resolve2, 200));
5816
6076
  }
5817
6077
  }
5818
6078
  return allKeys;
@@ -5881,7 +6141,7 @@ var LokaliseMigrator = class {
5881
6141
  hasMore = entries.length === limit;
5882
6142
  page++;
5883
6143
  if (hasMore) {
5884
- await new Promise((resolve) => setTimeout(resolve, 200));
6144
+ await new Promise((resolve2) => setTimeout(resolve2, 200));
5885
6145
  }
5886
6146
  }
5887
6147
  } catch {
@@ -5929,7 +6189,7 @@ var LokaliseMigrator = class {
5929
6189
  hasMore = terms.length === limit;
5930
6190
  page++;
5931
6191
  if (hasMore) {
5932
- await new Promise((resolve) => setTimeout(resolve, 200));
6192
+ await new Promise((resolve2) => setTimeout(resolve2, 200));
5933
6193
  }
5934
6194
  }
5935
6195
  }
@@ -6148,7 +6408,7 @@ var CrowdinMigrator = class {
6148
6408
  hasMore = strings.length === limit;
6149
6409
  offset += limit;
6150
6410
  if (hasMore) {
6151
- await new Promise((resolve) => setTimeout(resolve, 100));
6411
+ await new Promise((resolve2) => setTimeout(resolve2, 100));
6152
6412
  }
6153
6413
  }
6154
6414
  return allKeys;
@@ -6246,7 +6506,7 @@ var CrowdinMigrator = class {
6246
6506
  hasMore = segments.length === limit;
6247
6507
  offset += limit;
6248
6508
  if (hasMore) {
6249
- await new Promise((resolve) => setTimeout(resolve, 100));
6509
+ await new Promise((resolve2) => setTimeout(resolve2, 100));
6250
6510
  }
6251
6511
  } catch {
6252
6512
  hasMore = false;
@@ -6294,7 +6554,7 @@ var CrowdinMigrator = class {
6294
6554
  hasMore = terms.length === limit;
6295
6555
  offset += limit;
6296
6556
  if (hasMore) {
6297
- await new Promise((resolve) => setTimeout(resolve, 100));
6557
+ await new Promise((resolve2) => setTimeout(resolve2, 100));
6298
6558
  }
6299
6559
  }
6300
6560
  }
@@ -6482,7 +6742,7 @@ var PhraseMigrator = class {
6482
6742
  hasMore = projects2.length === perPage;
6483
6743
  page++;
6484
6744
  if (hasMore) {
6485
- await new Promise((resolve) => setTimeout(resolve, 200));
6745
+ await new Promise((resolve2) => setTimeout(resolve2, 200));
6486
6746
  }
6487
6747
  }
6488
6748
  return allProjects;
@@ -6538,7 +6798,7 @@ var PhraseMigrator = class {
6538
6798
  hasMore = keys.length === perPage;
6539
6799
  page++;
6540
6800
  if (hasMore) {
6541
- await new Promise((resolve) => setTimeout(resolve, 300));
6801
+ await new Promise((resolve2) => setTimeout(resolve2, 300));
6542
6802
  }
6543
6803
  }
6544
6804
  return allKeys;
@@ -6624,7 +6884,7 @@ var PhraseMigrator = class {
6624
6884
  hasMore = terms.length === perPage;
6625
6885
  page++;
6626
6886
  if (hasMore) {
6627
- await new Promise((resolve) => setTimeout(resolve, 200));
6887
+ await new Promise((resolve2) => setTimeout(resolve2, 200));
6628
6888
  }
6629
6889
  }
6630
6890
  }
@@ -7747,7 +8007,7 @@ Make sure your files follow naming conventions like:
7747
8007
  });
7748
8008
  try {
7749
8009
  if (optionsDryRun) {
7750
- await new Promise((resolve) => setTimeout(resolve, 500));
8010
+ await new Promise((resolve2) => setTimeout(resolve2, 500));
7751
8011
  setUploadStatuses((prev) => {
7752
8012
  const updated = [...prev];
7753
8013
  updated[index] = {
@@ -8039,7 +8299,7 @@ function runMigrateFiles(path4, options) {
8039
8299
  // src/commands/check.tsx
8040
8300
  import { useState as useState15, useEffect as useEffect12 } from "react";
8041
8301
  import { render as render10, Box as Box27, Text as Text28 } from "ink";
8042
- import { glob } from "glob";
8302
+ import { glob as glob2 } from "glob";
8043
8303
  import { readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
8044
8304
 
8045
8305
  // src/components/TaskList.tsx
@@ -8180,7 +8440,7 @@ function CheckCommand({ options }) {
8180
8440
  ];
8181
8441
  let localeFiles = [];
8182
8442
  for (const pattern of patterns) {
8183
- const files = await glob(pattern, { absolute: true });
8443
+ const files = await glob2(pattern, { absolute: true });
8184
8444
  if (files.length > 0) {
8185
8445
  localeFiles = files;
8186
8446
  break;
@@ -8776,7 +9036,7 @@ function runDiff(options) {
8776
9036
  import { useState as useState17, useEffect as useEffect14 } from "react";
8777
9037
  import { render as render12, Box as Box29, Text as Text30 } from "ink";
8778
9038
  import { readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
8779
- import { glob as glob2 } from "glob";
9039
+ import { glob as glob3 } from "glob";
8780
9040
  import { jsx as jsx33, jsxs as jsxs30 } from "react/jsx-runtime";
8781
9041
  function FixCommand({ options }) {
8782
9042
  const [tasks, setTasks] = useState17([
@@ -8816,7 +9076,7 @@ function FixCommand({ options }) {
8816
9076
  ];
8817
9077
  let localeFiles = [];
8818
9078
  for (const pattern of patterns) {
8819
- const files = await glob2(pattern, { absolute: true });
9079
+ const files = await glob3(pattern, { absolute: true });
8820
9080
  if (files.length > 0) {
8821
9081
  localeFiles = files;
8822
9082
  break;
@@ -11319,90 +11579,2753 @@ documentsCommand.command("download").description("Download a translated document
11319
11579
  });
11320
11580
  });
11321
11581
 
11322
- // src/index.tsx
11323
- var program = new Command2();
11324
- program.name("intlpull").description("Intelligent i18n CLI for modern apps").version("0.1.5").option("--env-file <path>", "Path to custom env file (e.g., .env.production)").hook("preAction", (thisCommand) => {
11325
- const envFile = thisCommand.opts().envFile;
11326
- if (envFile) {
11327
- setCustomEnvFile(envFile);
11328
- }
11329
- const { loaded } = loadEnvFiles();
11330
- if (loaded.length > 0 && process.env.INTLPULL_DEBUG) {
11331
- console.log(`Loaded env from: ${loaded.join(", ")}`);
11582
+ // src/commands/keys/index.tsx
11583
+ import { Command as Command2 } from "commander";
11584
+
11585
+ // src/commands/keys/list.tsx
11586
+ import { useState as useState26, useEffect as useEffect23 } from "react";
11587
+ import { render as render21, Box as Box36, Text as Text39, Newline as Newline2 } from "ink";
11588
+ import Spinner18 from "ink-spinner";
11589
+ import { jsx as jsx42, jsxs as jsxs39 } from "react/jsx-runtime";
11590
+ function SimpleTable2({ data }) {
11591
+ if (data.length === 0) return null;
11592
+ const headers = Object.keys(data[0]);
11593
+ const colWidths = headers.map(
11594
+ (h) => Math.max(h.length, ...data.map((row) => String(row[h] || "").length))
11595
+ );
11596
+ const separator = "\u2500".repeat(colWidths.reduce((a, b) => a + b + 3, 1));
11597
+ return /* @__PURE__ */ jsxs39(Box36, { flexDirection: "column", children: [
11598
+ /* @__PURE__ */ jsx42(Text39, { children: separator }),
11599
+ /* @__PURE__ */ jsxs39(Text39, { children: [
11600
+ "\u2502 ",
11601
+ headers.map((h, i) => h.padEnd(colWidths[i])).join(" \u2502 "),
11602
+ " \u2502"
11603
+ ] }),
11604
+ /* @__PURE__ */ jsx42(Text39, { children: separator }),
11605
+ data.map((row, rowIdx) => /* @__PURE__ */ jsxs39(Text39, { children: [
11606
+ "\u2502 ",
11607
+ headers.map((h, i) => String(row[h] || "").padEnd(colWidths[i])).join(" \u2502 "),
11608
+ " \u2502"
11609
+ ] }, rowIdx)),
11610
+ /* @__PURE__ */ jsx42(Text39, { children: separator })
11611
+ ] });
11612
+ }
11613
+ var KeysList = ({ options }) => {
11614
+ const [keys, setKeys] = useState26([]);
11615
+ const [isLoading, setIsLoading] = useState26(true);
11616
+ const [error, setError] = useState26(null);
11617
+ useEffect23(() => {
11618
+ const fetchKeys = async () => {
11619
+ try {
11620
+ const config = getProjectConfig();
11621
+ const projectId = options.project || config?.projectId;
11622
+ if (!projectId) {
11623
+ setError("Project ID is required. Use --project <id> or run from a linked directory.");
11624
+ setIsLoading(false);
11625
+ return;
11626
+ }
11627
+ const api = new KeysApi();
11628
+ const tags = options.tags ? options.tags.split(",").map((t) => t.trim()) : void 0;
11629
+ const limit = options.limit ? parseInt(options.limit, 10) : void 0;
11630
+ const result = await api.listKeys(projectId, {
11631
+ namespace: options.namespace,
11632
+ search: options.search,
11633
+ tags,
11634
+ limit
11635
+ });
11636
+ setKeys(result.keys);
11637
+ } catch (err) {
11638
+ setError(err instanceof Error ? err.message : "Failed to fetch keys");
11639
+ } finally {
11640
+ setIsLoading(false);
11641
+ }
11642
+ };
11643
+ fetchKeys();
11644
+ }, [options]);
11645
+ if (error) {
11646
+ return /* @__PURE__ */ jsxs39(Text39, { color: "red", children: [
11647
+ "Error: ",
11648
+ error
11649
+ ] });
11332
11650
  }
11333
- });
11334
- program.command("init").description("Initialize IntlPull in your project").option("--framework <framework>", "Framework (next|react|vue|svelte|astro)").option("--library <library>", "i18n library (next-intl|react-i18next|vue-i18n)").option("--output <dir>", "Output directory for translation files").option("--project <id>", "Project ID to link").option("-y, --yes", "Auto-detect and initialize without prompts (non-interactive mode)").action((options) => {
11335
- runInit({
11336
- framework: options.framework,
11337
- library: options.library,
11338
- output: options.output,
11339
- project: options.project,
11340
- yes: options.yes
11341
- });
11342
- });
11343
- program.command("status").description("Show project status and translation progress").action(() => {
11344
- runStatus();
11345
- });
11346
- program.command("login").description("Authenticate with IntlPull").option("--token <token>", "Use API token (prefer INTLPULL_API_KEY env var for security)").action((options) => {
11347
- runLogin({ token: options.token });
11348
- });
11349
- program.command("logout").description("Clear stored credentials").action(async () => {
11350
- const { clearAuthConfig } = await import("./config-DX34FMWV.js");
11351
- clearAuthConfig();
11352
- console.log("\u2713 Logged out successfully");
11353
- });
11354
- program.command("whoami").description("Show current authenticated user").action(async () => {
11355
- const { getResolvedApiKey: getResolvedApiKey2, getAuthErrorMessage: getAuthErrorMessage2 } = await import("./config-DX34FMWV.js");
11356
- const { createApiClient: createApiClient2 } = await import("./api-K3AY4VAI.js");
11357
- const resolved = getResolvedApiKey2();
11358
- if (!resolved) {
11359
- console.log(getAuthErrorMessage2());
11360
- return;
11651
+ if (isLoading) {
11652
+ return /* @__PURE__ */ jsxs39(Text39, { children: [
11653
+ /* @__PURE__ */ jsx42(Text39, { color: "green", children: /* @__PURE__ */ jsx42(Spinner18, { type: "dots" }) }),
11654
+ " Loading keys..."
11655
+ ] });
11361
11656
  }
11362
- try {
11363
- const api = createApiClient2();
11364
- const { projects: projects2 } = await api.listProjects();
11365
- const sourceLabel = resolved.source === "env" ? "INTLPULL_API_KEY env var" : "~/.intlpull/auth.json";
11366
- console.log("\n\u2713 Authenticated successfully\n");
11367
- console.log(" API Key:", resolved.key.substring(0, 20) + "...");
11368
- console.log(" Source:", sourceLabel);
11369
- console.log(" Projects:", projects2.length);
11370
- if (projects2.length > 0) {
11371
- console.log("\n Your projects:");
11372
- for (const p of projects2.slice(0, 5)) {
11373
- console.log(` - ${p.name} (${p.slug})`);
11374
- }
11375
- if (projects2.length > 5) {
11376
- console.log(` ... and ${projects2.length - 5} more`);
11377
- }
11378
- }
11379
- console.log("");
11380
- } catch (err) {
11381
- console.log("API Key:", resolved.key.substring(0, 15) + "...");
11382
- console.error("Error verifying API key:", err instanceof Error ? err.message : "Unknown error");
11657
+ if (keys.length === 0) {
11658
+ return /* @__PURE__ */ jsx42(Text39, { children: "No keys found." });
11383
11659
  }
11384
- });
11385
- program.command("upload").alias("push").description("Upload translation keys to IntlPull (auto-detects project and files)").option("--project <id>", "Project ID (auto-detected with project-scoped API key)").option("--file <path>", "Specific file to upload (e.g., messages/admin.json)").option("--branch <name>", "Branch name (auto-detected from git, pushes to IntlPull branch)").option("--platform <platform>", "Platform to tag keys with (ios, android, web)").option("--all-languages", "Upload translations for ALL languages (default: true)", true).option("--source-only", "Upload only source language keys", false).option("--dry-run", "Preview without uploading", false).option("-v, --verbose", "Show detailed detection info", false).option("-q, --quiet", "Suppress output except errors (CI mode)").action((options) => {
11386
- runPush({
11387
- project: options.project,
11388
- file: options.file,
11389
- branch: options.branch,
11390
- platform: options.platform,
11391
- // --source-only overrides --all-languages
11392
- allLanguages: options.sourceOnly ? false : options.allLanguages,
11393
- dryRun: options.dryRun,
11394
- verbose: options.verbose,
11395
- quiet: options.quiet
11396
- });
11397
- });
11398
- program.command("download").alias("pull").description("Download translations from IntlPull (auto-detects project, framework, and languages)").option("--project <id>", "Project ID (auto-detected with project-scoped API key)").option("--format <format>", "Output format (json|yaml|ts)", "json").option("--output <dir>", "Output directory (auto-detected from framework)").option("--languages <langs>", "Languages to download (comma-separated, defaults to all)").option("--branch <branch>", "Translation branch to download from").option("--platform <platform>", "Platform variant (default|ios|android|web)").option("--no-parallel", "Disable parallel fetching (use single request)").option("-i, --interactive", "Run in interactive mode with prompts").option("-q, --quiet", "Suppress output except errors").option("--clean-orphans [mode]", 'Remove orphaned files (files not in remote). Use "dry-run" to preview').option("-f, --force", "Skip confirmation prompts for destructive operations").action((options) => {
11399
- let cleanOrphans;
11400
- if (options.cleanOrphans === true || options.cleanOrphans === "") {
11401
- cleanOrphans = true;
11402
- } else if (options.cleanOrphans === "dry-run") {
11403
- cleanOrphans = "dry-run";
11660
+ if (options.format === "json") {
11661
+ console.log(JSON.stringify(keys, null, 2));
11662
+ return null;
11404
11663
  }
11405
- runPull({
11664
+ const data = keys.map((key) => ({
11665
+ Key: key.key,
11666
+ Namespace: key.namespace || "-",
11667
+ Description: key.description ? key.description.length > 40 ? key.description.substring(0, 37) + "..." : key.description : "-",
11668
+ Tags: key.tags && key.tags.length > 0 ? key.tags.join(", ") : "-"
11669
+ }));
11670
+ return /* @__PURE__ */ jsxs39(Box36, { flexDirection: "column", children: [
11671
+ /* @__PURE__ */ jsxs39(Text39, { children: [
11672
+ "Found ",
11673
+ keys.length,
11674
+ " key(s)"
11675
+ ] }),
11676
+ /* @__PURE__ */ jsx42(Newline2, {}),
11677
+ /* @__PURE__ */ jsx42(SimpleTable2, { data })
11678
+ ] });
11679
+ };
11680
+ function runKeysList(options) {
11681
+ render21(/* @__PURE__ */ jsx42(KeysList, { options }));
11682
+ }
11683
+
11684
+ // src/commands/keys/create.tsx
11685
+ import { useState as useState27, useEffect as useEffect24 } from "react";
11686
+ import { render as render22, Box as Box37, Text as Text40 } from "ink";
11687
+ import Spinner19 from "ink-spinner";
11688
+ import { jsx as jsx43, jsxs as jsxs40 } from "react/jsx-runtime";
11689
+ var KeysCreate = ({ keyName, options }) => {
11690
+ const [result, setResult] = useState27(null);
11691
+ const [isLoading, setIsLoading] = useState27(true);
11692
+ const [error, setError] = useState27(null);
11693
+ useEffect24(() => {
11694
+ const createKey = async () => {
11695
+ try {
11696
+ const config = getProjectConfig();
11697
+ const projectId = options.project || config?.projectId;
11698
+ if (!projectId) {
11699
+ setError("Project ID is required. Use --project <id> or run from a linked directory.");
11700
+ setIsLoading(false);
11701
+ return;
11702
+ }
11703
+ if (!options.value) {
11704
+ setError("Value is required. Use --value <translation>");
11705
+ setIsLoading(false);
11706
+ return;
11707
+ }
11708
+ const api = new KeysApi();
11709
+ const tags = options.tags ? options.tags.split(",").map((t) => t.trim()) : void 0;
11710
+ const { ProjectsApi: ProjectsApi2 } = await import("./projects-AMQMORAR.js");
11711
+ const projectsApi = new ProjectsApi2();
11712
+ const project = await projectsApi.getProject(projectId);
11713
+ const created = await api.createKey(projectId, {
11714
+ namespace: options.namespace || "default",
11715
+ key: keyName,
11716
+ value: options.value,
11717
+ language: project.source_language,
11718
+ context: options.context,
11719
+ tags
11720
+ });
11721
+ setResult(created);
11722
+ } catch (err) {
11723
+ setError(err instanceof Error ? err.message : "Failed to create key");
11724
+ } finally {
11725
+ setIsLoading(false);
11726
+ }
11727
+ };
11728
+ createKey();
11729
+ }, [keyName, options]);
11730
+ if (error) {
11731
+ return /* @__PURE__ */ jsxs40(Text40, { color: "red", children: [
11732
+ "Error: ",
11733
+ error
11734
+ ] });
11735
+ }
11736
+ if (isLoading) {
11737
+ return /* @__PURE__ */ jsxs40(Text40, { children: [
11738
+ /* @__PURE__ */ jsx43(Text40, { color: "green", children: /* @__PURE__ */ jsx43(Spinner19, { type: "dots" }) }),
11739
+ " Creating key..."
11740
+ ] });
11741
+ }
11742
+ if (!result) {
11743
+ return /* @__PURE__ */ jsx43(Text40, { color: "red", children: "Failed to create key" });
11744
+ }
11745
+ return /* @__PURE__ */ jsxs40(Box37, { flexDirection: "column", children: [
11746
+ /* @__PURE__ */ jsx43(Text40, { color: "green", children: "\u2713 Key created successfully" }),
11747
+ /* @__PURE__ */ jsxs40(Text40, { children: [
11748
+ "Key: ",
11749
+ result.key
11750
+ ] }),
11751
+ /* @__PURE__ */ jsxs40(Text40, { children: [
11752
+ "Namespace: ",
11753
+ result.namespace || "-"
11754
+ ] }),
11755
+ result.description && /* @__PURE__ */ jsxs40(Text40, { children: [
11756
+ "Description: ",
11757
+ result.description
11758
+ ] }),
11759
+ result.tags && result.tags.length > 0 && /* @__PURE__ */ jsxs40(Text40, { children: [
11760
+ "Tags: ",
11761
+ result.tags.join(", ")
11762
+ ] })
11763
+ ] });
11764
+ };
11765
+ function runKeysCreate(keyName, options) {
11766
+ render22(/* @__PURE__ */ jsx43(KeysCreate, { keyName, options }));
11767
+ }
11768
+
11769
+ // src/commands/keys/update.tsx
11770
+ import { useState as useState28, useEffect as useEffect25 } from "react";
11771
+ import { render as render23, Box as Box38, Text as Text41 } from "ink";
11772
+ import Spinner20 from "ink-spinner";
11773
+ import { jsx as jsx44, jsxs as jsxs41 } from "react/jsx-runtime";
11774
+ var KeysUpdate = ({ keyName, options }) => {
11775
+ const [result, setResult] = useState28(null);
11776
+ const [isLoading, setIsLoading] = useState28(true);
11777
+ const [error, setError] = useState28(null);
11778
+ useEffect25(() => {
11779
+ const updateKey = async () => {
11780
+ try {
11781
+ const config = getProjectConfig();
11782
+ const projectId = options.project || config?.projectId;
11783
+ if (!projectId) {
11784
+ setError("Project ID is required. Use --project <id> or run from a linked directory.");
11785
+ setIsLoading(false);
11786
+ return;
11787
+ }
11788
+ const api = new KeysApi();
11789
+ const keysResult = await api.listKeys(projectId, { search: keyName });
11790
+ const existingKey = keysResult.keys.find((k) => k.key === keyName);
11791
+ if (!existingKey) {
11792
+ setError(`Key "${keyName}" not found`);
11793
+ setIsLoading(false);
11794
+ return;
11795
+ }
11796
+ const updateData = {};
11797
+ if (options.context !== void 0) {
11798
+ updateData.context = options.context;
11799
+ }
11800
+ if (options.tags !== void 0) {
11801
+ updateData.tags = options.tags.split(",").map((t) => t.trim());
11802
+ }
11803
+ if (Object.keys(updateData).length === 0) {
11804
+ setError("No update fields provided. Use --context or --tags");
11805
+ setIsLoading(false);
11806
+ return;
11807
+ }
11808
+ const updated = await api.updateKey(projectId, existingKey.id, updateData);
11809
+ setResult(updated);
11810
+ } catch (err) {
11811
+ setError(err instanceof Error ? err.message : "Failed to update key");
11812
+ } finally {
11813
+ setIsLoading(false);
11814
+ }
11815
+ };
11816
+ updateKey();
11817
+ }, [keyName, options]);
11818
+ if (error) {
11819
+ return /* @__PURE__ */ jsxs41(Text41, { color: "red", children: [
11820
+ "Error: ",
11821
+ error
11822
+ ] });
11823
+ }
11824
+ if (isLoading) {
11825
+ return /* @__PURE__ */ jsxs41(Text41, { children: [
11826
+ /* @__PURE__ */ jsx44(Text41, { color: "green", children: /* @__PURE__ */ jsx44(Spinner20, { type: "dots" }) }),
11827
+ " Updating key..."
11828
+ ] });
11829
+ }
11830
+ if (!result) {
11831
+ return /* @__PURE__ */ jsx44(Text41, { color: "red", children: "Failed to update key" });
11832
+ }
11833
+ return /* @__PURE__ */ jsxs41(Box38, { flexDirection: "column", children: [
11834
+ /* @__PURE__ */ jsx44(Text41, { color: "green", children: "\u2713 Key updated successfully" }),
11835
+ /* @__PURE__ */ jsxs41(Text41, { children: [
11836
+ "Key: ",
11837
+ result.key
11838
+ ] }),
11839
+ /* @__PURE__ */ jsxs41(Text41, { children: [
11840
+ "Namespace: ",
11841
+ result.namespace || "-"
11842
+ ] }),
11843
+ result.description && /* @__PURE__ */ jsxs41(Text41, { children: [
11844
+ "Description: ",
11845
+ result.description
11846
+ ] }),
11847
+ result.tags && result.tags.length > 0 && /* @__PURE__ */ jsxs41(Text41, { children: [
11848
+ "Tags: ",
11849
+ result.tags.join(", ")
11850
+ ] })
11851
+ ] });
11852
+ };
11853
+ function runKeysUpdate(keyName, options) {
11854
+ render23(/* @__PURE__ */ jsx44(KeysUpdate, { keyName, options }));
11855
+ }
11856
+
11857
+ // src/commands/keys/delete.tsx
11858
+ import { useState as useState29, useEffect as useEffect26 } from "react";
11859
+ import { render as render24, Box as Box39, Text as Text42, useInput as useInput8 } from "ink";
11860
+ import Spinner21 from "ink-spinner";
11861
+ import { jsx as jsx45, jsxs as jsxs42 } from "react/jsx-runtime";
11862
+ var KeysDelete = ({ keyName, options }) => {
11863
+ const [key, setKey] = useState29(null);
11864
+ const [isLoading, setIsLoading] = useState29(true);
11865
+ const [error, setError] = useState29(null);
11866
+ const [needsConfirmation, setNeedsConfirmation] = useState29(!options.force);
11867
+ const [isDeleting, setIsDeleting] = useState29(false);
11868
+ const [deleted, setDeleted] = useState29(false);
11869
+ useEffect26(() => {
11870
+ const findKey = async () => {
11871
+ try {
11872
+ const config = getProjectConfig();
11873
+ const projectId = options.project || config?.projectId;
11874
+ if (!projectId) {
11875
+ setError("Project ID is required. Use --project <id> or run from a linked directory.");
11876
+ setIsLoading(false);
11877
+ return;
11878
+ }
11879
+ const api = new KeysApi();
11880
+ const keysResult = await api.listKeys(projectId, { search: keyName });
11881
+ const existingKey = keysResult.keys.find((k) => k.key === keyName);
11882
+ if (!existingKey) {
11883
+ setError(`Key "${keyName}" not found`);
11884
+ setIsLoading(false);
11885
+ return;
11886
+ }
11887
+ setKey(existingKey);
11888
+ setIsLoading(false);
11889
+ if (options.force) {
11890
+ await performDelete(projectId, existingKey.id, api);
11891
+ }
11892
+ } catch (err) {
11893
+ setError(err instanceof Error ? err.message : "Failed to find key");
11894
+ setIsLoading(false);
11895
+ }
11896
+ };
11897
+ findKey();
11898
+ }, [keyName, options.force, options.project]);
11899
+ const performDelete = async (projectId, keyId, api) => {
11900
+ try {
11901
+ setIsDeleting(true);
11902
+ await api.deleteKey(projectId, keyId);
11903
+ setDeleted(true);
11904
+ setNeedsConfirmation(false);
11905
+ } catch (err) {
11906
+ setError(err instanceof Error ? err.message : "Failed to delete key");
11907
+ } finally {
11908
+ setIsDeleting(false);
11909
+ }
11910
+ };
11911
+ useInput8((input, inputKey) => {
11912
+ if (!needsConfirmation || isDeleting) return;
11913
+ if (input.toLowerCase() === "y") {
11914
+ const config = getProjectConfig();
11915
+ const projectId = options.project || config?.projectId;
11916
+ if (projectId && key) {
11917
+ const api = new KeysApi();
11918
+ performDelete(projectId, key.id, api);
11919
+ }
11920
+ } else if (input.toLowerCase() === "n" || inputKey.escape) {
11921
+ process.exit(0);
11922
+ }
11923
+ });
11924
+ if (error) {
11925
+ return /* @__PURE__ */ jsxs42(Text42, { color: "red", children: [
11926
+ "Error: ",
11927
+ error
11928
+ ] });
11929
+ }
11930
+ if (isLoading) {
11931
+ return /* @__PURE__ */ jsxs42(Text42, { children: [
11932
+ /* @__PURE__ */ jsx45(Text42, { color: "green", children: /* @__PURE__ */ jsx45(Spinner21, { type: "dots" }) }),
11933
+ " Finding key..."
11934
+ ] });
11935
+ }
11936
+ if (isDeleting) {
11937
+ return /* @__PURE__ */ jsxs42(Text42, { children: [
11938
+ /* @__PURE__ */ jsx45(Text42, { color: "green", children: /* @__PURE__ */ jsx45(Spinner21, { type: "dots" }) }),
11939
+ " Deleting key..."
11940
+ ] });
11941
+ }
11942
+ if (deleted) {
11943
+ return /* @__PURE__ */ jsxs42(Box39, { flexDirection: "column", children: [
11944
+ /* @__PURE__ */ jsx45(Text42, { color: "green", children: "\u2713 Key deleted successfully" }),
11945
+ /* @__PURE__ */ jsxs42(Text42, { children: [
11946
+ "Key: ",
11947
+ keyName
11948
+ ] })
11949
+ ] });
11950
+ }
11951
+ if (needsConfirmation && key) {
11952
+ return /* @__PURE__ */ jsxs42(Box39, { flexDirection: "column", children: [
11953
+ /* @__PURE__ */ jsx45(Box39, { marginBottom: 1, children: /* @__PURE__ */ jsx45(Text42, { color: "yellow", children: "Warning: Delete Key" }) }),
11954
+ /* @__PURE__ */ jsxs42(Box39, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
11955
+ /* @__PURE__ */ jsx45(Text42, { children: "You are about to delete the following key:" }),
11956
+ /* @__PURE__ */ jsx45(Text42, { dimColor: true }),
11957
+ /* @__PURE__ */ jsxs42(Text42, { children: [
11958
+ " Key: ",
11959
+ /* @__PURE__ */ jsx45(Text42, { bold: true, children: key.key })
11960
+ ] }),
11961
+ /* @__PURE__ */ jsxs42(Text42, { children: [
11962
+ " Namespace: ",
11963
+ /* @__PURE__ */ jsx45(Text42, { bold: true, children: key.namespace || "-" })
11964
+ ] }),
11965
+ key.description && /* @__PURE__ */ jsxs42(Text42, { children: [
11966
+ " Description: ",
11967
+ key.description
11968
+ ] }),
11969
+ key.tags && key.tags.length > 0 && /* @__PURE__ */ jsxs42(Text42, { children: [
11970
+ " Tags: ",
11971
+ key.tags.join(", ")
11972
+ ] })
11973
+ ] }),
11974
+ /* @__PURE__ */ jsxs42(Box39, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
11975
+ /* @__PURE__ */ jsx45(Text42, { color: "red", children: "This will delete the key and all its translations." }),
11976
+ /* @__PURE__ */ jsx45(Text42, { dimColor: true, children: "This action cannot be undone." })
11977
+ ] }),
11978
+ /* @__PURE__ */ jsx45(Box39, { marginLeft: 2, children: /* @__PURE__ */ jsxs42(Text42, { children: [
11979
+ "Press",
11980
+ " ",
11981
+ /* @__PURE__ */ jsx45(Text42, { bold: true, color: "green", children: "Y" }),
11982
+ " ",
11983
+ "to confirm,",
11984
+ " ",
11985
+ /* @__PURE__ */ jsx45(Text42, { bold: true, color: "red", children: "N" }),
11986
+ " ",
11987
+ "to cancel"
11988
+ ] }) })
11989
+ ] });
11990
+ }
11991
+ return null;
11992
+ };
11993
+ function runKeysDelete(keyName, options) {
11994
+ render24(/* @__PURE__ */ jsx45(KeysDelete, { keyName, options }));
11995
+ }
11996
+
11997
+ // src/commands/keys/bulk-delete.tsx
11998
+ import { useState as useState30, useEffect as useEffect27 } from "react";
11999
+ import { render as render25, Box as Box40, Text as Text43, useInput as useInput9 } from "ink";
12000
+ import Spinner22 from "ink-spinner";
12001
+ import { jsx as jsx46, jsxs as jsxs43 } from "react/jsx-runtime";
12002
+ var KeysBulkDelete = ({ options }) => {
12003
+ const [keys, setKeys] = useState30([]);
12004
+ const [isLoading, setIsLoading] = useState30(true);
12005
+ const [error, setError] = useState30(null);
12006
+ const [needsConfirmation, setNeedsConfirmation] = useState30(!options.force);
12007
+ const [isDeleting, setIsDeleting] = useState30(false);
12008
+ const [deleted, setDeleted] = useState30(false);
12009
+ const [deletedCount, setDeletedCount] = useState30(0);
12010
+ useEffect27(() => {
12011
+ const findKeys = async () => {
12012
+ try {
12013
+ const config = getProjectConfig();
12014
+ const projectId = options.project || config?.projectId;
12015
+ if (!projectId) {
12016
+ setError("Project ID is required. Use --project <id> or run from a linked directory.");
12017
+ setIsLoading(false);
12018
+ return;
12019
+ }
12020
+ if (!options.query) {
12021
+ setError("Query is required. Use --query <search-term>");
12022
+ setIsLoading(false);
12023
+ return;
12024
+ }
12025
+ const api = new KeysApi();
12026
+ const keysResult = await api.listKeys(projectId, { search: options.query });
12027
+ if (keysResult.keys.length === 0) {
12028
+ setError(`No keys found matching query "${options.query}"`);
12029
+ setIsLoading(false);
12030
+ return;
12031
+ }
12032
+ setKeys(keysResult.keys);
12033
+ setIsLoading(false);
12034
+ if (options.dryRun) {
12035
+ return;
12036
+ }
12037
+ if (options.force) {
12038
+ await performBulkDelete(projectId, keysResult.keys.map((k) => k.id), api);
12039
+ }
12040
+ } catch (err) {
12041
+ setError(err instanceof Error ? err.message : "Failed to find keys");
12042
+ setIsLoading(false);
12043
+ }
12044
+ };
12045
+ findKeys();
12046
+ }, [options]);
12047
+ const performBulkDelete = async (projectId, keyIds, api) => {
12048
+ try {
12049
+ setIsDeleting(true);
12050
+ const result = await api.bulkDeleteKeys(projectId, keyIds);
12051
+ setDeletedCount(result.deleted);
12052
+ setDeleted(true);
12053
+ setNeedsConfirmation(false);
12054
+ } catch (err) {
12055
+ setError(err instanceof Error ? err.message : "Failed to delete keys");
12056
+ } finally {
12057
+ setIsDeleting(false);
12058
+ }
12059
+ };
12060
+ useInput9((input, inputKey) => {
12061
+ if (!needsConfirmation || isDeleting || options.dryRun) return;
12062
+ if (input.toLowerCase() === "y") {
12063
+ const config = getProjectConfig();
12064
+ const projectId = options.project || config?.projectId;
12065
+ if (projectId && keys.length > 0) {
12066
+ const api = new KeysApi();
12067
+ performBulkDelete(projectId, keys.map((k) => k.id), api);
12068
+ }
12069
+ } else if (input.toLowerCase() === "n" || inputKey.escape) {
12070
+ process.exit(0);
12071
+ }
12072
+ });
12073
+ if (error) {
12074
+ return /* @__PURE__ */ jsxs43(Text43, { color: "red", children: [
12075
+ "Error: ",
12076
+ error
12077
+ ] });
12078
+ }
12079
+ if (isLoading) {
12080
+ return /* @__PURE__ */ jsxs43(Text43, { children: [
12081
+ /* @__PURE__ */ jsx46(Text43, { color: "green", children: /* @__PURE__ */ jsx46(Spinner22, { type: "dots" }) }),
12082
+ " Searching for keys..."
12083
+ ] });
12084
+ }
12085
+ if (isDeleting) {
12086
+ return /* @__PURE__ */ jsxs43(Text43, { children: [
12087
+ /* @__PURE__ */ jsx46(Text43, { color: "green", children: /* @__PURE__ */ jsx46(Spinner22, { type: "dots" }) }),
12088
+ " Deleting ",
12089
+ keys.length,
12090
+ " key(s)..."
12091
+ ] });
12092
+ }
12093
+ if (deleted) {
12094
+ return /* @__PURE__ */ jsxs43(Box40, { flexDirection: "column", children: [
12095
+ /* @__PURE__ */ jsx46(Text43, { color: "green", children: "\u2713 Bulk delete completed successfully" }),
12096
+ /* @__PURE__ */ jsxs43(Text43, { children: [
12097
+ "Deleted ",
12098
+ deletedCount,
12099
+ " key(s)"
12100
+ ] })
12101
+ ] });
12102
+ }
12103
+ if (options.dryRun) {
12104
+ return /* @__PURE__ */ jsxs43(Box40, { flexDirection: "column", children: [
12105
+ /* @__PURE__ */ jsx46(Box40, { marginBottom: 1, children: /* @__PURE__ */ jsx46(Text43, { color: "cyan", children: "Dry Run Mode" }) }),
12106
+ /* @__PURE__ */ jsxs43(Box40, { flexDirection: "column", marginLeft: 2, children: [
12107
+ /* @__PURE__ */ jsxs43(Text43, { children: [
12108
+ "Found ",
12109
+ keys.length,
12110
+ ' key(s) matching query "',
12111
+ options.query,
12112
+ '":'
12113
+ ] }),
12114
+ /* @__PURE__ */ jsx46(Text43, { dimColor: true }),
12115
+ keys.slice(0, 10).map((key) => /* @__PURE__ */ jsxs43(Text43, { children: [
12116
+ " \u2022 ",
12117
+ key.key,
12118
+ " (",
12119
+ key.namespace || "default",
12120
+ ")"
12121
+ ] }, key.id)),
12122
+ keys.length > 10 && /* @__PURE__ */ jsxs43(Text43, { dimColor: true, children: [
12123
+ " ... and ",
12124
+ keys.length - 10,
12125
+ " more"
12126
+ ] })
12127
+ ] }),
12128
+ /* @__PURE__ */ jsx46(Box40, { marginTop: 1, marginLeft: 2, children: /* @__PURE__ */ jsx46(Text43, { dimColor: true, children: "Run without --dry-run to delete these keys." }) })
12129
+ ] });
12130
+ }
12131
+ if (needsConfirmation && keys.length > 0) {
12132
+ return /* @__PURE__ */ jsxs43(Box40, { flexDirection: "column", children: [
12133
+ /* @__PURE__ */ jsx46(Box40, { marginBottom: 1, children: /* @__PURE__ */ jsx46(Text43, { color: "yellow", children: "Warning: Bulk Delete" }) }),
12134
+ /* @__PURE__ */ jsxs43(Box40, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
12135
+ /* @__PURE__ */ jsxs43(Text43, { children: [
12136
+ "You are about to delete ",
12137
+ keys.length,
12138
+ ' key(s) matching query "',
12139
+ options.query,
12140
+ '":'
12141
+ ] }),
12142
+ /* @__PURE__ */ jsx46(Text43, { dimColor: true }),
12143
+ keys.slice(0, 5).map((key) => /* @__PURE__ */ jsxs43(Text43, { children: [
12144
+ " \u2022 ",
12145
+ key.key,
12146
+ " (",
12147
+ key.namespace || "default",
12148
+ ")"
12149
+ ] }, key.id)),
12150
+ keys.length > 5 && /* @__PURE__ */ jsxs43(Text43, { dimColor: true, children: [
12151
+ " ... and ",
12152
+ keys.length - 5,
12153
+ " more"
12154
+ ] })
12155
+ ] }),
12156
+ /* @__PURE__ */ jsxs43(Box40, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
12157
+ /* @__PURE__ */ jsx46(Text43, { color: "red", children: "This will delete all matching keys and their translations." }),
12158
+ /* @__PURE__ */ jsx46(Text43, { dimColor: true, children: "This action cannot be undone." })
12159
+ ] }),
12160
+ /* @__PURE__ */ jsx46(Box40, { marginLeft: 2, children: /* @__PURE__ */ jsxs43(Text43, { children: [
12161
+ "Press",
12162
+ " ",
12163
+ /* @__PURE__ */ jsx46(Text43, { bold: true, color: "green", children: "Y" }),
12164
+ " ",
12165
+ "to confirm,",
12166
+ " ",
12167
+ /* @__PURE__ */ jsx46(Text43, { bold: true, color: "red", children: "N" }),
12168
+ " ",
12169
+ "to cancel"
12170
+ ] }) })
12171
+ ] });
12172
+ }
12173
+ return null;
12174
+ };
12175
+ function runKeysBulkDelete(options) {
12176
+ render25(/* @__PURE__ */ jsx46(KeysBulkDelete, { options }));
12177
+ }
12178
+
12179
+ // src/commands/keys/index.tsx
12180
+ var keysCommand = new Command2("keys").description("Manage translation keys");
12181
+ keysCommand.command("list").description("List translation keys").option("--project <id>", "Project ID").option("--namespace <namespace>", "Filter by namespace").option("--search <query>", "Search keys by name").option("--tags <tags>", "Filter by tags (comma-separated)").option("--limit <n>", "Limit results").option("--format <format>", "Output format (table|json)", "table").action((options) => {
12182
+ runKeysList({
12183
+ project: options.project,
12184
+ namespace: options.namespace,
12185
+ search: options.search,
12186
+ tags: options.tags,
12187
+ limit: options.limit,
12188
+ format: options.format
12189
+ });
12190
+ });
12191
+ keysCommand.command("create <key>").description("Create a new translation key").requiredOption("--value <text>", "Translation value").option("--project <id>", "Project ID").option("--namespace <namespace>", "Namespace (default: default)").option("--context <text>", "Context description").option("--tags <tags>", "Tags (comma-separated)").action((key, options) => {
12192
+ runKeysCreate(key, {
12193
+ value: options.value,
12194
+ project: options.project,
12195
+ namespace: options.namespace,
12196
+ context: options.context,
12197
+ tags: options.tags
12198
+ });
12199
+ });
12200
+ keysCommand.command("update <key>").description("Update an existing translation key").option("--project <id>", "Project ID").option("--context <text>", "Update context description").option("--tags <tags>", "Update tags (comma-separated)").action((key, options) => {
12201
+ runKeysUpdate(key, {
12202
+ project: options.project,
12203
+ context: options.context,
12204
+ tags: options.tags
12205
+ });
12206
+ });
12207
+ keysCommand.command("delete <key>").description("Delete a translation key").option("--project <id>", "Project ID").option("--force", "Skip confirmation prompt").action((key, options) => {
12208
+ runKeysDelete(key, {
12209
+ project: options.project,
12210
+ force: options.force
12211
+ });
12212
+ });
12213
+ keysCommand.command("bulk-delete").description("Delete multiple keys matching a query").requiredOption("--query <search>", "Search query to match keys").option("--project <id>", "Project ID").option("--dry-run", "Show what would be deleted without deleting").option("--force", "Skip confirmation prompt").action((options) => {
12214
+ runKeysBulkDelete({
12215
+ query: options.query,
12216
+ project: options.project,
12217
+ dryRun: options.dryRun,
12218
+ force: options.force
12219
+ });
12220
+ });
12221
+
12222
+ // src/commands/tm/index.tsx
12223
+ import { Command as Command3 } from "commander";
12224
+
12225
+ // src/commands/tm/apply.tsx
12226
+ import { useState as useState31, useEffect as useEffect28 } from "react";
12227
+ import { render as render26, Box as Box41, Text as Text44, Newline as Newline3 } from "ink";
12228
+ import Spinner23 from "ink-spinner";
12229
+ import { Fragment as Fragment7, jsx as jsx47, jsxs as jsxs44 } from "react/jsx-runtime";
12230
+ var TMApply = ({ options }) => {
12231
+ const [result, setResult] = useState31(null);
12232
+ const [isLoading, setIsLoading] = useState31(true);
12233
+ const [error, setError] = useState31(null);
12234
+ useEffect28(() => {
12235
+ const applyTM = async () => {
12236
+ try {
12237
+ const config = getProjectConfig();
12238
+ const projectId = options.project || config?.projectId;
12239
+ if (!projectId) {
12240
+ setError("Project ID is required. Use --project <id> or run from a linked directory.");
12241
+ setIsLoading(false);
12242
+ return;
12243
+ }
12244
+ if (!options.language) {
12245
+ setError("Target language is required. Use --language <code>");
12246
+ setIsLoading(false);
12247
+ return;
12248
+ }
12249
+ const threshold2 = options.threshold ? parseInt(options.threshold, 10) : 85;
12250
+ const api = new TMApi();
12251
+ const tmResult = await api.applyTMMatches(projectId, {
12252
+ target_language: options.language,
12253
+ namespace: options.namespace,
12254
+ min_threshold: threshold2,
12255
+ dry_run: options.dryRun
12256
+ });
12257
+ setResult(tmResult);
12258
+ } catch (err) {
12259
+ setError(err instanceof Error ? err.message : "Failed to apply TM");
12260
+ } finally {
12261
+ setIsLoading(false);
12262
+ }
12263
+ };
12264
+ applyTM();
12265
+ }, [options]);
12266
+ if (error) {
12267
+ return /* @__PURE__ */ jsxs44(Text44, { color: "red", children: [
12268
+ "Error: ",
12269
+ error
12270
+ ] });
12271
+ }
12272
+ if (isLoading) {
12273
+ return /* @__PURE__ */ jsxs44(Text44, { children: [
12274
+ /* @__PURE__ */ jsx47(Text44, { color: "green", children: /* @__PURE__ */ jsx47(Spinner23, { type: "dots" }) }),
12275
+ " ",
12276
+ options.dryRun ? "Previewing TM matches..." : "Applying TM matches..."
12277
+ ] });
12278
+ }
12279
+ if (!result) {
12280
+ return /* @__PURE__ */ jsx47(Text44, { children: "No results" });
12281
+ }
12282
+ const threshold = options.threshold ? parseInt(options.threshold, 10) : 85;
12283
+ const qualifyingMatches = result.matches.filter((m) => m.score >= threshold);
12284
+ return /* @__PURE__ */ jsxs44(Box41, { flexDirection: "column", children: [
12285
+ /* @__PURE__ */ jsx47(Text44, { bold: true, children: options.dryRun ? "TM Match Preview" : "TM Applied" }),
12286
+ /* @__PURE__ */ jsx47(Newline3, {}),
12287
+ /* @__PURE__ */ jsxs44(Text44, { children: [
12288
+ "Threshold: ",
12289
+ threshold,
12290
+ "%"
12291
+ ] }),
12292
+ /* @__PURE__ */ jsxs44(Text44, { children: [
12293
+ "Language: ",
12294
+ options.language
12295
+ ] }),
12296
+ options.namespace && /* @__PURE__ */ jsxs44(Text44, { children: [
12297
+ "Namespace: ",
12298
+ options.namespace
12299
+ ] }),
12300
+ /* @__PURE__ */ jsx47(Newline3, {}),
12301
+ /* @__PURE__ */ jsxs44(Text44, { children: [
12302
+ "Qualifying matches: ",
12303
+ qualifyingMatches.length
12304
+ ] }),
12305
+ !options.dryRun && /* @__PURE__ */ jsxs44(Fragment7, { children: [
12306
+ /* @__PURE__ */ jsxs44(Text44, { color: "green", children: [
12307
+ "Applied: ",
12308
+ result.applied
12309
+ ] }),
12310
+ /* @__PURE__ */ jsxs44(Text44, { color: "yellow", children: [
12311
+ "Skipped: ",
12312
+ result.skipped
12313
+ ] })
12314
+ ] }),
12315
+ /* @__PURE__ */ jsx47(Newline3, {}),
12316
+ qualifyingMatches.length > 0 && /* @__PURE__ */ jsxs44(Fragment7, { children: [
12317
+ /* @__PURE__ */ jsx47(Text44, { bold: true, children: "Matches:" }),
12318
+ /* @__PURE__ */ jsx47(Newline3, {}),
12319
+ qualifyingMatches.slice(0, 20).map((match, idx) => /* @__PURE__ */ jsxs44(Box41, { flexDirection: "column", marginBottom: 1, children: [
12320
+ /* @__PURE__ */ jsxs44(Text44, { children: [
12321
+ /* @__PURE__ */ jsx47(Text44, { color: "cyan", children: match.key }),
12322
+ " (",
12323
+ match.score,
12324
+ "%)"
12325
+ ] }),
12326
+ /* @__PURE__ */ jsxs44(Text44, { dimColor: true, children: [
12327
+ " Source: ",
12328
+ match.source
12329
+ ] }),
12330
+ /* @__PURE__ */ jsxs44(Text44, { dimColor: true, children: [
12331
+ " Translation: ",
12332
+ match.translation
12333
+ ] }),
12334
+ match.applied && /* @__PURE__ */ jsx47(Text44, { color: "green", children: " \u2713 Applied" })
12335
+ ] }, idx)),
12336
+ qualifyingMatches.length > 20 && /* @__PURE__ */ jsxs44(Text44, { dimColor: true, children: [
12337
+ "... and ",
12338
+ qualifyingMatches.length - 20,
12339
+ " more matches"
12340
+ ] })
12341
+ ] }),
12342
+ qualifyingMatches.length === 0 && /* @__PURE__ */ jsx47(Text44, { dimColor: true, children: "No matches found above threshold." })
12343
+ ] });
12344
+ };
12345
+ function runTMApply(options) {
12346
+ render26(/* @__PURE__ */ jsx47(TMApply, { options }));
12347
+ }
12348
+
12349
+ // src/commands/tm/search.tsx
12350
+ import { useState as useState32, useEffect as useEffect29 } from "react";
12351
+ import { render as render27, Box as Box42, Text as Text45, Newline as Newline4 } from "ink";
12352
+ import Spinner24 from "ink-spinner";
12353
+ import { jsx as jsx48, jsxs as jsxs45 } from "react/jsx-runtime";
12354
+ var TMSearch = ({ options }) => {
12355
+ const [matches, setMatches] = useState32([]);
12356
+ const [isLoading, setIsLoading] = useState32(true);
12357
+ const [error, setError] = useState32(null);
12358
+ useEffect29(() => {
12359
+ const searchTM = async () => {
12360
+ try {
12361
+ if (!options.text) {
12362
+ setError("Search text is required");
12363
+ setIsLoading(false);
12364
+ return;
12365
+ }
12366
+ if (!options.source) {
12367
+ setError("Source language is required. Use --source <lang>");
12368
+ setIsLoading(false);
12369
+ return;
12370
+ }
12371
+ if (!options.target) {
12372
+ setError("Target language is required. Use --target <lang>");
12373
+ setIsLoading(false);
12374
+ return;
12375
+ }
12376
+ const api = new TMApi();
12377
+ const minQuality = options.minQuality ? parseInt(options.minQuality, 10) : 0;
12378
+ const result = await api.searchTM({
12379
+ text: options.text,
12380
+ source_lang: options.source,
12381
+ target_lang: options.target,
12382
+ min_quality: minQuality
12383
+ });
12384
+ setMatches(result.matches);
12385
+ } catch (err) {
12386
+ setError(err instanceof Error ? err.message : "Failed to search TM");
12387
+ } finally {
12388
+ setIsLoading(false);
12389
+ }
12390
+ };
12391
+ searchTM();
12392
+ }, [options]);
12393
+ if (error) {
12394
+ return /* @__PURE__ */ jsxs45(Text45, { color: "red", children: [
12395
+ "Error: ",
12396
+ error
12397
+ ] });
12398
+ }
12399
+ if (isLoading) {
12400
+ return /* @__PURE__ */ jsxs45(Text45, { children: [
12401
+ /* @__PURE__ */ jsx48(Text45, { color: "green", children: /* @__PURE__ */ jsx48(Spinner24, { type: "dots" }) }),
12402
+ " ",
12403
+ "Searching Translation Memory..."
12404
+ ] });
12405
+ }
12406
+ if (matches.length === 0) {
12407
+ return /* @__PURE__ */ jsxs45(Box42, { flexDirection: "column", children: [
12408
+ /* @__PURE__ */ jsxs45(Text45, { children: [
12409
+ "No matches found for: ",
12410
+ /* @__PURE__ */ jsx48(Text45, { bold: true, children: options.text })
12411
+ ] }),
12412
+ /* @__PURE__ */ jsxs45(Text45, { dimColor: true, children: [
12413
+ "Source: ",
12414
+ options.source,
12415
+ " \u2192 Target: ",
12416
+ options.target
12417
+ ] })
12418
+ ] });
12419
+ }
12420
+ const sortedMatches = [...matches].sort((a, b) => b.quality - a.quality);
12421
+ return /* @__PURE__ */ jsxs45(Box42, { flexDirection: "column", children: [
12422
+ /* @__PURE__ */ jsx48(Text45, { bold: true, children: "Translation Memory Matches" }),
12423
+ /* @__PURE__ */ jsx48(Newline4, {}),
12424
+ /* @__PURE__ */ jsxs45(Text45, { children: [
12425
+ "Query: ",
12426
+ /* @__PURE__ */ jsx48(Text45, { bold: true, children: options.text })
12427
+ ] }),
12428
+ /* @__PURE__ */ jsxs45(Text45, { dimColor: true, children: [
12429
+ options.source,
12430
+ " \u2192 ",
12431
+ options.target
12432
+ ] }),
12433
+ /* @__PURE__ */ jsx48(Newline4, {}),
12434
+ /* @__PURE__ */ jsxs45(Box42, { flexDirection: "column", children: [
12435
+ /* @__PURE__ */ jsx48(Text45, { bold: true, children: "Score Translation" }),
12436
+ /* @__PURE__ */ jsx48(Text45, { children: "\u2500".repeat(60) }),
12437
+ sortedMatches.map((match, idx) => /* @__PURE__ */ jsxs45(Box42, { flexDirection: "column", marginBottom: 1, children: [
12438
+ /* @__PURE__ */ jsxs45(Text45, { children: [
12439
+ /* @__PURE__ */ jsxs45(Text45, { color: match.quality >= 90 ? "green" : match.quality >= 75 ? "yellow" : "white", children: [
12440
+ match.quality,
12441
+ "%"
12442
+ ] }),
12443
+ " ",
12444
+ match.target_text
12445
+ ] }),
12446
+ match.context && /* @__PURE__ */ jsxs45(Text45, { dimColor: true, children: [
12447
+ " Context: ",
12448
+ match.context
12449
+ ] })
12450
+ ] }, idx))
12451
+ ] }),
12452
+ /* @__PURE__ */ jsx48(Newline4, {}),
12453
+ /* @__PURE__ */ jsxs45(Text45, { dimColor: true, children: [
12454
+ "Found ",
12455
+ matches.length,
12456
+ " match(es)"
12457
+ ] })
12458
+ ] });
12459
+ };
12460
+ function runTMSearch(options) {
12461
+ render27(/* @__PURE__ */ jsx48(TMSearch, { options }));
12462
+ }
12463
+
12464
+ // src/commands/tm/add.tsx
12465
+ import { useState as useState33, useEffect as useEffect30 } from "react";
12466
+ import { render as render28, Box as Box43, Text as Text46, Newline as Newline5 } from "ink";
12467
+ import Spinner25 from "ink-spinner";
12468
+ import { jsx as jsx49, jsxs as jsxs46 } from "react/jsx-runtime";
12469
+ var TMAdd = ({ options }) => {
12470
+ const [entry, setEntry] = useState33(null);
12471
+ const [isLoading, setIsLoading] = useState33(true);
12472
+ const [error, setError] = useState33(null);
12473
+ useEffect30(() => {
12474
+ const addEntry = async () => {
12475
+ try {
12476
+ if (!options.sourceText) {
12477
+ setError("Source text is required");
12478
+ setIsLoading(false);
12479
+ return;
12480
+ }
12481
+ if (!options.targetText) {
12482
+ setError("Target text is required");
12483
+ setIsLoading(false);
12484
+ return;
12485
+ }
12486
+ if (!options.sourceLang) {
12487
+ setError("Source language is required. Use --source-lang <lang>");
12488
+ setIsLoading(false);
12489
+ return;
12490
+ }
12491
+ if (!options.targetLang) {
12492
+ setError("Target language is required. Use --target-lang <lang>");
12493
+ setIsLoading(false);
12494
+ return;
12495
+ }
12496
+ const api = new TMApi();
12497
+ const quality = options.quality ? parseInt(options.quality, 10) : 100;
12498
+ const result = await api.addTMEntry({
12499
+ source_text: options.sourceText,
12500
+ target_text: options.targetText,
12501
+ source_lang: options.sourceLang,
12502
+ target_lang: options.targetLang,
12503
+ quality,
12504
+ context: options.context
12505
+ });
12506
+ setEntry(result);
12507
+ } catch (err) {
12508
+ setError(err instanceof Error ? err.message : "Failed to add TM entry");
12509
+ } finally {
12510
+ setIsLoading(false);
12511
+ }
12512
+ };
12513
+ addEntry();
12514
+ }, [options]);
12515
+ if (error) {
12516
+ return /* @__PURE__ */ jsxs46(Text46, { color: "red", children: [
12517
+ "Error: ",
12518
+ error
12519
+ ] });
12520
+ }
12521
+ if (isLoading) {
12522
+ return /* @__PURE__ */ jsxs46(Text46, { children: [
12523
+ /* @__PURE__ */ jsx49(Text46, { color: "green", children: /* @__PURE__ */ jsx49(Spinner25, { type: "dots" }) }),
12524
+ " ",
12525
+ "Adding TM entry..."
12526
+ ] });
12527
+ }
12528
+ if (!entry) {
12529
+ return /* @__PURE__ */ jsx49(Text46, { children: "No entry created" });
12530
+ }
12531
+ return /* @__PURE__ */ jsxs46(Box43, { flexDirection: "column", children: [
12532
+ /* @__PURE__ */ jsx49(Text46, { color: "green", children: "\u2713 Translation Memory entry added successfully" }),
12533
+ /* @__PURE__ */ jsx49(Newline5, {}),
12534
+ /* @__PURE__ */ jsxs46(Text46, { children: [
12535
+ /* @__PURE__ */ jsxs46(Text46, { bold: true, children: [
12536
+ "Source (",
12537
+ entry.source_lang,
12538
+ "):"
12539
+ ] }),
12540
+ " ",
12541
+ entry.source_text
12542
+ ] }),
12543
+ /* @__PURE__ */ jsxs46(Text46, { children: [
12544
+ /* @__PURE__ */ jsxs46(Text46, { bold: true, children: [
12545
+ "Target (",
12546
+ entry.target_lang,
12547
+ "):"
12548
+ ] }),
12549
+ " ",
12550
+ entry.target_text
12551
+ ] }),
12552
+ /* @__PURE__ */ jsxs46(Text46, { children: [
12553
+ /* @__PURE__ */ jsx49(Text46, { bold: true, children: "Quality:" }),
12554
+ " ",
12555
+ entry.quality,
12556
+ "%"
12557
+ ] }),
12558
+ entry.context && /* @__PURE__ */ jsxs46(Text46, { children: [
12559
+ /* @__PURE__ */ jsx49(Text46, { bold: true, children: "Context:" }),
12560
+ " ",
12561
+ entry.context
12562
+ ] }),
12563
+ /* @__PURE__ */ jsx49(Newline5, {}),
12564
+ /* @__PURE__ */ jsxs46(Text46, { dimColor: true, children: [
12565
+ "ID: ",
12566
+ entry.id
12567
+ ] })
12568
+ ] });
12569
+ };
12570
+ function runTMAdd(options) {
12571
+ render28(/* @__PURE__ */ jsx49(TMAdd, { options }));
12572
+ }
12573
+
12574
+ // src/commands/tm/stats.tsx
12575
+ import { useState as useState34, useEffect as useEffect31 } from "react";
12576
+ import { render as render29, Box as Box44, Text as Text47, Newline as Newline6 } from "ink";
12577
+ import Spinner26 from "ink-spinner";
12578
+ import { Fragment as Fragment8, jsx as jsx50, jsxs as jsxs47 } from "react/jsx-runtime";
12579
+ var TMStatsComponent = ({ options }) => {
12580
+ const [stats, setStats] = useState34(null);
12581
+ const [isLoading, setIsLoading] = useState34(true);
12582
+ const [error, setError] = useState34(null);
12583
+ useEffect31(() => {
12584
+ const fetchStats = async () => {
12585
+ try {
12586
+ const api = new TMApi();
12587
+ const result = await api.getTMStats();
12588
+ setStats(result);
12589
+ } catch (err) {
12590
+ setError(err instanceof Error ? err.message : "Failed to fetch TM stats");
12591
+ } finally {
12592
+ setIsLoading(false);
12593
+ }
12594
+ };
12595
+ fetchStats();
12596
+ }, [options]);
12597
+ if (error) {
12598
+ return /* @__PURE__ */ jsxs47(Text47, { color: "red", children: [
12599
+ "Error: ",
12600
+ error
12601
+ ] });
12602
+ }
12603
+ if (isLoading) {
12604
+ return /* @__PURE__ */ jsxs47(Text47, { children: [
12605
+ /* @__PURE__ */ jsx50(Text47, { color: "green", children: /* @__PURE__ */ jsx50(Spinner26, { type: "dots" }) }),
12606
+ " ",
12607
+ "Loading Translation Memory statistics..."
12608
+ ] });
12609
+ }
12610
+ if (!stats) {
12611
+ return /* @__PURE__ */ jsx50(Text47, { children: "No statistics available" });
12612
+ }
12613
+ return /* @__PURE__ */ jsxs47(Box44, { flexDirection: "column", children: [
12614
+ /* @__PURE__ */ jsx50(Text47, { bold: true, children: "Translation Memory Statistics" }),
12615
+ /* @__PURE__ */ jsx50(Newline6, {}),
12616
+ /* @__PURE__ */ jsxs47(Text47, { children: [
12617
+ /* @__PURE__ */ jsx50(Text47, { bold: true, children: "Total Entries:" }),
12618
+ " ",
12619
+ stats.total_entries.toLocaleString()
12620
+ ] }),
12621
+ /* @__PURE__ */ jsxs47(Text47, { children: [
12622
+ /* @__PURE__ */ jsx50(Text47, { bold: true, children: "Average Quality:" }),
12623
+ " ",
12624
+ stats.avg_quality.toFixed(1),
12625
+ "%"
12626
+ ] }),
12627
+ /* @__PURE__ */ jsx50(Newline6, {}),
12628
+ stats.language_pairs.length > 0 && /* @__PURE__ */ jsxs47(Fragment8, { children: [
12629
+ /* @__PURE__ */ jsx50(Text47, { bold: true, children: "Language Pairs:" }),
12630
+ /* @__PURE__ */ jsx50(Newline6, {}),
12631
+ /* @__PURE__ */ jsx50(Box44, { flexDirection: "column", children: stats.language_pairs.map((pair, idx) => /* @__PURE__ */ jsxs47(Text47, { children: [
12632
+ /* @__PURE__ */ jsxs47(Text47, { color: "cyan", children: [
12633
+ pair.source,
12634
+ " \u2192 ",
12635
+ pair.target
12636
+ ] }),
12637
+ ": ",
12638
+ pair.count.toLocaleString(),
12639
+ " ",
12640
+ pair.count === 1 ? "entry" : "entries"
12641
+ ] }, idx)) })
12642
+ ] }),
12643
+ stats.language_pairs.length === 0 && /* @__PURE__ */ jsx50(Text47, { dimColor: true, children: "No language pairs found. Add entries with `intlpull tm add`" })
12644
+ ] });
12645
+ };
12646
+ function runTMStats(options) {
12647
+ render29(/* @__PURE__ */ jsx50(TMStatsComponent, { options }));
12648
+ }
12649
+
12650
+ // src/commands/tm/index.tsx
12651
+ var tmCommand = new Command3("tm").description("Manage Translation Memory");
12652
+ tmCommand.command("apply").description("Apply TM matches to project translations").option("--project <id>", "Project ID").option("--language <code>", "Target language code").option("--namespace <namespace>", "Filter by namespace").option("--threshold <n>", "Minimum match score (default: 85)", "85").option("--dry-run", "Preview matches without applying").action((options) => {
12653
+ runTMApply({
12654
+ project: options.project,
12655
+ language: options.language,
12656
+ namespace: options.namespace,
12657
+ threshold: options.threshold,
12658
+ dryRun: options.dryRun
12659
+ });
12660
+ });
12661
+ tmCommand.command("search <text>").description("Search Translation Memory for a specific text").requiredOption("--source <lang>", "Source language code").requiredOption("--target <lang>", "Target language code").option("--min-quality <n>", "Minimum quality score (0-100)", "0").action((text, options) => {
12662
+ runTMSearch({
12663
+ text,
12664
+ source: options.source,
12665
+ target: options.target,
12666
+ minQuality: options.minQuality
12667
+ });
12668
+ });
12669
+ tmCommand.command("add <source> <target>").description("Add a new Translation Memory entry").requiredOption("--source-lang <lang>", "Source language code").requiredOption("--target-lang <lang>", "Target language code").option("--quality <n>", "Quality score (0-100, default: 100)", "100").option("--context <text>", "Context description").action((source, target, options) => {
12670
+ runTMAdd({
12671
+ sourceText: source,
12672
+ targetText: target,
12673
+ sourceLang: options.sourceLang,
12674
+ targetLang: options.targetLang,
12675
+ quality: options.quality,
12676
+ context: options.context
12677
+ });
12678
+ });
12679
+ tmCommand.command("stats").description("Show Translation Memory statistics").action(() => {
12680
+ runTMStats({});
12681
+ });
12682
+
12683
+ // src/commands/cleanup.tsx
12684
+ import { useState as useState35, useEffect as useEffect32 } from "react";
12685
+ import { render as render30, Box as Box45, Text as Text48 } from "ink";
12686
+ import Spinner27 from "ink-spinner";
12687
+ import { jsx as jsx51, jsxs as jsxs48 } from "react/jsx-runtime";
12688
+ var CleanupComponent = ({ options }) => {
12689
+ const [state, setState] = useState35("scanning");
12690
+ const [orphanKeys, setOrphanKeys] = useState35([]);
12691
+ const [totalKeys, setTotalKeys] = useState35(0);
12692
+ const [deleted, setDeleted] = useState35(0);
12693
+ const [error, setError] = useState35(null);
12694
+ useEffect32(() => {
12695
+ async function run() {
12696
+ try {
12697
+ const resolved = getResolvedApiKey();
12698
+ if (!resolved?.key) {
12699
+ setError("Not authenticated. Run `intlpull login` or set INTLPULL_API_KEY");
12700
+ setState("error");
12701
+ return;
12702
+ }
12703
+ const config = getProjectConfig();
12704
+ let projectId = options.project || config?.projectId;
12705
+ if (!projectId) {
12706
+ const projectsApi = new ProjectsApi();
12707
+ const { projects: projects2 } = await projectsApi.listProjects();
12708
+ if (projects2.length === 0) {
12709
+ setError("No projects found. Create one first.");
12710
+ setState("error");
12711
+ return;
12712
+ }
12713
+ if (projects2.length === 1) {
12714
+ projectId = projects2[0].id;
12715
+ } else {
12716
+ setError(
12717
+ `Multiple projects found. Specify with --project:
12718
+ ` + projects2.map((p) => ` --project ${p.id} (${p.name})`).join("\n")
12719
+ );
12720
+ setState("error");
12721
+ return;
12722
+ }
12723
+ }
12724
+ if (!projectId) {
12725
+ setError("Could not determine project ID");
12726
+ setState("error");
12727
+ return;
12728
+ }
12729
+ const api = new KeysApi();
12730
+ setState("scanning");
12731
+ const { keys: serverKeys } = await api.listKeys(projectId);
12732
+ setTotalKeys(serverKeys.length);
12733
+ if (serverKeys.length === 0) {
12734
+ setState("done");
12735
+ return;
12736
+ }
12737
+ let usedKeys = null;
12738
+ if (options.scanCodebase) {
12739
+ setState("comparing");
12740
+ usedKeys = await scanForKeys(process.cwd());
12741
+ }
12742
+ setState("comparing");
12743
+ const orphans = serverKeys.filter((k) => {
12744
+ if (usedKeys && !usedKeys.has(k.key)) {
12745
+ return true;
12746
+ }
12747
+ if (options.days !== void 0 && k.updated_at) {
12748
+ const daysSinceUpdate = (Date.now() - new Date(k.updated_at).getTime()) / (1e3 * 60 * 60 * 24);
12749
+ return daysSinceUpdate > options.days;
12750
+ }
12751
+ return false;
12752
+ });
12753
+ setOrphanKeys(orphans);
12754
+ if (options.dryRun || orphans.length === 0) {
12755
+ setState("done");
12756
+ return;
12757
+ }
12758
+ if (!options.force) {
12759
+ setState("confirming");
12760
+ if (!process.stdin.isTTY) {
12761
+ setError("Refusing to delete keys in non-interactive mode without --force flag");
12762
+ setState("error");
12763
+ return;
12764
+ }
12765
+ }
12766
+ setState("deleting");
12767
+ const keyIds = orphans.map((k) => k.id);
12768
+ const batchSize = 100;
12769
+ let deletedCount = 0;
12770
+ for (let i = 0; i < keyIds.length; i += batchSize) {
12771
+ const batch = keyIds.slice(i, i + batchSize);
12772
+ await api.bulkDeleteKeys(projectId, batch);
12773
+ deletedCount += batch.length;
12774
+ setDeleted(deletedCount);
12775
+ }
12776
+ setState("done");
12777
+ } catch (err) {
12778
+ setError(err instanceof Error ? err.message : "Failed to cleanup keys");
12779
+ setState("error");
12780
+ }
12781
+ }
12782
+ run();
12783
+ }, [options]);
12784
+ if (state === "error") {
12785
+ return /* @__PURE__ */ jsx51(Box45, { flexDirection: "column", children: /* @__PURE__ */ jsxs48(Text48, { color: "red", children: [
12786
+ "Error: ",
12787
+ error
12788
+ ] }) });
12789
+ }
12790
+ if (state === "scanning") {
12791
+ return /* @__PURE__ */ jsxs48(Box45, { children: [
12792
+ /* @__PURE__ */ jsx51(Text48, { color: "green", children: /* @__PURE__ */ jsx51(Spinner27, { type: "dots" }) }),
12793
+ /* @__PURE__ */ jsx51(Text48, { children: " Fetching keys from server..." })
12794
+ ] });
12795
+ }
12796
+ if (state === "comparing") {
12797
+ return /* @__PURE__ */ jsxs48(Box45, { flexDirection: "column", children: [
12798
+ /* @__PURE__ */ jsxs48(Box45, { children: [
12799
+ /* @__PURE__ */ jsx51(Text48, { color: "green", children: /* @__PURE__ */ jsx51(Spinner27, { type: "dots" }) }),
12800
+ /* @__PURE__ */ jsxs48(Text48, { children: [
12801
+ " ",
12802
+ options.scanCodebase ? "Scanning codebase for key usage..." : "Analyzing keys..."
12803
+ ] })
12804
+ ] }),
12805
+ /* @__PURE__ */ jsxs48(Text48, { dimColor: true, children: [
12806
+ " Found ",
12807
+ totalKeys,
12808
+ " total keys"
12809
+ ] })
12810
+ ] });
12811
+ }
12812
+ if (state === "confirming") {
12813
+ return /* @__PURE__ */ jsxs48(Box45, { flexDirection: "column", children: [
12814
+ /* @__PURE__ */ jsxs48(Text48, { color: "yellow", children: [
12815
+ "Warning: About to delete ",
12816
+ orphanKeys.length,
12817
+ " keys"
12818
+ ] }),
12819
+ /* @__PURE__ */ jsx51(Text48, { dimColor: true, children: "Press Ctrl+C to cancel or add --force flag to skip confirmation" })
12820
+ ] });
12821
+ }
12822
+ if (state === "deleting") {
12823
+ return /* @__PURE__ */ jsx51(Box45, { flexDirection: "column", children: /* @__PURE__ */ jsxs48(Box45, { children: [
12824
+ /* @__PURE__ */ jsx51(Text48, { color: "green", children: /* @__PURE__ */ jsx51(Spinner27, { type: "dots" }) }),
12825
+ /* @__PURE__ */ jsxs48(Text48, { children: [
12826
+ " Deleting orphaned keys... (",
12827
+ deleted,
12828
+ "/",
12829
+ orphanKeys.length,
12830
+ ")"
12831
+ ] })
12832
+ ] }) });
12833
+ }
12834
+ if (state === "done") {
12835
+ if (orphanKeys.length === 0) {
12836
+ return /* @__PURE__ */ jsxs48(Box45, { flexDirection: "column", children: [
12837
+ /* @__PURE__ */ jsx51(Text48, { color: "green", children: "\u2713 No orphaned keys found" }),
12838
+ /* @__PURE__ */ jsxs48(Text48, { dimColor: true, children: [
12839
+ " Total keys: ",
12840
+ totalKeys
12841
+ ] }),
12842
+ options.scanCodebase && /* @__PURE__ */ jsx51(Text48, { dimColor: true, children: " All keys are used in codebase" })
12843
+ ] });
12844
+ }
12845
+ if (options.dryRun) {
12846
+ return /* @__PURE__ */ jsxs48(Box45, { flexDirection: "column", children: [
12847
+ /* @__PURE__ */ jsxs48(Text48, { color: "yellow", children: [
12848
+ "Dry run: Found ",
12849
+ orphanKeys.length,
12850
+ " orphaned keys"
12851
+ ] }),
12852
+ /* @__PURE__ */ jsxs48(Box45, { flexDirection: "column", marginTop: 1, children: [
12853
+ /* @__PURE__ */ jsx51(Text48, { bold: true, children: "Orphaned keys:" }),
12854
+ orphanKeys.slice(0, 20).map((k) => /* @__PURE__ */ jsxs48(Text48, { dimColor: true, children: [
12855
+ k.key,
12856
+ options.days !== void 0 && k.updated_at && /* @__PURE__ */ jsxs48(Text48, { dimColor: true, children: [
12857
+ " ",
12858
+ "(last updated:",
12859
+ " ",
12860
+ Math.floor((Date.now() - new Date(k.updated_at).getTime()) / (1e3 * 60 * 60 * 24)),
12861
+ " ",
12862
+ "days ago)"
12863
+ ] })
12864
+ ] }, k.id)),
12865
+ orphanKeys.length > 20 && /* @__PURE__ */ jsxs48(Text48, { dimColor: true, children: [
12866
+ " ... and ",
12867
+ orphanKeys.length - 20,
12868
+ " more"
12869
+ ] })
12870
+ ] }),
12871
+ /* @__PURE__ */ jsx51(Box45, { marginTop: 1, children: /* @__PURE__ */ jsx51(Text48, { dimColor: true, children: "Remove --dry-run to delete these keys" }) })
12872
+ ] });
12873
+ }
12874
+ return /* @__PURE__ */ jsxs48(Box45, { flexDirection: "column", children: [
12875
+ /* @__PURE__ */ jsx51(Text48, { color: "green", children: "\u2713 Cleanup complete" }),
12876
+ /* @__PURE__ */ jsxs48(Text48, { children: [
12877
+ " Deleted: ",
12878
+ deleted,
12879
+ " keys"
12880
+ ] }),
12881
+ /* @__PURE__ */ jsxs48(Text48, { dimColor: true, children: [
12882
+ " Remaining: ",
12883
+ totalKeys - deleted,
12884
+ " keys"
12885
+ ] })
12886
+ ] });
12887
+ }
12888
+ return null;
12889
+ };
12890
+ function runCleanup(options) {
12891
+ render30(/* @__PURE__ */ jsx51(CleanupComponent, { options }));
12892
+ }
12893
+
12894
+ // src/commands/snapshots/index.tsx
12895
+ import { Command as Command4 } from "commander";
12896
+
12897
+ // src/commands/snapshots/create.tsx
12898
+ import { useState as useState36, useEffect as useEffect33 } from "react";
12899
+ import { render as render31, Box as Box46, Text as Text49 } from "ink";
12900
+ import Spinner28 from "ink-spinner";
12901
+ import { jsx as jsx52, jsxs as jsxs49 } from "react/jsx-runtime";
12902
+ var SnapshotsCreate = ({ options }) => {
12903
+ const [result, setResult] = useState36(null);
12904
+ const [isLoading, setIsLoading] = useState36(true);
12905
+ const [error, setError] = useState36(null);
12906
+ useEffect33(() => {
12907
+ const createSnapshot = async () => {
12908
+ try {
12909
+ const resolved = getResolvedApiKey();
12910
+ if (!resolved?.key) {
12911
+ setError("Not authenticated. Run `intlpull login` or set INTLPULL_API_KEY");
12912
+ setIsLoading(false);
12913
+ return;
12914
+ }
12915
+ const config = getProjectConfig();
12916
+ let projectId = options.project || config?.projectId;
12917
+ if (!projectId) {
12918
+ const projectsApi = new ProjectsApi();
12919
+ const { projects: projects2 } = await projectsApi.listProjects();
12920
+ if (projects2.length === 0) {
12921
+ setError("No projects found. Create one first.");
12922
+ setIsLoading(false);
12923
+ return;
12924
+ }
12925
+ if (projects2.length === 1) {
12926
+ projectId = projects2[0].id;
12927
+ } else {
12928
+ setError(
12929
+ `Multiple projects found. Specify with --project:
12930
+ ` + projects2.map((p) => ` --project ${p.id} (${p.name})`).join("\n")
12931
+ );
12932
+ setIsLoading(false);
12933
+ return;
12934
+ }
12935
+ }
12936
+ if (!projectId) {
12937
+ setError("Could not determine project ID");
12938
+ setIsLoading(false);
12939
+ return;
12940
+ }
12941
+ const api = new SnapshotsApi();
12942
+ const snapshot = await api.createSnapshot(projectId, {
12943
+ name: options.name,
12944
+ description: options.description
12945
+ });
12946
+ setResult(snapshot);
12947
+ } catch (err) {
12948
+ setError(err instanceof Error ? err.message : "Failed to create snapshot");
12949
+ } finally {
12950
+ setIsLoading(false);
12951
+ }
12952
+ };
12953
+ createSnapshot();
12954
+ }, [options]);
12955
+ if (error) {
12956
+ return /* @__PURE__ */ jsxs49(Text49, { color: "red", children: [
12957
+ "Error: ",
12958
+ error
12959
+ ] });
12960
+ }
12961
+ if (isLoading) {
12962
+ return /* @__PURE__ */ jsxs49(Box46, { children: [
12963
+ /* @__PURE__ */ jsx52(Text49, { color: "green", children: /* @__PURE__ */ jsx52(Spinner28, { type: "dots" }) }),
12964
+ /* @__PURE__ */ jsx52(Text49, { children: " Creating snapshot..." })
12965
+ ] });
12966
+ }
12967
+ if (!result) {
12968
+ return /* @__PURE__ */ jsx52(Text49, { color: "red", children: "Failed to create snapshot" });
12969
+ }
12970
+ return /* @__PURE__ */ jsxs49(Box46, { flexDirection: "column", children: [
12971
+ /* @__PURE__ */ jsx52(Text49, { color: "green", children: "\u2713 Snapshot created successfully" }),
12972
+ /* @__PURE__ */ jsxs49(Text49, { children: [
12973
+ " ID: ",
12974
+ result.id
12975
+ ] }),
12976
+ /* @__PURE__ */ jsxs49(Text49, { children: [
12977
+ " Name: ",
12978
+ result.name
12979
+ ] }),
12980
+ result.description && /* @__PURE__ */ jsxs49(Text49, { children: [
12981
+ " Description: ",
12982
+ result.description
12983
+ ] }),
12984
+ /* @__PURE__ */ jsxs49(Text49, { children: [
12985
+ " Keys: ",
12986
+ result.keys_count.toLocaleString()
12987
+ ] }),
12988
+ /* @__PURE__ */ jsxs49(Text49, { children: [
12989
+ " Languages: ",
12990
+ result.languages_count
12991
+ ] }),
12992
+ /* @__PURE__ */ jsxs49(Text49, { dimColor: true, children: [
12993
+ " Created: ",
12994
+ new Date(result.created_at).toLocaleString()
12995
+ ] })
12996
+ ] });
12997
+ };
12998
+ function runSnapshotsCreate(options) {
12999
+ render31(/* @__PURE__ */ jsx52(SnapshotsCreate, { options }));
13000
+ }
13001
+
13002
+ // src/commands/snapshots/list.tsx
13003
+ import { useState as useState37, useEffect as useEffect34 } from "react";
13004
+ import { render as render32, Box as Box47, Text as Text50 } from "ink";
13005
+ import Spinner29 from "ink-spinner";
13006
+ import { jsx as jsx53, jsxs as jsxs50 } from "react/jsx-runtime";
13007
+ var SnapshotsList = ({ options }) => {
13008
+ const [snapshots, setSnapshots] = useState37([]);
13009
+ const [isLoading, setIsLoading] = useState37(true);
13010
+ const [error, setError] = useState37(null);
13011
+ useEffect34(() => {
13012
+ const listSnapshots = async () => {
13013
+ try {
13014
+ const resolved = getResolvedApiKey();
13015
+ if (!resolved?.key) {
13016
+ setError("Not authenticated. Run `intlpull login` or set INTLPULL_API_KEY");
13017
+ setIsLoading(false);
13018
+ return;
13019
+ }
13020
+ const config = getProjectConfig();
13021
+ let projectId = options.project || config?.projectId;
13022
+ if (!projectId) {
13023
+ const projectsApi = new ProjectsApi();
13024
+ const { projects: projects2 } = await projectsApi.listProjects();
13025
+ if (projects2.length === 0) {
13026
+ setError("No projects found");
13027
+ setIsLoading(false);
13028
+ return;
13029
+ }
13030
+ if (projects2.length === 1) {
13031
+ projectId = projects2[0].id;
13032
+ } else {
13033
+ setError(
13034
+ `Multiple projects found. Specify with --project:
13035
+ ` + projects2.map((p) => ` --project ${p.id} (${p.name})`).join("\n")
13036
+ );
13037
+ setIsLoading(false);
13038
+ return;
13039
+ }
13040
+ }
13041
+ if (!projectId) {
13042
+ setError("Could not determine project ID");
13043
+ setIsLoading(false);
13044
+ return;
13045
+ }
13046
+ const api = new SnapshotsApi();
13047
+ const result = await api.listSnapshots(projectId);
13048
+ setSnapshots(result);
13049
+ } catch (err) {
13050
+ setError(err instanceof Error ? err.message : "Failed to list snapshots");
13051
+ } finally {
13052
+ setIsLoading(false);
13053
+ }
13054
+ };
13055
+ listSnapshots();
13056
+ }, [options]);
13057
+ if (error) {
13058
+ return /* @__PURE__ */ jsxs50(Text50, { color: "red", children: [
13059
+ "Error: ",
13060
+ error
13061
+ ] });
13062
+ }
13063
+ if (isLoading) {
13064
+ return /* @__PURE__ */ jsxs50(Box47, { children: [
13065
+ /* @__PURE__ */ jsx53(Text50, { color: "green", children: /* @__PURE__ */ jsx53(Spinner29, { type: "dots" }) }),
13066
+ /* @__PURE__ */ jsx53(Text50, { children: " Loading snapshots..." })
13067
+ ] });
13068
+ }
13069
+ if (snapshots.length === 0) {
13070
+ return /* @__PURE__ */ jsxs50(Box47, { flexDirection: "column", children: [
13071
+ /* @__PURE__ */ jsx53(Text50, { children: "No snapshots found" }),
13072
+ /* @__PURE__ */ jsx53(Text50, { dimColor: true, children: 'Create one with: intlpull snapshots create --name="my-snapshot"' })
13073
+ ] });
13074
+ }
13075
+ const formatDate2 = (date) => {
13076
+ const d = new Date(date);
13077
+ const now = /* @__PURE__ */ new Date();
13078
+ const diffMs = now.getTime() - d.getTime();
13079
+ const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
13080
+ if (diffDays === 0) {
13081
+ const diffHours = Math.floor(diffMs / (1e3 * 60 * 60));
13082
+ if (diffHours === 0) {
13083
+ const diffMins = Math.floor(diffMs / (1e3 * 60));
13084
+ return `${diffMins} min${diffMins === 1 ? "" : "s"} ago`;
13085
+ }
13086
+ return `${diffHours} hour${diffHours === 1 ? "" : "s"} ago`;
13087
+ }
13088
+ if (diffDays < 7) {
13089
+ return `${diffDays} day${diffDays === 1 ? "" : "s"} ago`;
13090
+ }
13091
+ return d.toLocaleDateString();
13092
+ };
13093
+ return /* @__PURE__ */ jsxs50(Box47, { flexDirection: "column", children: [
13094
+ /* @__PURE__ */ jsxs50(Text50, { bold: true, children: [
13095
+ "Snapshots (",
13096
+ snapshots.length,
13097
+ ")"
13098
+ ] }),
13099
+ /* @__PURE__ */ jsxs50(Text50, { dimColor: true, children: [
13100
+ "ID",
13101
+ " ".repeat(34),
13102
+ "Name",
13103
+ " ".repeat(26),
13104
+ "Created",
13105
+ " ".repeat(10),
13106
+ "Keys",
13107
+ " ".repeat(5),
13108
+ "Languages"
13109
+ ] }),
13110
+ snapshots.map((s) => /* @__PURE__ */ jsxs50(Text50, { children: [
13111
+ s.id.substring(0, 8),
13112
+ "...",
13113
+ " ".repeat(25),
13114
+ s.name.substring(0, 25).padEnd(30),
13115
+ formatDate2(s.created_at).padEnd(17),
13116
+ s.keys_count.toLocaleString().padEnd(9),
13117
+ s.languages_count
13118
+ ] }, s.id))
13119
+ ] });
13120
+ };
13121
+ function runSnapshotsList(options) {
13122
+ render32(/* @__PURE__ */ jsx53(SnapshotsList, { options }));
13123
+ }
13124
+
13125
+ // src/commands/snapshots/restore.tsx
13126
+ import { useState as useState38, useEffect as useEffect35 } from "react";
13127
+ import { render as render33, Box as Box48, Text as Text51, useInput as useInput10 } from "ink";
13128
+ import Spinner30 from "ink-spinner";
13129
+ import { jsx as jsx54, jsxs as jsxs51 } from "react/jsx-runtime";
13130
+ var SnapshotsRestore = ({ options }) => {
13131
+ const [result, setResult] = useState38(null);
13132
+ const [isLoading, setIsLoading] = useState38(true);
13133
+ const [error, setError] = useState38(null);
13134
+ const [awaitingConfirmation, setAwaitingConfirmation] = useState38(false);
13135
+ const [projectId, setProjectId] = useState38(null);
13136
+ useInput10((input, key) => {
13137
+ if (!awaitingConfirmation) return;
13138
+ if (input.toLowerCase() === "y") {
13139
+ setAwaitingConfirmation(false);
13140
+ setIsLoading(true);
13141
+ performRestore();
13142
+ } else if (input.toLowerCase() === "n" || key.escape) {
13143
+ setError("Restore cancelled by user");
13144
+ setAwaitingConfirmation(false);
13145
+ }
13146
+ });
13147
+ const performRestore = async () => {
13148
+ if (!projectId) return;
13149
+ try {
13150
+ const api = new SnapshotsApi();
13151
+ const response = await api.restoreSnapshot(projectId, options.snapshotId);
13152
+ setResult(response);
13153
+ } catch (err) {
13154
+ setError(err instanceof Error ? err.message : "Failed to restore snapshot");
13155
+ } finally {
13156
+ setIsLoading(false);
13157
+ }
13158
+ };
13159
+ useEffect35(() => {
13160
+ const initRestore = async () => {
13161
+ try {
13162
+ const resolved = getResolvedApiKey();
13163
+ if (!resolved?.key) {
13164
+ setError("Not authenticated. Run `intlpull login` or set INTLPULL_API_KEY");
13165
+ setIsLoading(false);
13166
+ return;
13167
+ }
13168
+ const config = getProjectConfig();
13169
+ let resolvedProjectId = options.project || config?.projectId;
13170
+ if (!resolvedProjectId) {
13171
+ const projectsApi = new ProjectsApi();
13172
+ const { projects: projects2 } = await projectsApi.listProjects();
13173
+ if (projects2.length === 0) {
13174
+ setError("No projects found");
13175
+ setIsLoading(false);
13176
+ return;
13177
+ }
13178
+ if (projects2.length === 1) {
13179
+ resolvedProjectId = projects2[0].id;
13180
+ } else {
13181
+ setError(
13182
+ `Multiple projects found. Specify with --project:
13183
+ ` + projects2.map((p) => ` --project ${p.id} (${p.name})`).join("\n")
13184
+ );
13185
+ setIsLoading(false);
13186
+ return;
13187
+ }
13188
+ }
13189
+ if (!resolvedProjectId) {
13190
+ setError("Could not determine project ID");
13191
+ setIsLoading(false);
13192
+ return;
13193
+ }
13194
+ setProjectId(resolvedProjectId);
13195
+ if (!options.force && !process.stdin.isTTY) {
13196
+ setError("Refusing to restore in non-interactive mode without --force flag");
13197
+ setIsLoading(false);
13198
+ return;
13199
+ }
13200
+ if (options.force) {
13201
+ const api = new SnapshotsApi();
13202
+ const response = await api.restoreSnapshot(resolvedProjectId, options.snapshotId);
13203
+ setResult(response);
13204
+ setIsLoading(false);
13205
+ } else {
13206
+ setIsLoading(false);
13207
+ setAwaitingConfirmation(true);
13208
+ }
13209
+ } catch (err) {
13210
+ setError(err instanceof Error ? err.message : "Failed to restore snapshot");
13211
+ setIsLoading(false);
13212
+ }
13213
+ };
13214
+ initRestore();
13215
+ }, [options]);
13216
+ if (error) {
13217
+ return /* @__PURE__ */ jsxs51(Text51, { color: "red", children: [
13218
+ "Error: ",
13219
+ error
13220
+ ] });
13221
+ }
13222
+ if (awaitingConfirmation) {
13223
+ return /* @__PURE__ */ jsxs51(Box48, { flexDirection: "column", children: [
13224
+ /* @__PURE__ */ jsx54(Text51, { color: "yellow", children: "\u26A0 WARNING: This will overwrite the current project state!" }),
13225
+ /* @__PURE__ */ jsxs51(Text51, { children: [
13226
+ "Snapshot ID: ",
13227
+ options.snapshotId
13228
+ ] }),
13229
+ /* @__PURE__ */ jsx54(Box48, { marginTop: 1, children: /* @__PURE__ */ jsx54(Text51, { bold: true, children: "Are you sure you want to restore this snapshot? (Y/N): " }) })
13230
+ ] });
13231
+ }
13232
+ if (isLoading) {
13233
+ return /* @__PURE__ */ jsxs51(Box48, { children: [
13234
+ /* @__PURE__ */ jsx54(Text51, { color: "green", children: /* @__PURE__ */ jsx54(Spinner30, { type: "dots" }) }),
13235
+ /* @__PURE__ */ jsx54(Text51, { children: " Restoring snapshot..." })
13236
+ ] });
13237
+ }
13238
+ if (!result) {
13239
+ return /* @__PURE__ */ jsx54(Text51, { color: "red", children: "Failed to restore snapshot" });
13240
+ }
13241
+ return /* @__PURE__ */ jsxs51(Box48, { flexDirection: "column", children: [
13242
+ /* @__PURE__ */ jsx54(Text51, { color: "green", children: "\u2713 Snapshot restored successfully" }),
13243
+ /* @__PURE__ */ jsxs51(Text51, { children: [
13244
+ " Restored keys: ",
13245
+ result.restored_keys.toLocaleString()
13246
+ ] }),
13247
+ /* @__PURE__ */ jsxs51(Text51, { children: [
13248
+ " Restored translations: ",
13249
+ result.restored_translations.toLocaleString()
13250
+ ] }),
13251
+ /* @__PURE__ */ jsx54(Box48, { marginTop: 1, children: /* @__PURE__ */ jsx54(Text51, { dimColor: true, children: "Note: This operation overwrites current project state" }) })
13252
+ ] });
13253
+ };
13254
+ function runSnapshotsRestore(options) {
13255
+ render33(/* @__PURE__ */ jsx54(SnapshotsRestore, { options }));
13256
+ }
13257
+
13258
+ // src/commands/snapshots/delete.tsx
13259
+ import { useState as useState39, useEffect as useEffect36 } from "react";
13260
+ import { render as render34, Box as Box49, Text as Text52 } from "ink";
13261
+ import Spinner31 from "ink-spinner";
13262
+ import { jsx as jsx55, jsxs as jsxs52 } from "react/jsx-runtime";
13263
+ var SnapshotsDelete = ({ options }) => {
13264
+ const [isLoading, setIsLoading] = useState39(true);
13265
+ const [error, setError] = useState39(null);
13266
+ const [success, setSuccess] = useState39(false);
13267
+ useEffect36(() => {
13268
+ const deleteSnapshot = async () => {
13269
+ try {
13270
+ const resolved = getResolvedApiKey();
13271
+ if (!resolved?.key) {
13272
+ setError("Not authenticated. Run `intlpull login` or set INTLPULL_API_KEY");
13273
+ setIsLoading(false);
13274
+ return;
13275
+ }
13276
+ const config = getProjectConfig();
13277
+ let projectId = options.project || config?.projectId;
13278
+ if (!projectId) {
13279
+ const projectsApi = new ProjectsApi();
13280
+ const { projects: projects2 } = await projectsApi.listProjects();
13281
+ if (projects2.length === 0) {
13282
+ setError("No projects found");
13283
+ setIsLoading(false);
13284
+ return;
13285
+ }
13286
+ if (projects2.length === 1) {
13287
+ projectId = projects2[0].id;
13288
+ } else {
13289
+ setError(
13290
+ `Multiple projects found. Specify with --project:
13291
+ ` + projects2.map((p) => ` --project ${p.id} (${p.name})`).join("\n")
13292
+ );
13293
+ setIsLoading(false);
13294
+ return;
13295
+ }
13296
+ }
13297
+ if (!projectId) {
13298
+ setError("Could not determine project ID");
13299
+ setIsLoading(false);
13300
+ return;
13301
+ }
13302
+ if (!options.force && !process.stdin.isTTY) {
13303
+ setError("Refusing to delete in non-interactive mode without --force flag");
13304
+ setIsLoading(false);
13305
+ return;
13306
+ }
13307
+ const api = new SnapshotsApi();
13308
+ await api.deleteSnapshot(projectId, options.snapshotId);
13309
+ setSuccess(true);
13310
+ } catch (err) {
13311
+ setError(err instanceof Error ? err.message : "Failed to delete snapshot");
13312
+ } finally {
13313
+ setIsLoading(false);
13314
+ }
13315
+ };
13316
+ deleteSnapshot();
13317
+ }, [options]);
13318
+ if (error) {
13319
+ return /* @__PURE__ */ jsxs52(Text52, { color: "red", children: [
13320
+ "Error: ",
13321
+ error
13322
+ ] });
13323
+ }
13324
+ if (isLoading) {
13325
+ return /* @__PURE__ */ jsxs52(Box49, { children: [
13326
+ /* @__PURE__ */ jsx55(Text52, { color: "green", children: /* @__PURE__ */ jsx55(Spinner31, { type: "dots" }) }),
13327
+ /* @__PURE__ */ jsx55(Text52, { children: " Deleting snapshot..." })
13328
+ ] });
13329
+ }
13330
+ if (success) {
13331
+ return /* @__PURE__ */ jsx55(Text52, { color: "green", children: "\u2713 Snapshot deleted successfully" });
13332
+ }
13333
+ return /* @__PURE__ */ jsx55(Text52, { color: "red", children: "Failed to delete snapshot" });
13334
+ };
13335
+ function runSnapshotsDelete(options) {
13336
+ render34(/* @__PURE__ */ jsx55(SnapshotsDelete, { options }));
13337
+ }
13338
+
13339
+ // src/commands/snapshots/index.tsx
13340
+ var snapshotsCommand = new Command4("snapshots").description("Manage project snapshots (capture and restore project state)");
13341
+ snapshotsCommand.command("create").description("Create a snapshot of current project state").requiredOption("--name <name>", "Snapshot name").option("--description <desc>", "Snapshot description").option("--project <id>", "Project ID (auto-detected with project-scoped API key)").action((options) => {
13342
+ runSnapshotsCreate({
13343
+ name: options.name,
13344
+ description: options.description,
13345
+ project: options.project
13346
+ });
13347
+ });
13348
+ snapshotsCommand.command("list").description("List all snapshots for a project").option("--project <id>", "Project ID (auto-detected with project-scoped API key)").action((options) => {
13349
+ runSnapshotsList({
13350
+ project: options.project
13351
+ });
13352
+ });
13353
+ snapshotsCommand.command("restore <snapshotId>").description("Restore project to snapshot state").option("--project <id>", "Project ID (auto-detected with project-scoped API key)").option("-f, --force", "Skip confirmation prompt").action((snapshotId, options) => {
13354
+ runSnapshotsRestore({
13355
+ snapshotId,
13356
+ project: options.project,
13357
+ force: options.force
13358
+ });
13359
+ });
13360
+ snapshotsCommand.command("delete <snapshotId>").description("Delete a snapshot").option("--project <id>", "Project ID (auto-detected with project-scoped API key)").option("-f, --force", "Skip confirmation prompt").action((snapshotId, options) => {
13361
+ runSnapshotsDelete({
13362
+ snapshotId,
13363
+ project: options.project,
13364
+ force: options.force
13365
+ });
13366
+ });
13367
+
13368
+ // src/commands/webhooks/index.tsx
13369
+ import { Command as Command5 } from "commander";
13370
+
13371
+ // src/commands/webhooks/list.tsx
13372
+ import { useState as useState40, useEffect as useEffect37 } from "react";
13373
+ import { render as render35, Box as Box50, Text as Text53, Newline as Newline7 } from "ink";
13374
+ import Spinner32 from "ink-spinner";
13375
+ import { jsx as jsx56, jsxs as jsxs53 } from "react/jsx-runtime";
13376
+ function SimpleTable3({ data }) {
13377
+ if (data.length === 0) return null;
13378
+ const headers = Object.keys(data[0]);
13379
+ const colWidths = headers.map(
13380
+ (h) => Math.max(h.length, ...data.map((row) => String(row[h] || "").length))
13381
+ );
13382
+ const separator = "\u2500".repeat(colWidths.reduce((a, b) => a + b + 3, 1));
13383
+ return /* @__PURE__ */ jsxs53(Box50, { flexDirection: "column", children: [
13384
+ /* @__PURE__ */ jsx56(Text53, { children: separator }),
13385
+ /* @__PURE__ */ jsxs53(Text53, { children: [
13386
+ "\u2502 ",
13387
+ headers.map((h, i) => h.padEnd(colWidths[i])).join(" \u2502 "),
13388
+ " \u2502"
13389
+ ] }),
13390
+ /* @__PURE__ */ jsx56(Text53, { children: separator }),
13391
+ data.map((row, rowIdx) => /* @__PURE__ */ jsxs53(Text53, { children: [
13392
+ "\u2502 ",
13393
+ headers.map((h, i) => String(row[h] || "").padEnd(colWidths[i])).join(" \u2502 "),
13394
+ " \u2502"
13395
+ ] }, rowIdx)),
13396
+ /* @__PURE__ */ jsx56(Text53, { children: separator })
13397
+ ] });
13398
+ }
13399
+ var WebhooksList = ({ options }) => {
13400
+ const [webhooks, setWebhooks] = useState40([]);
13401
+ const [isLoading, setIsLoading] = useState40(true);
13402
+ const [error, setError] = useState40(null);
13403
+ useEffect37(() => {
13404
+ const fetchWebhooks = async () => {
13405
+ try {
13406
+ const config = getProjectConfig();
13407
+ const projectId = options.project || config?.projectId;
13408
+ if (!projectId) {
13409
+ setError("Project ID is required. Use --project <id> or run from a linked directory.");
13410
+ setIsLoading(false);
13411
+ return;
13412
+ }
13413
+ const api = new WebhooksApi();
13414
+ const result = await api.listWebhooks(projectId);
13415
+ setWebhooks(result.webhooks);
13416
+ } catch (err) {
13417
+ setError(err instanceof Error ? err.message : "Failed to fetch webhooks");
13418
+ } finally {
13419
+ setIsLoading(false);
13420
+ }
13421
+ };
13422
+ fetchWebhooks();
13423
+ }, [options]);
13424
+ if (error) {
13425
+ return /* @__PURE__ */ jsxs53(Text53, { color: "red", children: [
13426
+ "Error: ",
13427
+ error
13428
+ ] });
13429
+ }
13430
+ if (isLoading) {
13431
+ return /* @__PURE__ */ jsxs53(Text53, { children: [
13432
+ /* @__PURE__ */ jsx56(Text53, { color: "green", children: /* @__PURE__ */ jsx56(Spinner32, { type: "dots" }) }),
13433
+ " Loading webhooks..."
13434
+ ] });
13435
+ }
13436
+ if (webhooks.length === 0) {
13437
+ return /* @__PURE__ */ jsx56(Text53, { children: "No webhooks found." });
13438
+ }
13439
+ if (options.format === "json") {
13440
+ console.log(JSON.stringify(webhooks, null, 2));
13441
+ return null;
13442
+ }
13443
+ const data = webhooks.map((webhook) => {
13444
+ const eventsDisplay = webhook.events.length > 2 ? webhook.events.slice(0, 2).join(", ") + ` +${webhook.events.length - 2}` : webhook.events.join(", ");
13445
+ return {
13446
+ ID: webhook.id.substring(0, 12) + "...",
13447
+ URL: webhook.url.length > 35 ? webhook.url.substring(0, 32) + "..." : webhook.url,
13448
+ Events: eventsDisplay,
13449
+ Status: webhook.enabled ? "Active" : "Disabled",
13450
+ "Last Triggered": webhook.last_triggered_at || "Never"
13451
+ };
13452
+ });
13453
+ return /* @__PURE__ */ jsxs53(Box50, { flexDirection: "column", children: [
13454
+ /* @__PURE__ */ jsxs53(Text53, { children: [
13455
+ "Found ",
13456
+ webhooks.length,
13457
+ " webhook(s)"
13458
+ ] }),
13459
+ /* @__PURE__ */ jsx56(Newline7, {}),
13460
+ /* @__PURE__ */ jsx56(SimpleTable3, { data })
13461
+ ] });
13462
+ };
13463
+ function runWebhooksList(options) {
13464
+ render35(/* @__PURE__ */ jsx56(WebhooksList, { options }));
13465
+ }
13466
+
13467
+ // src/commands/webhooks/create.tsx
13468
+ import { useState as useState41, useEffect as useEffect38 } from "react";
13469
+ import { render as render36, Box as Box51, Text as Text54 } from "ink";
13470
+ import Spinner33 from "ink-spinner";
13471
+ import { jsx as jsx57, jsxs as jsxs54 } from "react/jsx-runtime";
13472
+ var WebhooksCreate = ({ options }) => {
13473
+ const [result, setResult] = useState41(null);
13474
+ const [isLoading, setIsLoading] = useState41(true);
13475
+ const [error, setError] = useState41(null);
13476
+ useEffect38(() => {
13477
+ const createWebhook = async () => {
13478
+ try {
13479
+ const config = getProjectConfig();
13480
+ const projectId = options.project || config?.projectId;
13481
+ if (!projectId) {
13482
+ setError("Project ID is required. Use --project <id> or run from a linked directory.");
13483
+ setIsLoading(false);
13484
+ return;
13485
+ }
13486
+ if (!options.url) {
13487
+ setError("Webhook URL is required. Use --url <url>");
13488
+ setIsLoading(false);
13489
+ return;
13490
+ }
13491
+ if (!options.events) {
13492
+ setError("Events are required. Use --events <event1,event2,...>");
13493
+ setIsLoading(false);
13494
+ return;
13495
+ }
13496
+ const api = new WebhooksApi();
13497
+ const events = options.events.split(",").map((e) => e.trim());
13498
+ const created = await api.createWebhook(projectId, {
13499
+ url: options.url,
13500
+ events,
13501
+ secret: options.secret,
13502
+ enabled: options.enabled !== false
13503
+ });
13504
+ setResult(created);
13505
+ } catch (err) {
13506
+ setError(err instanceof Error ? err.message : "Failed to create webhook");
13507
+ } finally {
13508
+ setIsLoading(false);
13509
+ }
13510
+ };
13511
+ createWebhook();
13512
+ }, [options]);
13513
+ if (error) {
13514
+ return /* @__PURE__ */ jsxs54(Text54, { color: "red", children: [
13515
+ "Error: ",
13516
+ error
13517
+ ] });
13518
+ }
13519
+ if (isLoading) {
13520
+ return /* @__PURE__ */ jsxs54(Text54, { children: [
13521
+ /* @__PURE__ */ jsx57(Text54, { color: "green", children: /* @__PURE__ */ jsx57(Spinner33, { type: "dots" }) }),
13522
+ " Creating webhook..."
13523
+ ] });
13524
+ }
13525
+ if (!result) {
13526
+ return /* @__PURE__ */ jsx57(Text54, { color: "red", children: "Failed to create webhook" });
13527
+ }
13528
+ const { webhook, secret } = result;
13529
+ return /* @__PURE__ */ jsxs54(Box51, { flexDirection: "column", children: [
13530
+ /* @__PURE__ */ jsx57(Text54, { color: "green", children: "\u2713 Webhook created successfully" }),
13531
+ /* @__PURE__ */ jsxs54(Text54, { children: [
13532
+ "ID: ",
13533
+ webhook.id
13534
+ ] }),
13535
+ /* @__PURE__ */ jsxs54(Text54, { children: [
13536
+ "URL: ",
13537
+ webhook.url
13538
+ ] }),
13539
+ /* @__PURE__ */ jsxs54(Text54, { children: [
13540
+ "Events: ",
13541
+ webhook.events.join(", ")
13542
+ ] }),
13543
+ /* @__PURE__ */ jsxs54(Text54, { children: [
13544
+ "Status: ",
13545
+ webhook.enabled ? "Active" : "Disabled"
13546
+ ] }),
13547
+ secret && /* @__PURE__ */ jsxs54(Box51, { flexDirection: "column", marginTop: 1, children: [
13548
+ /* @__PURE__ */ jsx57(Text54, { color: "yellow", children: "\u26A0 SECURITY WARNING" }),
13549
+ /* @__PURE__ */ jsx57(Text54, { color: "yellow", children: "Webhook secret shown below. Save it securely - it will NOT be shown again." }),
13550
+ /* @__PURE__ */ jsx57(Text54, { color: "yellow", children: "Clear your terminal history after copying." }),
13551
+ /* @__PURE__ */ jsx57(Box51, { marginTop: 1, borderStyle: "single", borderColor: "yellow", paddingX: 1, children: /* @__PURE__ */ jsx57(Text54, { bold: true, children: secret }) })
13552
+ ] })
13553
+ ] });
13554
+ };
13555
+ function runWebhooksCreate(options) {
13556
+ render36(/* @__PURE__ */ jsx57(WebhooksCreate, { options }));
13557
+ }
13558
+
13559
+ // src/commands/webhooks/test.tsx
13560
+ import { useState as useState42, useEffect as useEffect39 } from "react";
13561
+ import { render as render37, Box as Box52, Text as Text55 } from "ink";
13562
+ import Spinner34 from "ink-spinner";
13563
+ import { jsx as jsx58, jsxs as jsxs55 } from "react/jsx-runtime";
13564
+ var WebhooksTest = ({ webhookId, options }) => {
13565
+ const [result, setResult] = useState42(null);
13566
+ const [isLoading, setIsLoading] = useState42(true);
13567
+ const [error, setError] = useState42(null);
13568
+ useEffect39(() => {
13569
+ const testWebhook = async () => {
13570
+ try {
13571
+ const config = getProjectConfig();
13572
+ const projectId = options.project || config?.projectId;
13573
+ if (!projectId) {
13574
+ setError("Project ID is required. Use --project <id> or run from a linked directory.");
13575
+ setIsLoading(false);
13576
+ return;
13577
+ }
13578
+ const api = new WebhooksApi();
13579
+ const testResult = await api.testWebhook(projectId, webhookId);
13580
+ setResult(testResult);
13581
+ } catch (err) {
13582
+ setError(err instanceof Error ? err.message : "Failed to test webhook");
13583
+ } finally {
13584
+ setIsLoading(false);
13585
+ }
13586
+ };
13587
+ testWebhook();
13588
+ }, [webhookId, options]);
13589
+ if (error) {
13590
+ return /* @__PURE__ */ jsxs55(Text55, { color: "red", children: [
13591
+ "Error: ",
13592
+ error
13593
+ ] });
13594
+ }
13595
+ if (isLoading) {
13596
+ return /* @__PURE__ */ jsxs55(Text55, { children: [
13597
+ /* @__PURE__ */ jsx58(Text55, { color: "green", children: /* @__PURE__ */ jsx58(Spinner34, { type: "dots" }) }),
13598
+ " Testing webhook..."
13599
+ ] });
13600
+ }
13601
+ if (!result) {
13602
+ return /* @__PURE__ */ jsx58(Text55, { color: "red", children: "Failed to test webhook" });
13603
+ }
13604
+ if (result.success) {
13605
+ return /* @__PURE__ */ jsxs55(Box52, { flexDirection: "column", children: [
13606
+ /* @__PURE__ */ jsx58(Text55, { color: "green", children: "\u2713 Webhook test successful" }),
13607
+ result.status_code && /* @__PURE__ */ jsxs55(Text55, { children: [
13608
+ "Status Code: ",
13609
+ result.status_code
13610
+ ] }),
13611
+ result.response_time_ms && /* @__PURE__ */ jsxs55(Text55, { children: [
13612
+ "Response Time: ",
13613
+ result.response_time_ms,
13614
+ "ms"
13615
+ ] })
13616
+ ] });
13617
+ }
13618
+ return /* @__PURE__ */ jsxs55(Box52, { flexDirection: "column", children: [
13619
+ /* @__PURE__ */ jsx58(Text55, { color: "red", children: "\u2717 Webhook test failed" }),
13620
+ result.status_code && /* @__PURE__ */ jsxs55(Text55, { children: [
13621
+ "Status Code: ",
13622
+ result.status_code
13623
+ ] }),
13624
+ result.response_time_ms && /* @__PURE__ */ jsxs55(Text55, { children: [
13625
+ "Response Time: ",
13626
+ result.response_time_ms,
13627
+ "ms"
13628
+ ] }),
13629
+ result.error && /* @__PURE__ */ jsxs55(Text55, { children: [
13630
+ "Error: ",
13631
+ result.error
13632
+ ] })
13633
+ ] });
13634
+ };
13635
+ function runWebhooksTest(webhookId, options) {
13636
+ render37(/* @__PURE__ */ jsx58(WebhooksTest, { webhookId, options }));
13637
+ }
13638
+
13639
+ // src/commands/webhooks/delete.tsx
13640
+ import { useState as useState43, useEffect as useEffect40 } from "react";
13641
+ import { render as render38, Box as Box53, Text as Text56, useInput as useInput11 } from "ink";
13642
+ import Spinner35 from "ink-spinner";
13643
+ import { jsx as jsx59, jsxs as jsxs56 } from "react/jsx-runtime";
13644
+ var WebhooksDelete = ({ webhookId, options }) => {
13645
+ const [webhook, setWebhook] = useState43(null);
13646
+ const [isLoading, setIsLoading] = useState43(true);
13647
+ const [error, setError] = useState43(null);
13648
+ const [needsConfirmation, setNeedsConfirmation] = useState43(!options.force);
13649
+ const [isDeleting, setIsDeleting] = useState43(false);
13650
+ const [deleted, setDeleted] = useState43(false);
13651
+ useEffect40(() => {
13652
+ const findWebhook = async () => {
13653
+ try {
13654
+ const config = getProjectConfig();
13655
+ const projectId = options.project || config?.projectId;
13656
+ if (!projectId) {
13657
+ setError("Project ID is required. Use --project <id> or run from a linked directory.");
13658
+ setIsLoading(false);
13659
+ return;
13660
+ }
13661
+ const api = new WebhooksApi();
13662
+ const webhookData = await api.getWebhook(projectId, webhookId);
13663
+ setWebhook(webhookData);
13664
+ setIsLoading(false);
13665
+ if (options.force) {
13666
+ await performDelete(projectId, webhookId, api);
13667
+ }
13668
+ } catch (err) {
13669
+ setError(err instanceof Error ? err.message : "Failed to find webhook");
13670
+ setIsLoading(false);
13671
+ }
13672
+ };
13673
+ findWebhook();
13674
+ }, [webhookId, options.force, options.project]);
13675
+ const performDelete = async (projectId, id, api) => {
13676
+ try {
13677
+ setIsDeleting(true);
13678
+ await api.deleteWebhook(projectId, id);
13679
+ setDeleted(true);
13680
+ setNeedsConfirmation(false);
13681
+ } catch (err) {
13682
+ setError(err instanceof Error ? err.message : "Failed to delete webhook");
13683
+ } finally {
13684
+ setIsDeleting(false);
13685
+ }
13686
+ };
13687
+ useInput11((input, inputKey) => {
13688
+ if (!needsConfirmation || isDeleting) return;
13689
+ if (input.toLowerCase() === "y") {
13690
+ const config = getProjectConfig();
13691
+ const projectId = options.project || config?.projectId;
13692
+ if (projectId) {
13693
+ const api = new WebhooksApi();
13694
+ performDelete(projectId, webhookId, api);
13695
+ }
13696
+ } else if (input.toLowerCase() === "n" || inputKey.escape) {
13697
+ process.exit(0);
13698
+ }
13699
+ });
13700
+ if (error) {
13701
+ return /* @__PURE__ */ jsxs56(Text56, { color: "red", children: [
13702
+ "Error: ",
13703
+ error
13704
+ ] });
13705
+ }
13706
+ if (isLoading) {
13707
+ return /* @__PURE__ */ jsxs56(Text56, { children: [
13708
+ /* @__PURE__ */ jsx59(Text56, { color: "green", children: /* @__PURE__ */ jsx59(Spinner35, { type: "dots" }) }),
13709
+ " Finding webhook..."
13710
+ ] });
13711
+ }
13712
+ if (isDeleting) {
13713
+ return /* @__PURE__ */ jsxs56(Text56, { children: [
13714
+ /* @__PURE__ */ jsx59(Text56, { color: "green", children: /* @__PURE__ */ jsx59(Spinner35, { type: "dots" }) }),
13715
+ " Deleting webhook..."
13716
+ ] });
13717
+ }
13718
+ if (deleted) {
13719
+ return /* @__PURE__ */ jsxs56(Box53, { flexDirection: "column", children: [
13720
+ /* @__PURE__ */ jsx59(Text56, { color: "green", children: "\u2713 Webhook deleted successfully" }),
13721
+ /* @__PURE__ */ jsxs56(Text56, { children: [
13722
+ "ID: ",
13723
+ webhookId
13724
+ ] })
13725
+ ] });
13726
+ }
13727
+ if (needsConfirmation && webhook) {
13728
+ return /* @__PURE__ */ jsxs56(Box53, { flexDirection: "column", children: [
13729
+ /* @__PURE__ */ jsx59(Box53, { marginBottom: 1, children: /* @__PURE__ */ jsx59(Text56, { color: "yellow", children: "Warning: Delete Webhook" }) }),
13730
+ /* @__PURE__ */ jsxs56(Box53, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
13731
+ /* @__PURE__ */ jsx59(Text56, { children: "You are about to delete the following webhook:" }),
13732
+ /* @__PURE__ */ jsx59(Text56, { dimColor: true }),
13733
+ /* @__PURE__ */ jsxs56(Text56, { children: [
13734
+ " ID: ",
13735
+ /* @__PURE__ */ jsx59(Text56, { bold: true, children: webhook.id })
13736
+ ] }),
13737
+ /* @__PURE__ */ jsxs56(Text56, { children: [
13738
+ " URL: ",
13739
+ /* @__PURE__ */ jsx59(Text56, { bold: true, children: webhook.url })
13740
+ ] }),
13741
+ /* @__PURE__ */ jsxs56(Text56, { children: [
13742
+ " Events: ",
13743
+ webhook.events.join(", ")
13744
+ ] })
13745
+ ] }),
13746
+ /* @__PURE__ */ jsxs56(Box53, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
13747
+ /* @__PURE__ */ jsx59(Text56, { color: "red", children: "This will permanently remove the webhook." }),
13748
+ /* @__PURE__ */ jsx59(Text56, { dimColor: true, children: "This action cannot be undone." })
13749
+ ] }),
13750
+ /* @__PURE__ */ jsx59(Box53, { marginLeft: 2, children: /* @__PURE__ */ jsxs56(Text56, { children: [
13751
+ "Press",
13752
+ " ",
13753
+ /* @__PURE__ */ jsx59(Text56, { bold: true, color: "green", children: "Y" }),
13754
+ " ",
13755
+ "to confirm,",
13756
+ " ",
13757
+ /* @__PURE__ */ jsx59(Text56, { bold: true, color: "red", children: "N" }),
13758
+ " ",
13759
+ "to cancel"
13760
+ ] }) })
13761
+ ] });
13762
+ }
13763
+ return null;
13764
+ };
13765
+ function runWebhooksDelete(webhookId, options) {
13766
+ render38(/* @__PURE__ */ jsx59(WebhooksDelete, { webhookId, options }));
13767
+ }
13768
+
13769
+ // src/commands/webhooks/index.tsx
13770
+ var webhooksCommand = new Command5("webhooks").description("Manage webhooks");
13771
+ webhooksCommand.command("list").description("List all webhooks").option("--project <id>", "Project ID").option("--format <format>", "Output format (table|json)", "table").action((options) => {
13772
+ runWebhooksList({
13773
+ project: options.project,
13774
+ format: options.format
13775
+ });
13776
+ });
13777
+ webhooksCommand.command("create").description("Create a new webhook").requiredOption("--url <url>", "Webhook URL endpoint").requiredOption("--events <events>", "Comma-separated list of events (e.g., translation.created,key.updated)").option("--project <id>", "Project ID").option("--secret <secret>", "Webhook secret for payload signing").option("--enabled", "Enable webhook immediately (default: true)", true).action((options) => {
13778
+ runWebhooksCreate({
13779
+ project: options.project,
13780
+ url: options.url,
13781
+ events: options.events,
13782
+ secret: options.secret,
13783
+ enabled: options.enabled
13784
+ });
13785
+ });
13786
+ webhooksCommand.command("test <id>").description("Test a webhook by sending a test event").option("--project <id>", "Project ID").action((id, options) => {
13787
+ runWebhooksTest(id, {
13788
+ project: options.project
13789
+ });
13790
+ });
13791
+ webhooksCommand.command("delete <id>").description("Delete a webhook").option("--project <id>", "Project ID").option("--force", "Skip confirmation prompt").action((id, options) => {
13792
+ runWebhooksDelete(id, {
13793
+ project: options.project,
13794
+ force: options.force
13795
+ });
13796
+ });
13797
+
13798
+ // src/commands/contributors/index.tsx
13799
+ import { Command as Command6 } from "commander";
13800
+
13801
+ // src/commands/contributors/list.tsx
13802
+ import { useState as useState44, useEffect as useEffect41 } from "react";
13803
+ import { render as render39, Box as Box54, Text as Text57, Newline as Newline8 } from "ink";
13804
+ import Spinner36 from "ink-spinner";
13805
+ import { jsx as jsx60, jsxs as jsxs57 } from "react/jsx-runtime";
13806
+ function SimpleTable4({ data }) {
13807
+ if (data.length === 0) return null;
13808
+ const headers = Object.keys(data[0]);
13809
+ const colWidths = headers.map(
13810
+ (h) => Math.max(h.length, ...data.map((row) => String(row[h] || "").length))
13811
+ );
13812
+ const separator = "\u2500".repeat(colWidths.reduce((a, b) => a + b + 3, 1));
13813
+ return /* @__PURE__ */ jsxs57(Box54, { flexDirection: "column", children: [
13814
+ /* @__PURE__ */ jsx60(Text57, { children: separator }),
13815
+ /* @__PURE__ */ jsxs57(Text57, { children: [
13816
+ "\u2502 ",
13817
+ headers.map((h, i) => h.padEnd(colWidths[i])).join(" \u2502 "),
13818
+ " \u2502"
13819
+ ] }),
13820
+ /* @__PURE__ */ jsx60(Text57, { children: separator }),
13821
+ data.map((row, rowIdx) => /* @__PURE__ */ jsxs57(Text57, { children: [
13822
+ "\u2502 ",
13823
+ headers.map((h, i) => String(row[h] || "").padEnd(colWidths[i])).join(" \u2502 "),
13824
+ " \u2502"
13825
+ ] }, rowIdx)),
13826
+ /* @__PURE__ */ jsx60(Text57, { children: separator })
13827
+ ] });
13828
+ }
13829
+ var ContributorsList = ({ options }) => {
13830
+ const [members, setMembers] = useState44([]);
13831
+ const [isLoading, setIsLoading] = useState44(true);
13832
+ const [error, setError] = useState44(null);
13833
+ useEffect41(() => {
13834
+ const fetchMembers = async () => {
13835
+ try {
13836
+ const config = getProjectConfig();
13837
+ const projectId = options.project || config?.projectId;
13838
+ if (!projectId) {
13839
+ setError("Project ID is required. Use --project <id> or run from a linked directory.");
13840
+ setIsLoading(false);
13841
+ return;
13842
+ }
13843
+ const api = new ContributorsApi();
13844
+ const result = await api.listTeamMembers(projectId);
13845
+ setMembers(result.members);
13846
+ } catch (err) {
13847
+ setError(err instanceof Error ? err.message : "Failed to fetch team members");
13848
+ } finally {
13849
+ setIsLoading(false);
13850
+ }
13851
+ };
13852
+ fetchMembers();
13853
+ }, [options]);
13854
+ if (error) {
13855
+ return /* @__PURE__ */ jsxs57(Text57, { color: "red", children: [
13856
+ "Error: ",
13857
+ error
13858
+ ] });
13859
+ }
13860
+ if (isLoading) {
13861
+ return /* @__PURE__ */ jsxs57(Text57, { children: [
13862
+ /* @__PURE__ */ jsx60(Text57, { color: "green", children: /* @__PURE__ */ jsx60(Spinner36, { type: "dots" }) }),
13863
+ " Loading team members..."
13864
+ ] });
13865
+ }
13866
+ if (members.length === 0) {
13867
+ return /* @__PURE__ */ jsx60(Text57, { children: "No team members found." });
13868
+ }
13869
+ if (options.format === "json") {
13870
+ console.log(JSON.stringify(members, null, 2));
13871
+ return null;
13872
+ }
13873
+ const data = members.map((member) => {
13874
+ const emailDisplay = member.email.length > 25 ? member.email.substring(0, 22) + "..." : member.email;
13875
+ const languagesDisplay = member.languages && member.languages.length > 0 ? member.languages.length > 3 ? member.languages.slice(0, 3).join(",") + ` +${member.languages.length - 3}` : member.languages.join(",") : "All";
13876
+ const joinedDate = new Date(member.joined_at).toLocaleDateString();
13877
+ return {
13878
+ Email: emailDisplay,
13879
+ Name: member.name || "-",
13880
+ Role: member.role.charAt(0).toUpperCase() + member.role.slice(1),
13881
+ Languages: languagesDisplay,
13882
+ Joined: joinedDate
13883
+ };
13884
+ });
13885
+ return /* @__PURE__ */ jsxs57(Box54, { flexDirection: "column", children: [
13886
+ /* @__PURE__ */ jsxs57(Text57, { children: [
13887
+ "Found ",
13888
+ members.length,
13889
+ " team member(s)"
13890
+ ] }),
13891
+ /* @__PURE__ */ jsx60(Newline8, {}),
13892
+ /* @__PURE__ */ jsx60(SimpleTable4, { data })
13893
+ ] });
13894
+ };
13895
+ function runContributorsList(options) {
13896
+ render39(/* @__PURE__ */ jsx60(ContributorsList, { options }));
13897
+ }
13898
+
13899
+ // src/commands/contributors/add.tsx
13900
+ import { useState as useState45, useEffect as useEffect42 } from "react";
13901
+ import { render as render40, Box as Box55, Text as Text58 } from "ink";
13902
+ import Spinner37 from "ink-spinner";
13903
+ import { jsx as jsx61, jsxs as jsxs58 } from "react/jsx-runtime";
13904
+ var ContributorsAdd = ({ options }) => {
13905
+ const [result, setResult] = useState45(null);
13906
+ const [isLoading, setIsLoading] = useState45(true);
13907
+ const [error, setError] = useState45(null);
13908
+ useEffect42(() => {
13909
+ const addContributor = async () => {
13910
+ try {
13911
+ const config = getProjectConfig();
13912
+ const projectId = options.project || config?.projectId;
13913
+ if (!projectId) {
13914
+ setError("Project ID is required. Use --project <id> or run from a linked directory.");
13915
+ setIsLoading(false);
13916
+ return;
13917
+ }
13918
+ if (!options.email) {
13919
+ setError("Email is required. Use --email <email>");
13920
+ setIsLoading(false);
13921
+ return;
13922
+ }
13923
+ if (!options.role) {
13924
+ setError("Role is required. Use --role <owner|admin|member|viewer>");
13925
+ setIsLoading(false);
13926
+ return;
13927
+ }
13928
+ const validRoles = ["owner", "admin", "member", "viewer"];
13929
+ if (!validRoles.includes(options.role)) {
13930
+ setError("Invalid role. Must be one of: owner, admin, member, viewer");
13931
+ setIsLoading(false);
13932
+ return;
13933
+ }
13934
+ const api = new ContributorsApi();
13935
+ const languages = options.languages ? options.languages.split(",").map((l) => l.trim()) : void 0;
13936
+ const invited = await api.inviteTeamMember(projectId, {
13937
+ email: options.email,
13938
+ role: options.role,
13939
+ languages,
13940
+ message: options.message
13941
+ });
13942
+ setResult(invited.member);
13943
+ } catch (err) {
13944
+ setError(err instanceof Error ? err.message : "Failed to invite team member");
13945
+ } finally {
13946
+ setIsLoading(false);
13947
+ }
13948
+ };
13949
+ addContributor();
13950
+ }, [options]);
13951
+ if (error) {
13952
+ return /* @__PURE__ */ jsxs58(Text58, { color: "red", children: [
13953
+ "Error: ",
13954
+ error
13955
+ ] });
13956
+ }
13957
+ if (isLoading) {
13958
+ return /* @__PURE__ */ jsxs58(Text58, { children: [
13959
+ /* @__PURE__ */ jsx61(Text58, { color: "green", children: /* @__PURE__ */ jsx61(Spinner37, { type: "dots" }) }),
13960
+ " Inviting team member..."
13961
+ ] });
13962
+ }
13963
+ if (!result) {
13964
+ return /* @__PURE__ */ jsx61(Text58, { color: "red", children: "Failed to invite team member" });
13965
+ }
13966
+ return /* @__PURE__ */ jsxs58(Box55, { flexDirection: "column", children: [
13967
+ /* @__PURE__ */ jsx61(Text58, { color: "green", children: "\u2713 Team member invited successfully" }),
13968
+ /* @__PURE__ */ jsxs58(Text58, { children: [
13969
+ "Email: ",
13970
+ result.email
13971
+ ] }),
13972
+ /* @__PURE__ */ jsxs58(Text58, { children: [
13973
+ "Role: ",
13974
+ result.role
13975
+ ] }),
13976
+ result.languages && result.languages.length > 0 && /* @__PURE__ */ jsxs58(Text58, { children: [
13977
+ "Languages: ",
13978
+ result.languages.join(", ")
13979
+ ] }),
13980
+ /* @__PURE__ */ jsx61(Box55, { marginTop: 1, children: /* @__PURE__ */ jsxs58(Text58, { dimColor: true, children: [
13981
+ "An invitation email has been sent to ",
13982
+ result.email
13983
+ ] }) })
13984
+ ] });
13985
+ };
13986
+ function runContributorsAdd(options) {
13987
+ render40(/* @__PURE__ */ jsx61(ContributorsAdd, { options }));
13988
+ }
13989
+
13990
+ // src/commands/contributors/update.tsx
13991
+ import { useState as useState46, useEffect as useEffect43 } from "react";
13992
+ import { render as render41, Box as Box56, Text as Text59 } from "ink";
13993
+ import Spinner38 from "ink-spinner";
13994
+ import { jsx as jsx62, jsxs as jsxs59 } from "react/jsx-runtime";
13995
+ var ContributorsUpdate = ({ userId, options }) => {
13996
+ const [result, setResult] = useState46(null);
13997
+ const [isLoading, setIsLoading] = useState46(true);
13998
+ const [error, setError] = useState46(null);
13999
+ useEffect43(() => {
14000
+ const updateContributor = async () => {
14001
+ try {
14002
+ const config = getProjectConfig();
14003
+ const projectId = options.project || config?.projectId;
14004
+ if (!projectId) {
14005
+ setError("Project ID is required. Use --project <id> or run from a linked directory.");
14006
+ setIsLoading(false);
14007
+ return;
14008
+ }
14009
+ if (!options.role && !options.languages) {
14010
+ setError("At least one of --role or --languages is required");
14011
+ setIsLoading(false);
14012
+ return;
14013
+ }
14014
+ const validRoles = ["owner", "admin", "member", "viewer"];
14015
+ if (options.role && !validRoles.includes(options.role)) {
14016
+ setError("Invalid role. Must be one of: owner, admin, member, viewer");
14017
+ setIsLoading(false);
14018
+ return;
14019
+ }
14020
+ const api = new ContributorsApi();
14021
+ const languages = options.languages ? options.languages.split(",").map((l) => l.trim()) : void 0;
14022
+ const updated = await api.updateTeamMember(projectId, userId, {
14023
+ role: options.role,
14024
+ languages
14025
+ });
14026
+ setResult(updated.member);
14027
+ } catch (err) {
14028
+ setError(err instanceof Error ? err.message : "Failed to update team member");
14029
+ } finally {
14030
+ setIsLoading(false);
14031
+ }
14032
+ };
14033
+ updateContributor();
14034
+ }, [userId, options]);
14035
+ if (error) {
14036
+ return /* @__PURE__ */ jsxs59(Text59, { color: "red", children: [
14037
+ "Error: ",
14038
+ error
14039
+ ] });
14040
+ }
14041
+ if (isLoading) {
14042
+ return /* @__PURE__ */ jsxs59(Text59, { children: [
14043
+ /* @__PURE__ */ jsx62(Text59, { color: "green", children: /* @__PURE__ */ jsx62(Spinner38, { type: "dots" }) }),
14044
+ " Updating team member..."
14045
+ ] });
14046
+ }
14047
+ if (!result) {
14048
+ return /* @__PURE__ */ jsx62(Text59, { color: "red", children: "Failed to update team member" });
14049
+ }
14050
+ return /* @__PURE__ */ jsxs59(Box56, { flexDirection: "column", children: [
14051
+ /* @__PURE__ */ jsx62(Text59, { color: "green", children: "\u2713 Team member updated successfully" }),
14052
+ /* @__PURE__ */ jsxs59(Text59, { children: [
14053
+ "Email: ",
14054
+ result.email
14055
+ ] }),
14056
+ /* @__PURE__ */ jsxs59(Text59, { children: [
14057
+ "Role: ",
14058
+ result.role
14059
+ ] }),
14060
+ result.languages && result.languages.length > 0 && /* @__PURE__ */ jsxs59(Text59, { children: [
14061
+ "Languages: ",
14062
+ result.languages.join(", ")
14063
+ ] })
14064
+ ] });
14065
+ };
14066
+ function runContributorsUpdate(userId, options) {
14067
+ render41(/* @__PURE__ */ jsx62(ContributorsUpdate, { userId, options }));
14068
+ }
14069
+
14070
+ // src/commands/contributors/remove.tsx
14071
+ import { useState as useState47, useEffect as useEffect44 } from "react";
14072
+ import { render as render42, Box as Box57, Text as Text60, useInput as useInput12 } from "ink";
14073
+ import Spinner39 from "ink-spinner";
14074
+ import { jsx as jsx63, jsxs as jsxs60 } from "react/jsx-runtime";
14075
+ var ContributorsRemove = ({ userId, options }) => {
14076
+ const [member, setMember] = useState47(null);
14077
+ const [isLoading, setIsLoading] = useState47(true);
14078
+ const [error, setError] = useState47(null);
14079
+ const [needsConfirmation, setNeedsConfirmation] = useState47(!options.force);
14080
+ const [isRemoving, setIsRemoving] = useState47(false);
14081
+ const [removed, setRemoved] = useState47(false);
14082
+ useEffect44(() => {
14083
+ const findMember = async () => {
14084
+ try {
14085
+ const config = getProjectConfig();
14086
+ const projectId = options.project || config?.projectId;
14087
+ if (!projectId) {
14088
+ setError("Project ID is required. Use --project <id> or run from a linked directory.");
14089
+ setIsLoading(false);
14090
+ return;
14091
+ }
14092
+ const api = new ContributorsApi();
14093
+ const result = await api.listTeamMembers(projectId);
14094
+ const existingMember = result.members.find((m) => m.id === userId || m.email === userId);
14095
+ if (!existingMember) {
14096
+ setError(`Team member "${userId}" not found`);
14097
+ setIsLoading(false);
14098
+ return;
14099
+ }
14100
+ setMember(existingMember);
14101
+ setIsLoading(false);
14102
+ if (options.force) {
14103
+ await performRemove(projectId, existingMember.id, api);
14104
+ }
14105
+ } catch (err) {
14106
+ setError(err instanceof Error ? err.message : "Failed to find team member");
14107
+ setIsLoading(false);
14108
+ }
14109
+ };
14110
+ findMember();
14111
+ }, [userId, options.force, options.project]);
14112
+ const performRemove = async (projectId, memberId, api) => {
14113
+ try {
14114
+ setIsRemoving(true);
14115
+ await api.removeTeamMember(projectId, memberId);
14116
+ setRemoved(true);
14117
+ setNeedsConfirmation(false);
14118
+ } catch (err) {
14119
+ setError(err instanceof Error ? err.message : "Failed to remove team member");
14120
+ } finally {
14121
+ setIsRemoving(false);
14122
+ }
14123
+ };
14124
+ useInput12((input, inputKey) => {
14125
+ if (!needsConfirmation || isRemoving) return;
14126
+ if (input.toLowerCase() === "y") {
14127
+ const config = getProjectConfig();
14128
+ const projectId = options.project || config?.projectId;
14129
+ if (projectId && member) {
14130
+ const api = new ContributorsApi();
14131
+ performRemove(projectId, member.id, api);
14132
+ }
14133
+ } else if (input.toLowerCase() === "n" || inputKey.escape) {
14134
+ process.exit(0);
14135
+ }
14136
+ });
14137
+ if (error) {
14138
+ return /* @__PURE__ */ jsxs60(Text60, { color: "red", children: [
14139
+ "Error: ",
14140
+ error
14141
+ ] });
14142
+ }
14143
+ if (isLoading) {
14144
+ return /* @__PURE__ */ jsxs60(Text60, { children: [
14145
+ /* @__PURE__ */ jsx63(Text60, { color: "green", children: /* @__PURE__ */ jsx63(Spinner39, { type: "dots" }) }),
14146
+ " Finding team member..."
14147
+ ] });
14148
+ }
14149
+ if (isRemoving) {
14150
+ return /* @__PURE__ */ jsxs60(Text60, { children: [
14151
+ /* @__PURE__ */ jsx63(Text60, { color: "green", children: /* @__PURE__ */ jsx63(Spinner39, { type: "dots" }) }),
14152
+ " Removing team member..."
14153
+ ] });
14154
+ }
14155
+ if (removed) {
14156
+ return /* @__PURE__ */ jsxs60(Box57, { flexDirection: "column", children: [
14157
+ /* @__PURE__ */ jsx63(Text60, { color: "green", children: "\u2713 Team member removed successfully" }),
14158
+ /* @__PURE__ */ jsxs60(Text60, { children: [
14159
+ "Email: ",
14160
+ member?.email
14161
+ ] })
14162
+ ] });
14163
+ }
14164
+ if (needsConfirmation && member) {
14165
+ return /* @__PURE__ */ jsxs60(Box57, { flexDirection: "column", children: [
14166
+ /* @__PURE__ */ jsx63(Box57, { marginBottom: 1, children: /* @__PURE__ */ jsx63(Text60, { color: "yellow", children: "Warning: Remove Team Member" }) }),
14167
+ /* @__PURE__ */ jsxs60(Box57, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
14168
+ /* @__PURE__ */ jsx63(Text60, { children: "You are about to remove the following team member:" }),
14169
+ /* @__PURE__ */ jsx63(Text60, { dimColor: true }),
14170
+ /* @__PURE__ */ jsxs60(Text60, { children: [
14171
+ " Email: ",
14172
+ /* @__PURE__ */ jsx63(Text60, { bold: true, children: member.email })
14173
+ ] }),
14174
+ /* @__PURE__ */ jsxs60(Text60, { children: [
14175
+ " Name: ",
14176
+ /* @__PURE__ */ jsx63(Text60, { bold: true, children: member.name || "-" })
14177
+ ] }),
14178
+ /* @__PURE__ */ jsxs60(Text60, { children: [
14179
+ " Role: ",
14180
+ member.role
14181
+ ] }),
14182
+ member.languages && member.languages.length > 0 && /* @__PURE__ */ jsxs60(Text60, { children: [
14183
+ " Languages: ",
14184
+ member.languages.join(", ")
14185
+ ] })
14186
+ ] }),
14187
+ /* @__PURE__ */ jsxs60(Box57, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
14188
+ /* @__PURE__ */ jsx63(Text60, { color: "red", children: "This will revoke their access to the project." }),
14189
+ /* @__PURE__ */ jsx63(Text60, { dimColor: true, children: "This action cannot be undone." })
14190
+ ] }),
14191
+ /* @__PURE__ */ jsx63(Box57, { marginLeft: 2, children: /* @__PURE__ */ jsxs60(Text60, { children: [
14192
+ "Press",
14193
+ " ",
14194
+ /* @__PURE__ */ jsx63(Text60, { bold: true, color: "green", children: "Y" }),
14195
+ " ",
14196
+ "to confirm,",
14197
+ " ",
14198
+ /* @__PURE__ */ jsx63(Text60, { bold: true, color: "red", children: "N" }),
14199
+ " ",
14200
+ "to cancel"
14201
+ ] }) })
14202
+ ] });
14203
+ }
14204
+ return null;
14205
+ };
14206
+ function runContributorsRemove(userId, options) {
14207
+ render42(/* @__PURE__ */ jsx63(ContributorsRemove, { userId, options }));
14208
+ }
14209
+
14210
+ // src/commands/contributors/index.tsx
14211
+ var contributorsCommand = new Command6("contributors").description("Manage team contributors");
14212
+ contributorsCommand.command("list").description("List all team members").option("--project <id>", "Project ID").option("--format <format>", "Output format (table|json)", "table").action((options) => {
14213
+ runContributorsList({
14214
+ project: options.project,
14215
+ format: options.format
14216
+ });
14217
+ });
14218
+ contributorsCommand.command("add").description("Invite a new team member").requiredOption("--email <email>", "Email address of the person to invite").requiredOption("--role <role>", "Role: owner, admin, member, or viewer").option("--project <id>", "Project ID").option("--languages <langs>", "Languages the member can access (comma-separated, default: all)").option("--message <message>", "Custom invitation message").action((options) => {
14219
+ runContributorsAdd({
14220
+ project: options.project,
14221
+ email: options.email,
14222
+ role: options.role,
14223
+ languages: options.languages,
14224
+ message: options.message
14225
+ });
14226
+ });
14227
+ contributorsCommand.command("update <userId>").description("Update a team member's role or permissions").option("--project <id>", "Project ID").option("--role <role>", "New role: owner, admin, member, or viewer").option("--languages <langs>", "Languages the member can access (comma-separated)").action((userId, options) => {
14228
+ runContributorsUpdate(userId, {
14229
+ project: options.project,
14230
+ role: options.role,
14231
+ languages: options.languages
14232
+ });
14233
+ });
14234
+ contributorsCommand.command("remove <userId>").description("Remove a team member").option("--project <id>", "Project ID").option("--force", "Skip confirmation prompt").action((userId, options) => {
14235
+ runContributorsRemove(userId, {
14236
+ project: options.project,
14237
+ force: options.force
14238
+ });
14239
+ });
14240
+
14241
+ // src/index.tsx
14242
+ var program = new Command7();
14243
+ program.name("intlpull").description("Intelligent i18n CLI for modern apps").version("0.1.5").option("--env-file <path>", "Path to custom env file (e.g., .env.production)").hook("preAction", (thisCommand) => {
14244
+ const envFile = thisCommand.opts().envFile;
14245
+ if (envFile) {
14246
+ setCustomEnvFile(envFile);
14247
+ }
14248
+ const { loaded } = loadEnvFiles();
14249
+ if (loaded.length > 0 && process.env.INTLPULL_DEBUG) {
14250
+ console.log(`Loaded env from: ${loaded.join(", ")}`);
14251
+ }
14252
+ });
14253
+ program.command("init").description("Initialize IntlPull in your project").option("--framework <framework>", "Framework (next|react|vue|svelte|astro)").option("--library <library>", "i18n library (next-intl|react-i18next|vue-i18n)").option("--output <dir>", "Output directory for translation files").option("--project <id>", "Project ID to link").option("-y, --yes", "Auto-detect and initialize without prompts (non-interactive mode)").action((options) => {
14254
+ runInit({
14255
+ framework: options.framework,
14256
+ library: options.library,
14257
+ output: options.output,
14258
+ project: options.project,
14259
+ yes: options.yes
14260
+ });
14261
+ });
14262
+ program.command("status").description("Show project status and translation progress").action(() => {
14263
+ runStatus();
14264
+ });
14265
+ program.command("login").description("Authenticate with IntlPull").option("--token <token>", "Use API token (prefer INTLPULL_API_KEY env var for security)").action((options) => {
14266
+ runLogin({ token: options.token });
14267
+ });
14268
+ program.command("logout").description("Clear stored credentials").action(async () => {
14269
+ const { clearAuthConfig } = await import("./config-F3O3XWYX.js");
14270
+ clearAuthConfig();
14271
+ console.log("\u2713 Logged out successfully");
14272
+ });
14273
+ program.command("whoami").description("Show current authenticated user").action(async () => {
14274
+ const { getResolvedApiKey: getResolvedApiKey2, getAuthErrorMessage: getAuthErrorMessage2 } = await import("./config-F3O3XWYX.js");
14275
+ const { createApiClient: createApiClient2 } = await import("./api-77ZWU4IB.js");
14276
+ const resolved = getResolvedApiKey2();
14277
+ if (!resolved) {
14278
+ console.log(getAuthErrorMessage2());
14279
+ return;
14280
+ }
14281
+ try {
14282
+ const api = createApiClient2();
14283
+ const { projects: projects2 } = await api.listProjects();
14284
+ const sourceLabel = resolved.source === "env" ? "INTLPULL_API_KEY env var" : "~/.intlpull/auth.json";
14285
+ console.log("\n\u2713 Authenticated successfully\n");
14286
+ console.log(" API Key:", resolved.key.substring(0, 20) + "...");
14287
+ console.log(" Source:", sourceLabel);
14288
+ console.log(" Projects:", projects2.length);
14289
+ if (projects2.length > 0) {
14290
+ console.log("\n Your projects:");
14291
+ for (const p of projects2.slice(0, 5)) {
14292
+ console.log(` - ${p.name} (${p.slug})`);
14293
+ }
14294
+ if (projects2.length > 5) {
14295
+ console.log(` ... and ${projects2.length - 5} more`);
14296
+ }
14297
+ }
14298
+ console.log("");
14299
+ } catch (err) {
14300
+ console.log("API Key:", resolved.key.substring(0, 15) + "...");
14301
+ console.error("Error verifying API key:", err instanceof Error ? err.message : "Unknown error");
14302
+ }
14303
+ });
14304
+ program.command("upload").description("Upload translation keys to IntlPull (auto-detects project and files)").option("--project <id>", "Project ID (auto-detected with project-scoped API key)").option("--file <path>", "Specific file to upload (e.g., messages/admin.json)").option("--branch <name>", "Branch name (auto-detected from git, pushes to IntlPull branch)").option("--platform <platform>", "Platform to tag keys with (ios, android, web)").option("--all-languages", "Upload translations for ALL languages (default: true)", true).option("--source-only", "Upload only source language keys", false).option("--dry-run", "Preview without uploading", false).option("-v, --verbose", "Show detailed detection info", false).option("-q, --quiet", "Suppress output except errors (CI mode)").option("--translation-key-prefix <prefix>", 'Prepend prefix to all keys (e.g., "checkout" makes "submit" \u2192 "checkout.submit")').option("--apply-tm", "Apply Translation Memory matches after pushing", false).option("--tm-threshold <n>", "Minimum TM match score (default: 85)", "85").option("--tm-dry-run", "Preview TM matches without applying", false).action((options) => {
14305
+ runUpload({
14306
+ project: options.project,
14307
+ file: options.file,
14308
+ branch: options.branch,
14309
+ platform: options.platform,
14310
+ // --source-only overrides --all-languages
14311
+ allLanguages: options.sourceOnly ? false : options.allLanguages,
14312
+ dryRun: options.dryRun,
14313
+ verbose: options.verbose,
14314
+ quiet: options.quiet,
14315
+ translationKeyPrefix: options.translationKeyPrefix,
14316
+ applyTm: options.applyTm,
14317
+ tmThreshold: options.tmThreshold,
14318
+ tmDryRun: options.tmDryRun
14319
+ });
14320
+ });
14321
+ program.command("download").description("Download translations from IntlPull (auto-detects project, framework, and languages)").option("--project <id>", "Project ID (auto-detected with project-scoped API key)").option("--format <format>", "Output format (json|yaml|ts)", "json").option("--output <dir>", "Output directory (auto-detected from framework)").option("--languages <langs>", "Languages to download (comma-separated, defaults to all)").option("--branch <branch>", "Translation branch to download from").option("--platform <platform>", "Platform variant (default|ios|android|web)").option("--no-parallel", "Disable parallel fetching (use single request)").option("-i, --interactive", "Run in interactive mode with prompts").option("-q, --quiet", "Suppress output except errors").option("--clean-orphans [mode]", 'Remove orphaned files (files not in remote). Use "dry-run" to preview').option("-f, --force", "Skip confirmation prompts for destructive operations").option("--translation-key-prefix <prefix>", 'Strip this prefix from keys (e.g., "checkout" makes "checkout.submit" \u2192 "submit")').option("--filter-by-prefix <prefix>", "Only download keys starting with this prefix").action((options) => {
14322
+ let cleanOrphans;
14323
+ if (options.cleanOrphans === true || options.cleanOrphans === "") {
14324
+ cleanOrphans = true;
14325
+ } else if (options.cleanOrphans === "dry-run") {
14326
+ cleanOrphans = "dry-run";
14327
+ }
14328
+ runDownload({
11406
14329
  project: options.project,
11407
14330
  format: options.format,
11408
14331
  output: options.output,
@@ -11413,7 +14336,9 @@ program.command("download").alias("pull").description("Download translations fro
11413
14336
  interactive: options.interactive,
11414
14337
  quiet: options.quiet,
11415
14338
  cleanOrphans,
11416
- force: options.force
14339
+ force: options.force,
14340
+ translationKeyPrefix: options.translationKeyPrefix,
14341
+ filterByPrefix: options.filterByPrefix
11417
14342
  });
11418
14343
  });
11419
14344
  program.command("listen").alias("watch").description("Watch for translation updates and auto-sync to local files").option("--project <id>", "Project ID").option("--output <dir>", "Output directory (auto-detected from .intlpull.json)").option("--format <format>", "Output format (json|yaml|ts)", "json").option("--languages <langs>", "Languages to sync (comma-separated)").option("--branch <branch>", "Translation branch").option("--platform <platform>", "Platform variant").option("--interval <seconds>", "Polling interval in seconds", "5").option("--timeout <seconds>", "Auto-stop after N seconds (default: 300 = 5 min)").option("--once", "Sync once and exit (no watching)").option("--quiet", "Minimal output").option("--no-parallel", "Disable parallel fetching (use single request)").action((options) => {
@@ -11461,7 +14386,7 @@ program.command("releases [action] [releaseId]").description("Manage Over-The-Ai
11461
14386
  quiet: options.quiet
11462
14387
  });
11463
14388
  });
11464
- program.command("export").description("Export translations as ZIP").option("--format <format>", "Export format (json|yaml|ts|android|ios)", "json").option("--languages <langs>", "Languages to export (comma-separated)").option("--output <file>", "Output file path").option("--version <version>", "Version to export").option("--branch <branch>", "Translation branch").option("--platform <platform>", "Platform variant").option("--project <id>", "Project ID").action((options) => {
14389
+ program.command("export").description("Export translations in various formats").option("--format <format>", "Export format (json|yaml|ts|android|ios|xliff|po|arb|stringsdict|xcstrings|csv)", "json").option("--languages <langs>", "Languages to export (comma-separated)").option("--output <file>", "Output file path").option("--version <version>", "Version to export").option("--branch <branch>", "Translation branch").option("--platform <platform>", "Platform variant").option("--project <id>", "Project ID").action((options) => {
11465
14390
  runExport({
11466
14391
  format: options.format,
11467
14392
  languages: options.languages,
@@ -11472,7 +14397,7 @@ program.command("export").description("Export translations as ZIP").option("--fo
11472
14397
  project: options.project
11473
14398
  });
11474
14399
  });
11475
- program.command("import").description("Import existing translation files").option("--path <path>", "Path to file or directory to import").option("--pattern <pattern>", 'Glob pattern for multi-file import (e.g., "*.json")').option("--format <format>", "File format (json|yaml)", "json").option("--project <id>", "Project ID").option("--language <code>", "Target language code (auto-detect if omitted)").option("--namespace <name>", "Target namespace", "common").option("--branch <name>", "Branch to import translations to").option("--platform <platform>", "Platform to tag imported keys with (ios, android, web)").option("--dry-run", "Preview without importing", false).option("--update-existing", "Update existing translations", true).option("--skip-existing", "Skip if key already exists", false).option("--detect-icu-plurals", "Detect ICU plural syntax", true).action((options) => {
14400
+ program.command("import").description("Import existing translation files").option("--path <path>", "Path to file or directory to import").option("--pattern <pattern>", 'Glob pattern for multi-file import (e.g., "*.json")').option("--format <format>", "File format (json|yaml|xliff|po|arb|stringsdict|xcstrings|csv)", "json").option("--project <id>", "Project ID").option("--language <code>", "Target language code (auto-detect if omitted)").option("--namespace <name>", "Target namespace", "common").option("--branch <name>", "Branch to import translations to").option("--platform <platform>", "Platform to tag imported keys with (ios, android, web)").option("--dry-run", "Preview without importing", false).option("--update-existing", "Update existing translations", true).option("--skip-existing", "Skip if key already exists", false).option("--detect-icu-plurals", "Detect ICU plural syntax", true).action((options) => {
11476
14401
  runImport({
11477
14402
  path: options.path,
11478
14403
  pattern: options.pattern,
@@ -11526,7 +14451,7 @@ program.command("fix").description("Auto-fix missing translations").option("--so
11526
14451
  dryRun: options.dryRun
11527
14452
  });
11528
14453
  });
11529
- program.command("diff").description("Show what would change on push/pull").option("--source <lang>", "Source language").option("--target <lang>", "Target language to compare").action((options) => {
14454
+ program.command("diff").description("Show what would change on upload/download").option("--source <lang>", "Source language").option("--target <lang>", "Target language to compare").action((options) => {
11530
14455
  runDiff({
11531
14456
  source: options.source,
11532
14457
  target: options.target
@@ -11534,12 +14459,12 @@ program.command("diff").description("Show what would change on push/pull").optio
11534
14459
  });
11535
14460
  var projects = program.command("projects").description("Manage projects");
11536
14461
  projects.command("list").description("List all projects").action(async () => {
11537
- const { isAuthenticated } = await import("./config-DX34FMWV.js");
14462
+ const { isAuthenticated } = await import("./config-F3O3XWYX.js");
11538
14463
  if (!isAuthenticated()) {
11539
14464
  console.log("Not authenticated. Run `npx @intlpullhq/cli login` first.");
11540
14465
  return;
11541
14466
  }
11542
- const { createApiClient: createApiClient2 } = await import("./api-K3AY4VAI.js");
14467
+ const { createApiClient: createApiClient2 } = await import("./api-77ZWU4IB.js");
11543
14468
  try {
11544
14469
  const api = createApiClient2();
11545
14470
  const { projects: projects2 } = await api.listProjects();
@@ -11560,12 +14485,12 @@ projects.command("list").description("List all projects").action(async () => {
11560
14485
  }
11561
14486
  });
11562
14487
  projects.command("create <name>").description("Create a new project").option("--source-language <lang>", "Source language", "en").option("--languages <langs>", "Supported languages (comma-separated)", "en").action(async (name, options) => {
11563
- const { isAuthenticated } = await import("./config-DX34FMWV.js");
14488
+ const { isAuthenticated } = await import("./config-F3O3XWYX.js");
11564
14489
  if (!isAuthenticated()) {
11565
14490
  console.log("Not authenticated. Run `npx @intlpullhq/cli login` first.");
11566
14491
  return;
11567
14492
  }
11568
- const { createApiClient: createApiClient2 } = await import("./api-K3AY4VAI.js");
14493
+ const { createApiClient: createApiClient2 } = await import("./api-77ZWU4IB.js");
11569
14494
  try {
11570
14495
  const api = createApiClient2();
11571
14496
  const languages = options.languages.split(",").map((l) => l.trim());
@@ -11584,7 +14509,7 @@ projects.command("create <name>").description("Create a new project").option("--
11584
14509
  }
11585
14510
  });
11586
14511
  projects.command("link <id>").description("Link current directory to a project").action(async (id) => {
11587
- const { getProjectConfig: getProjectConfig2, saveProjectConfig: saveProjectConfig3, detectFramework: detectFramework2 } = await import("./config-DX34FMWV.js");
14512
+ const { getProjectConfig: getProjectConfig2, saveProjectConfig: saveProjectConfig3, detectFramework: detectFramework2 } = await import("./config-F3O3XWYX.js");
11588
14513
  const existing = getProjectConfig2();
11589
14514
  const detected = detectFramework2();
11590
14515
  const config = {
@@ -11666,4 +14591,18 @@ zendesk.command("disconnect").description("Disconnect Zendesk integration").acti
11666
14591
  runZendeskDisconnect();
11667
14592
  });
11668
14593
  program.addCommand(documentsCommand);
14594
+ program.addCommand(keysCommand);
14595
+ program.addCommand(tmCommand);
14596
+ program.addCommand(snapshotsCommand);
14597
+ program.addCommand(webhooksCommand);
14598
+ program.addCommand(contributorsCommand);
14599
+ program.command("cleanup").description("Detect and delete orphaned translation keys").option("--project <id>", "Project ID (auto-detected with project-scoped API key)").option("--scan-codebase", "Scan local files for key usage patterns", false).option("--dry-run", "Preview orphans without deleting", false).option("--days <n>", "Only consider keys not updated in N days", parseInt).option("-f, --force", "Skip confirmation prompt", false).action((options) => {
14600
+ runCleanup({
14601
+ project: options.project,
14602
+ scanCodebase: options.scanCodebase,
14603
+ dryRun: options.dryRun,
14604
+ days: options.days,
14605
+ force: options.force
14606
+ });
14607
+ });
11669
14608
  program.parse();