@ownlate/cli 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.cjs +226 -135
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -5,20 +5,37 @@ let node_fs_promises = require("node:fs/promises");
5
5
  let node_os = require("node:os");
6
6
  let node_path = require("node:path");
7
7
 
8
+ //#region src/lib/global-config.ts
9
+ const CONFIG_PATH = (0, node_path.join)((0, node_os.homedir)(), ".ownlate");
10
+ const API_URL = process.env.DEPLOY_ENV === "dev" ? "http://127.0.0.1:3000" : "https://api.ownlate.com";
11
+ async function readGlobalConfig() {
12
+ try {
13
+ const raw = await (0, node_fs_promises.readFile)(CONFIG_PATH, "utf-8");
14
+ return JSON.parse(raw);
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+ async function writeGlobalConfig(config) {
20
+ await (0, node_fs_promises.writeFile)(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
21
+ }
22
+ async function requireGlobalConfig() {
23
+ const config = await readGlobalConfig();
24
+ if (!config?.apiKey) throw new Error("Not logged in. Run \"ownlate login\" first.");
25
+ return config;
26
+ }
27
+
28
+ //#endregion
8
29
  //#region src/lib/api-client.ts
9
30
  var ApiClient = class {
10
- constructor(baseUrl, apiKey, workspaceId) {
11
- this.baseUrl = baseUrl.replace(/\/$/, "");
31
+ constructor(apiKey) {
12
32
  this.apiKey = apiKey;
13
- this.workspaceId = workspaceId;
14
33
  }
15
34
  authHeaders() {
16
- const headers = { Authorization: `Bearer ${this.apiKey}` };
17
- if (this.workspaceId) headers["X-Tenant-Id"] = this.workspaceId;
18
- return headers;
35
+ return { Authorization: `Bearer ${this.apiKey}` };
19
36
  }
20
37
  async request(path, init) {
21
- const res = await fetch(`${this.baseUrl}${path}`, {
38
+ const res = await fetch(`${API_URL}${path}`, {
22
39
  ...init,
23
40
  headers: {
24
41
  ...this.authHeaders(),
@@ -37,6 +54,27 @@ var ApiClient = class {
37
54
  async listProjects(workspaceId) {
38
55
  return this.request(`/v1/projects?workspaceId=${encodeURIComponent(workspaceId)}`);
39
56
  }
57
+ async createProject(dto) {
58
+ return this.request("/v1/projects", {
59
+ method: "POST",
60
+ headers: { "Content-Type": "application/json" },
61
+ body: JSON.stringify(dto)
62
+ });
63
+ }
64
+ async getProgress(projectId, targetLanguages) {
65
+ const query = new URLSearchParams({
66
+ projectId,
67
+ targetLanguages: targetLanguages.join(",")
68
+ });
69
+ return this.request(`/v1/segments/progress?${query}`);
70
+ }
71
+ async preTranslate(params) {
72
+ await this.request("/v1/segments/pre-translate", {
73
+ method: "POST",
74
+ headers: { "Content-Type": "application/json" },
75
+ body: JSON.stringify(params)
76
+ });
77
+ }
40
78
  async upload(dto) {
41
79
  return this.request("/v1/translation-files/upload", {
42
80
  method: "POST",
@@ -52,7 +90,7 @@ var ApiClient = class {
52
90
  });
53
91
  if (params.fileId) query.set("fileId", params.fileId);
54
92
  if (params.sourceLanguage) query.set("sourceLanguage", params.sourceLanguage);
55
- const res = await fetch(`${this.baseUrl}/v1/segments/export?${query}`, { headers: this.authHeaders() });
93
+ const res = await fetch(`${API_URL}/v1/segments/export?${query}`, { headers: this.authHeaders() });
56
94
  if (!res.ok) {
57
95
  const text = await res.text();
58
96
  throw new Error(`HTTP ${res.status}: ${text}`);
@@ -61,46 +99,21 @@ var ApiClient = class {
61
99
  }
62
100
  };
63
101
 
64
- //#endregion
65
- //#region src/lib/global-config.ts
66
- const CONFIG_PATH = (0, node_path.join)((0, node_os.homedir)(), ".ownlate");
67
- const DEFAULT_API_URL = "https://api.ownlate.com";
68
- async function readGlobalConfig() {
69
- try {
70
- const raw = await (0, node_fs_promises.readFile)(CONFIG_PATH, "utf-8");
71
- return JSON.parse(raw);
72
- } catch {
73
- return null;
74
- }
75
- }
76
- async function writeGlobalConfig(config) {
77
- await (0, node_fs_promises.writeFile)(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
78
- }
79
- async function requireGlobalConfig() {
80
- const config = await readGlobalConfig();
81
- if (!config?.apiKey) throw new Error("Not logged in. Run \"ownlate login\" first.");
82
- return config;
83
- }
84
-
85
102
  //#endregion
86
103
  //#region src/commands/login.ts
87
- async function ask$1(rl, question, fallback = "") {
88
- return (await rl.question(question)).trim() || fallback;
89
- }
90
- async function loginCommand(options) {
104
+ async function loginCommand() {
91
105
  const rl = (0, node_readline_promises.createInterface)({
92
106
  input: process.stdin,
93
107
  output: process.stdout
94
108
  });
95
109
  try {
96
- const apiUrl = options.url ?? await ask$1(rl, `API URL [${DEFAULT_API_URL}]: `, DEFAULT_API_URL);
97
- const apiKey = await ask$1(rl, "API Key: ");
110
+ const apiKey = (await rl.question("API Key: ")).trim();
98
111
  if (!apiKey) {
99
112
  console.error("API Key is required.");
100
113
  process.exit(1);
101
114
  }
102
- process.stdout.write("\nConnecting...");
103
- const client = new ApiClient(apiUrl, apiKey);
115
+ process.stdout.write("Connecting...");
116
+ const client = new ApiClient(apiKey);
104
117
  let workspaces;
105
118
  try {
106
119
  workspaces = await client.listWorkspaces();
@@ -110,26 +123,17 @@ async function loginCommand(options) {
110
123
  console.error(`\n${err.message}`);
111
124
  process.exit(1);
112
125
  }
113
- let currentWorkspaceId;
114
- if (workspaces.length === 0) console.log("\nNo workspaces found. Create one in the web app first.");
115
- else if (workspaces.length === 1) {
116
- currentWorkspaceId = workspaces[0].id;
117
- console.log(`\nUsing workspace: ${workspaces[0].name}`);
118
- } else {
119
- console.log("\nAvailable workspaces:");
120
- workspaces.forEach((w, i) => console.log(` ${i + 1}. ${w.name}`));
121
- const choice = await ask$1(rl, "\nSelect workspace [1]: ", "1");
122
- const idx = parseInt(choice, 10) - 1;
123
- const picked = workspaces[Math.max(0, Math.min(idx, workspaces.length - 1))];
124
- currentWorkspaceId = picked.id;
125
- console.log(`Using: ${picked.name}`);
126
+ if (workspaces.length === 0) {
127
+ console.error("\nNo workspace found for this API key.");
128
+ process.exit(1);
126
129
  }
130
+ const workspace = workspaces[0];
127
131
  await writeGlobalConfig({
128
- apiUrl,
129
132
  apiKey,
130
- currentWorkspaceId
133
+ workspaceId: workspace.id
131
134
  });
132
- console.log("\nLogged in. Run \"ownlate init\" to set up a project.");
135
+ console.log(`\nLogged in to workspace: ${workspace.name}`);
136
+ console.log("Run \"ownlate project list\" to see your projects.");
133
137
  } finally {
134
138
  rl.close();
135
139
  }
@@ -147,48 +151,45 @@ async function logoutCommand() {
147
151
  }
148
152
 
149
153
  //#endregion
150
- //#region src/commands/workspace.ts
151
- async function workspaceListCommand() {
154
+ //#region src/commands/project.ts
155
+ async function projectListCommand() {
152
156
  const auth = await requireGlobalConfig();
153
- const workspaces = await new ApiClient(auth.apiUrl, auth.apiKey, auth.currentWorkspaceId).listWorkspaces();
154
- if (workspaces.length === 0) {
155
- console.log("No workspaces found.");
157
+ const projects = await new ApiClient(auth.apiKey).listProjects(auth.workspaceId);
158
+ if (projects.length === 0) {
159
+ console.log("No projects found.");
156
160
  return;
157
161
  }
158
- for (const w of workspaces) {
159
- const marker = w.id === auth.currentWorkspaceId ? "* " : " ";
160
- console.log(`${marker}${w.name} ${w.id}`);
161
- }
162
+ for (const p of projects) console.log(` ${p.name.padEnd(32)} ${p.id}`);
162
163
  }
163
- async function workspaceUseCommand(idOrName) {
164
+ async function projectCreateCommand(name, options) {
164
165
  const auth = await requireGlobalConfig();
165
- const found = (await new ApiClient(auth.apiUrl, auth.apiKey, auth.currentWorkspaceId).listWorkspaces()).find((w) => w.id === idOrName || w.name === idOrName);
166
- if (!found) {
167
- console.error(`Workspace not found: "${idOrName}"`);
168
- console.log("Run \"ownlate workspace list\" to see available workspaces.");
169
- process.exit(1);
170
- }
171
- await writeGlobalConfig({
172
- ...auth,
173
- currentWorkspaceId: found.id
166
+ const rl = (0, node_readline_promises.createInterface)({
167
+ input: process.stdin,
168
+ output: process.stdout
174
169
  });
175
- console.log(`Now using workspace: ${found.name}`);
176
- }
177
-
178
- //#endregion
179
- //#region src/commands/project.ts
180
- async function projectListCommand() {
181
- const auth = await requireGlobalConfig();
182
- if (!auth.currentWorkspaceId) {
183
- console.error("No workspace selected. Run \"ownlate workspace use <id>\" first.");
184
- process.exit(1);
185
- }
186
- const projects = await new ApiClient(auth.apiUrl, auth.apiKey, auth.currentWorkspaceId).listProjects(auth.currentWorkspaceId);
187
- if (projects.length === 0) {
188
- console.log("No projects found in current workspace.");
189
- return;
170
+ try {
171
+ const ask = async (question, fallback = "") => {
172
+ return (await rl.question(question)).trim() || fallback;
173
+ };
174
+ const projectName = name ?? await ask("Project name: ");
175
+ if (!projectName) {
176
+ console.error("Project name is required.");
177
+ process.exit(1);
178
+ }
179
+ const sourceLanguage = options.source ?? await ask("Source language [en]: ", "en");
180
+ const targetLanguages = (options.langs ?? await ask("Target languages, comma-separated [fr,de]: ", "fr,de")).split(",").map((l) => l.trim()).filter(Boolean);
181
+ const result = await new ApiClient(auth.apiKey).createProject({
182
+ workspaceId: auth.workspaceId,
183
+ name: projectName,
184
+ sourceLanguage,
185
+ targetLanguages
186
+ });
187
+ console.log(`\nCreated project: ${projectName}`);
188
+ console.log(`ID: ${result.id}`);
189
+ console.log(`\nRun "ownlate init" in your project directory to set it up.`);
190
+ } finally {
191
+ rl.close();
190
192
  }
191
- for (const p of projects) console.log(` ${p.name} ${p.id}`);
192
193
  }
193
194
 
194
195
  //#endregion
@@ -204,37 +205,29 @@ async function initCommand(options) {
204
205
  process.exit(1);
205
206
  } catch {}
206
207
  const auth = await requireGlobalConfig();
207
- const client = new ApiClient(auth.apiUrl, auth.apiKey, auth.currentWorkspaceId);
208
+ const client = new ApiClient(auth.apiKey);
208
209
  const rl = (0, node_readline_promises.createInterface)({
209
210
  input: process.stdin,
210
211
  output: process.stdout
211
212
  });
212
213
  try {
213
- console.log("Initializing ownlate project config...\n");
214
+ let projects = [];
215
+ try {
216
+ projects = await client.listProjects(auth.workspaceId);
217
+ } catch {}
214
218
  let projectId;
215
- if (auth.currentWorkspaceId) {
216
- let projects = [];
217
- try {
218
- projects = await client.listProjects(auth.currentWorkspaceId);
219
- } catch {}
220
- if (projects.length > 0) {
221
- console.log("Projects in current workspace:");
222
- projects.forEach((p, i) => console.log(` ${i + 1}. ${p.name} (${p.id})`));
223
- console.log(` ${projects.length + 1}. Enter ID manually\n`);
224
- const choice = await ask(rl, "Select project: ");
225
- const idx = parseInt(choice, 10) - 1;
226
- if (idx >= 0 && idx < projects.length) {
227
- projectId = projects[idx].id;
228
- console.log(`Using: ${projects[idx].name}`);
229
- } else projectId = await ask(rl, "Project ID: ");
230
- } else projectId = await ask(rl, "Project ID: ");
231
- } else {
232
- console.log("No workspace selected. Run \"ownlate workspace use <id>\" to pick one, or enter the project ID manually.\n");
233
- projectId = await ask(rl, "Project ID: ");
234
- }
219
+ if (projects.length > 0) {
220
+ console.log("Projects:\n");
221
+ projects.forEach((p, i) => console.log(` ${i + 1}. ${p.name} (${p.id})`));
222
+ console.log(` ${projects.length + 1}. Enter ID manually\n`);
223
+ const choice = await ask(rl, "Select project: ");
224
+ const idx = parseInt(choice, 10) - 1;
225
+ if (idx >= 0 && idx < projects.length) projectId = projects[idx].id;
226
+ else projectId = await ask(rl, "Project ID: ");
227
+ } else projectId = await ask(rl, "Project ID: ");
235
228
  const sourceLanguage = await ask(rl, "Source language [en]: ", "en");
236
229
  const targetLangsRaw = await ask(rl, "Target languages, comma-separated [fr,de]: ", "fr,de");
237
- const sourceFile = await ask(rl, "Source file path [locales/en.json]: ", "locales/en.json");
230
+ const sourceFile = await ask(rl, "Source file [locales/en.json]: ", "locales/en.json");
238
231
  const outputPattern = await ask(rl, "Output pattern [locales/{{language}}.json]: ", "locales/{{language}}.json");
239
232
  const config = {
240
233
  projectId,
@@ -247,9 +240,8 @@ async function initCommand(options) {
247
240
  };
248
241
  await (0, node_fs_promises.writeFile)(configPath, JSON.stringify(config, null, 2) + "\n");
249
242
  console.log(`\nCreated ${configPath}`);
250
- console.log("\nNext steps:");
251
- console.log(" ownlate pushupload source strings to ownlate");
252
- console.log(" ownlate pull — download translations to local files");
243
+ console.log("\n ownlate push — upload source strings");
244
+ console.log(" ownlate pulldownload translations");
253
245
  } finally {
254
246
  rl.close();
255
247
  }
@@ -287,24 +279,21 @@ function detectFormat$1(filePath) {
287
279
  if (ext === ".po") return "po";
288
280
  throw new Error(`Unsupported extension "${ext}". Use .json, .yaml, .yml or .po`);
289
281
  }
290
- async function pushCommand(options) {
291
- const [auth, config] = await Promise.all([requireGlobalConfig(), readProjectConfig(options.config)]);
292
- const client = new ApiClient(auth.apiUrl, auth.apiKey, auth.currentWorkspaceId);
282
+ async function pushAll(client, config, configPath, prefix = "") {
293
283
  let configUpdated = false;
294
- console.log(`Pushing to ${auth.apiUrl} (project: ${config.projectId})\n`);
295
284
  for (const file of config.files) {
296
285
  const absPath = (0, node_path.resolve)(file.source);
297
286
  let content;
298
287
  try {
299
288
  content = await (0, node_fs_promises.readFile)(absPath, "utf-8");
300
289
  } catch {
301
- console.error(` [skip] ${file.source} — file not found`);
290
+ console.error(`${prefix} [skip] ${file.source} — file not found (${absPath})`);
302
291
  continue;
303
292
  }
304
293
  const format = detectFormat$1(file.source);
305
294
  const name = (0, node_path.basename)(file.source);
306
295
  const dir = (0, node_path.dirname)(file.source);
307
- process.stdout.write(` ${file.source} ...`);
296
+ process.stdout.write(`${prefix} pushing ${file.source} ...`);
308
297
  try {
309
298
  const result = await client.upload({
310
299
  projectId: config.projectId,
@@ -321,12 +310,41 @@ async function pushCommand(options) {
321
310
  console.log(` ${result.segmentsCreated} segments`);
322
311
  } catch (err) {
323
312
  console.log(" failed");
324
- console.error(` [!] ${err.message}`);
313
+ console.error(`${prefix} [!] ${err.message}`);
325
314
  }
326
315
  }
327
- if (configUpdated) {
328
- await writeProjectConfig(options.config, config);
329
- console.log(`\nSaved file IDs to ${options.config}`);
316
+ if (configUpdated) await writeProjectConfig(configPath, config);
317
+ }
318
+ async function pushCommand(options) {
319
+ const configPath = (0, node_path.resolve)(options.config);
320
+ const [auth, config] = await Promise.all([requireGlobalConfig(), readProjectConfig(configPath)]);
321
+ const client = new ApiClient(auth.apiKey);
322
+ console.log(`project: ${config.projectId}`);
323
+ console.log(`config: ${configPath}\n`);
324
+ await pushAll(client, config, configPath);
325
+ if (!options.watch) return;
326
+ const sources = config.files.map((f) => (0, node_path.resolve)(f.source));
327
+ console.log(`\nWatching ${sources.map((s) => s.split("/").pop()).join(", ")}...\n`);
328
+ const timers = /* @__PURE__ */ new Map();
329
+ for (const absPath of sources) watchFile(absPath, client, config, configPath, timers);
330
+ }
331
+ async function watchFile(absPath, client, config, configPath, timers) {
332
+ try {
333
+ const watcher = (0, node_fs_promises.watch)(absPath);
334
+ for await (const event of watcher) {
335
+ if (event.eventType !== "change") continue;
336
+ const existing = timers.get(absPath);
337
+ if (existing) clearTimeout(existing);
338
+ timers.set(absPath, setTimeout(async () => {
339
+ const time = (/* @__PURE__ */ new Date()).toLocaleTimeString();
340
+ process.stdout.write(`[${time}] ${absPath.split("/").pop()} changed → `);
341
+ await pushAll(client, config, configPath, "");
342
+ }, 300));
343
+ }
344
+ } catch {
345
+ setTimeout(() => {
346
+ watchFile(absPath, client, config, configPath, timers);
347
+ }, 1e3);
330
348
  }
331
349
  }
332
350
 
@@ -340,10 +358,12 @@ function detectFormat(pattern) {
340
358
  throw new Error(`Cannot detect format from output pattern "${pattern}"`);
341
359
  }
342
360
  async function pullCommand(options) {
343
- const [auth, config] = await Promise.all([requireGlobalConfig(), readProjectConfig(options.config)]);
344
- const client = new ApiClient(auth.apiUrl, auth.apiKey, auth.currentWorkspaceId);
361
+ const configPath = (0, node_path.resolve)(options.config);
362
+ const [auth, config] = await Promise.all([requireGlobalConfig(), readProjectConfig(configPath)]);
363
+ const client = new ApiClient(auth.apiKey);
345
364
  const languages = options.lang ? [options.lang] : config.targetLanguages;
346
- console.log(`Pulling from ${auth.apiUrl} (project: ${config.projectId})\n`);
365
+ console.log(`project: ${config.projectId}`);
366
+ console.log(`config: ${configPath}\n`);
347
367
  for (const file of config.files) {
348
368
  const format = detectFormat(file.output);
349
369
  for (const language of languages) {
@@ -369,24 +389,95 @@ async function pullCommand(options) {
369
389
  }
370
390
  }
371
391
 
392
+ //#endregion
393
+ //#region src/commands/status.ts
394
+ const BAR_WIDTH = 24;
395
+ function bar(progress) {
396
+ const filled = Math.round(progress / 100 * BAR_WIDTH);
397
+ return "█".repeat(filled) + "░".repeat(BAR_WIDTH - filled);
398
+ }
399
+ function pad(s, n) {
400
+ return s.length >= n ? s : s + " ".repeat(n - s.length);
401
+ }
402
+ async function statusCommand(options) {
403
+ const configPath = (0, node_path.resolve)(options.config);
404
+ const [auth, config] = await Promise.all([requireGlobalConfig(), readProjectConfig(configPath)]);
405
+ const client = new ApiClient(auth.apiKey);
406
+ let items;
407
+ try {
408
+ items = await client.getProgress(config.projectId, config.targetLanguages);
409
+ } catch (err) {
410
+ console.error(`Failed: ${err.message}`);
411
+ process.exit(1);
412
+ }
413
+ if (items.length === 0) {
414
+ console.log("No segments found for this project.");
415
+ return;
416
+ }
417
+ console.log(`project: ${config.projectId}\n`);
418
+ for (const item of items) {
419
+ const pct = `${item.progress}%`.padStart(4);
420
+ const detail = `(${item.approved}/${item.total} approved)`;
421
+ console.log(` ${pad(item.language, 6)} ${bar(item.progress)} ${pct} ${detail}`);
422
+ }
423
+ const minArg = options.min ? parseInt(options.min, 10) : void 0;
424
+ if (minArg !== void 0) {
425
+ const below = items.filter((i) => i.progress < minArg);
426
+ if (below.length > 0) {
427
+ console.error(`\nFailed: ${below.map((i) => i.language).join(", ")} below ${minArg}%`);
428
+ process.exit(1);
429
+ }
430
+ }
431
+ }
432
+
433
+ //#endregion
434
+ //#region src/commands/translate.ts
435
+ async function translateCommand(options) {
436
+ const configPath = (0, node_path.resolve)(options.config);
437
+ const [auth, config] = await Promise.all([requireGlobalConfig(), readProjectConfig(configPath)]);
438
+ const client = new ApiClient(auth.apiKey);
439
+ const languages = options.lang ? [options.lang] : config.targetLanguages;
440
+ const minTmScore = options.score ? parseInt(options.score, 10) : 100;
441
+ console.log(`project: ${config.projectId}`);
442
+ console.log(`score: ≥${minTmScore}%\n`);
443
+ for (const lang of languages) {
444
+ process.stdout.write(` pre-translating ${lang} ...`);
445
+ try {
446
+ await client.preTranslate({
447
+ projectId: config.projectId,
448
+ workspaceId: auth.workspaceId,
449
+ targetLanguage: lang,
450
+ sourceLanguage: config.sourceLanguage,
451
+ minTmScore
452
+ });
453
+ console.log(" queued");
454
+ } catch (err) {
455
+ console.log(" failed");
456
+ console.error(` [!] ${err.message}`);
457
+ }
458
+ }
459
+ console.log("\nTranslation runs in background. Use \"ownlate status\" to check progress.");
460
+ }
461
+
372
462
  //#endregion
373
463
  //#region src/index.ts
374
464
  const program = new commander.Command();
375
- program.name("ownlate").description("Sync translation files with your ownlate project").version("1.0.1");
376
- program.command("login").description("Log in with your API key").option("--url <url>", "API URL (default: https://api.ownlate.com)").action(loginCommand);
465
+ program.name("ownlate").description("Sync translation files with your ownlate project").version("1.0.4");
466
+ program.command("login").description("Log in with your API key").action(loginCommand);
377
467
  program.command("logout").description("Remove stored credentials").action(logoutCommand);
378
- const workspace = program.command("workspace").description("Manage workspaces");
379
- workspace.command("list").description("List available workspaces").action(workspaceListCommand);
380
- workspace.command("use <id>").description("Switch current workspace by ID or name").action(workspaceUseCommand);
381
- program.command("project").description("Manage projects").command("list").description("List projects in the current workspace").action(projectListCommand);
468
+ const project = program.command("project").description("Manage projects");
469
+ project.command("list").description("List projects in your workspace").action(projectListCommand);
470
+ project.command("create [name]").description("Create a new project").option("-s, --source <lang>", "source language (e.g. en)").option("-l, --langs <codes>", "target languages, comma-separated (e.g. fr,de)").action(projectCreateCommand);
382
471
  const configOpt = [
383
472
  "-c, --config <path>",
384
473
  "path to project config file",
385
474
  ".ownlate.json"
386
475
  ];
387
476
  program.command("init").description("Create .ownlate.json for this project").option(...configOpt).action(initCommand);
388
- program.command("push").description("Upload source files creates/updates segments in ownlate").option(...configOpt).action(pushCommand);
389
- program.command("pull").description("Download translated files from ownlate to local filesystem").option(...configOpt).option("-l, --lang <code>", "pull a single language only (e.g. fr)").action(pullCommand);
477
+ program.command("push").description("Upload source files to ownlate").option(...configOpt).option("-w, --watch", "watch source files and push on change").action(pushCommand);
478
+ program.command("pull").description("Download translations to local files").option(...configOpt).option("-l, --lang <code>", "pull a single language only").action(pullCommand);
479
+ program.command("status").description("Show translation progress for each language").option(...configOpt).option("--min <percent>", "exit with code 1 if any language is below this threshold (for CI)").action(statusCommand);
480
+ program.command("translate").description("Pre-translate untranslated segments using Translation Memory").option(...configOpt).option("-l, --lang <code>", "translate a single language only").option("-s, --score <n>", "minimum TM match score 0–100 (default: 100)").action(translateCommand);
390
481
  program.parseAsync(process.argv).catch((err) => {
391
482
  console.error(err.message);
392
483
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ownlate/cli",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Ownlate CLI — push source files and pull translations",
5
5
  "type": "module",
6
6
  "files": [