@i18n-agent/cli 1.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 i18n Agent Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # @i18n-agent/cli
2
+
3
+ Terminal client for the i18n-agent translation service. Translate text and files directly from your terminal.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @i18n-agent/cli
9
+ ```
10
+
11
+ Requires Node.js >= 18.
12
+
13
+ ## Setup
14
+
15
+ ```bash
16
+ # Save your API key (get one at https://app.i18nagent.ai)
17
+ i18nagent login
18
+ ```
19
+
20
+ Or set via environment variable (for CI/CD):
21
+ ```bash
22
+ export I18N_AGENT_API_KEY=your-key-here
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### Translate text
28
+ ```bash
29
+ i18nagent translate "Hello world" --lang es
30
+ i18nagent translate "Hello world" --lang es,fr,ja
31
+ ```
32
+
33
+ ### Translate files
34
+ ```bash
35
+ i18nagent translate ./locales/en.json --lang es,fr
36
+ i18nagent translate ./docs/guide.md --lang de --namespace my-project
37
+ ```
38
+
39
+ ### Interactive mode
40
+ ```bash
41
+ i18nagent translate # prompts for input
42
+ i18nagent tui # full interactive menu
43
+ ```
44
+
45
+ ### Check job status
46
+ ```bash
47
+ i18nagent status <jobId>
48
+ ```
49
+
50
+ ### Download translations
51
+ ```bash
52
+ i18nagent download <jobId> --output ./locales
53
+ ```
54
+
55
+ ### Resume failed jobs
56
+ ```bash
57
+ i18nagent resume <jobId>
58
+ ```
59
+
60
+ ### Check credits
61
+ ```bash
62
+ i18nagent credits
63
+ ```
64
+
65
+ ### Upload existing translations
66
+ ```bash
67
+ i18nagent upload ./de.json --source en --target de --namespace my-project
68
+ ```
69
+
70
+ ### List supported languages
71
+ ```bash
72
+ i18nagent languages
73
+ ```
74
+
75
+ ### Analyze content
76
+ ```bash
77
+ i18nagent analyze ./en.json --lang es
78
+ ```
79
+
80
+ ## JSON output
81
+
82
+ All commands support `--json` for machine-readable output:
83
+ ```bash
84
+ i18nagent translate "Hello" --lang es --json
85
+ i18nagent credits --json
86
+ ```
87
+
88
+ ## Config
89
+
90
+ Config is stored at `~/.config/i18nagent/config.json`:
91
+ ```json
92
+ {
93
+ "apiKey": "i18n_...",
94
+ "defaultLanguages": ["es", "fr"],
95
+ "defaultNamespace": "my-project"
96
+ }
97
+ ```
98
+
99
+ Environment variable `I18N_AGENT_API_KEY` takes priority over config file.
100
+
101
+ ## Binary distribution
102
+
103
+ See [docs/BINARY_DISTRIBUTION.md](docs/BINARY_DISTRIBUTION.md) for standalone binary packaging.
104
+
105
+ ## License
106
+
107
+ MIT
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createProgram } from '../src/index.js';
4
+
5
+ const program = createProgram();
6
+ program.parse(process.argv);
@@ -0,0 +1,108 @@
1
+ # Binary Distribution Strategy
2
+
3
+ Notes on future standalone binary packaging for `@i18n-agent/cli`.
4
+
5
+ ## Current: npm only
6
+
7
+ ```bash
8
+ npm install -g @i18n-agent/cli
9
+ ```
10
+
11
+ Requires Node.js >= 18.
12
+
13
+ ## Future Options
14
+
15
+ ### Option 1: Node.js SEA (Single Executable Applications)
16
+
17
+ Available since Node.js 20. Bundles the app into a single executable.
18
+
19
+ **Pros:** Official Node.js feature, no external tools
20
+ **Cons:** Large binary (~50MB), Node 20+ only for building
21
+
22
+ ```bash
23
+ # 1. Generate SEA config
24
+ echo '{"main":"bin/i18nagent.js","output":"sea-prep.blob"}' > sea-config.json
25
+
26
+ # 2. Generate the blob
27
+ node --experimental-sea-config sea-config.json
28
+
29
+ # 3. Copy node binary and inject
30
+ cp $(which node) i18nagent
31
+ npx postject i18nagent NODE_SEA_BLOB sea-prep.blob \
32
+ --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
33
+
34
+ # 4. Sign on macOS
35
+ codesign --sign - i18nagent
36
+ ```
37
+
38
+ ### Option 2: Bun compile
39
+
40
+ Bun has built-in single-file compilation.
41
+
42
+ **Pros:** Fast, small binaries (~10-20MB), cross-compile support
43
+ **Cons:** Requires Bun, may have Node.js compatibility edge cases
44
+
45
+ ```bash
46
+ bun build --compile bin/i18nagent.js --outfile i18nagent
47
+ # Cross-compile:
48
+ bun build --compile --target=bun-linux-x64 bin/i18nagent.js --outfile i18nagent-linux
49
+ bun build --compile --target=bun-darwin-arm64 bin/i18nagent.js --outfile i18nagent-macos
50
+ ```
51
+
52
+ ### Option 3: pkg (legacy)
53
+
54
+ Vercel's pkg tool. Deprecated but still works.
55
+
56
+ ```bash
57
+ npx pkg bin/i18nagent.js --targets node18-macos-arm64,node18-linux-x64,node18-win-x64
58
+ ```
59
+
60
+ ### Option 4: esbuild + SEA
61
+
62
+ Bundle with esbuild first (tree-shaking, single file), then use SEA.
63
+
64
+ ```bash
65
+ npx esbuild bin/i18nagent.js --bundle --platform=node --outfile=dist/i18nagent.cjs --format=cjs
66
+ # Then use SEA on the bundled file
67
+ ```
68
+
69
+ ## Recommended Approach
70
+
71
+ **Bun compile** for simplest DX and smallest binaries. Fall back to **Node.js SEA** if Bun compatibility issues arise.
72
+
73
+ ## Distribution Channels
74
+
75
+ 1. **GitHub Releases** - attach binaries for macOS (arm64, x64), Linux (x64, arm64), Windows (x64)
76
+ 2. **Homebrew tap** - `brew install i18n-agent/tap/i18nagent`
77
+ 3. **curl installer** - `curl -fsSL https://i18nagent.ai/install.sh | sh`
78
+
79
+ ## CI/CD
80
+
81
+ Use GitHub Actions to build binaries on each release tag:
82
+
83
+ ```yaml
84
+ # .github/workflows/release-binaries.yml
85
+ on:
86
+ release:
87
+ types: [published]
88
+
89
+ jobs:
90
+ build:
91
+ strategy:
92
+ matrix:
93
+ include:
94
+ - os: macos-latest
95
+ target: bun-darwin-arm64
96
+ artifact: i18nagent-macos-arm64
97
+ - os: ubuntu-latest
98
+ target: bun-linux-x64
99
+ artifact: i18nagent-linux-x64
100
+ runs-on: ${{ matrix.os }}
101
+ steps:
102
+ - uses: actions/checkout@v4
103
+ - uses: oven-sh/setup-bun@v2
104
+ - run: cd cli && bun build --compile --target=${{ matrix.target }} bin/i18nagent.js --outfile ${{ matrix.artifact }}
105
+ - uses: softprops/action-gh-release@v2
106
+ with:
107
+ files: cli/${{ matrix.artifact }}
108
+ ```
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@i18n-agent/cli",
3
+ "version": "1.0.1",
4
+ "description": "Terminal client for i18n-agent translation service",
5
+ "type": "module",
6
+ "bin": {
7
+ "i18nagent": "./bin/i18nagent.js"
8
+ },
9
+ "main": "./src/index.js",
10
+ "engines": {
11
+ "node": ">=18.0.0"
12
+ },
13
+ "dependencies": {
14
+ "commander": "^12.0.0"
15
+ },
16
+ "files": [
17
+ "bin/",
18
+ "src/",
19
+ "docs/",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "keywords": [
24
+ "i18n",
25
+ "translation",
26
+ "cli",
27
+ "internationalization",
28
+ "localization"
29
+ ],
30
+ "author": "FatCouple OU",
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/i18n-agent/cli.git"
35
+ },
36
+ "homepage": "https://i18nagent.ai",
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "scripts": {
41
+ "test": "node --test tests/unit/*.test.js",
42
+ "test:integration": "node --test tests/integration/*.test.js"
43
+ }
44
+ }
@@ -0,0 +1,127 @@
1
+ export const MCP_SERVER_URL = 'https://mcp.i18nagent.ai';
2
+
3
+ const HEAVY_LOAD_PATTERNS = [
4
+ /Our system is under heavy load/i,
5
+ /system is under heavy load.*resume.*later/i,
6
+ ];
7
+
8
+ export function isHeavyLoadError(message) {
9
+ if (!message) return false;
10
+ return HEAVY_LOAD_PATTERNS.some(p => p.test(message));
11
+ }
12
+
13
+ export function buildJsonRpcRequest(toolName, args) {
14
+ return {
15
+ jsonrpc: '2.0',
16
+ id: Date.now(),
17
+ method: 'tools/call',
18
+ params: {
19
+ name: toolName,
20
+ arguments: args,
21
+ },
22
+ };
23
+ }
24
+
25
+ export function parseJsonRpcResponse(response) {
26
+ if (response.error) {
27
+ const msg = response.error.message || JSON.stringify(response.error);
28
+ throw new Error(msg);
29
+ }
30
+
31
+ const result = response.result;
32
+ if (result && result.content && result.content[0]) {
33
+ const text = result.content[0].text;
34
+ try {
35
+ return JSON.parse(text);
36
+ } catch {
37
+ return text;
38
+ }
39
+ }
40
+ return result;
41
+ }
42
+
43
+ export async function callTool(toolName, args, apiKey, opts = {}) {
44
+ const timeout = opts.timeout || 300000;
45
+ const controller = new AbortController();
46
+ const timer = setTimeout(() => controller.abort(), timeout);
47
+
48
+ try {
49
+ const response = await fetch(MCP_SERVER_URL, {
50
+ method: 'POST',
51
+ headers: {
52
+ 'Content-Type': 'application/json',
53
+ 'Authorization': `Bearer ${apiKey}`,
54
+ },
55
+ body: JSON.stringify(buildJsonRpcRequest(toolName, args)),
56
+ signal: controller.signal,
57
+ });
58
+
59
+ if (!response.ok) {
60
+ const errorText = await response.text().catch(() => 'Unknown error');
61
+ handleHttpError(response.status, errorText, toolName);
62
+ }
63
+
64
+ const data = await response.json();
65
+ return parseJsonRpcResponse(data);
66
+ } catch (error) {
67
+ if (error.name === 'AbortError') {
68
+ throw new Error(`Request timed out after ${timeout / 1000}s. For large translations, the job may still be processing. Check status with: i18nagent status <jobId>`);
69
+ }
70
+ if (isHeavyLoadError(error.message)) {
71
+ throw new Error('System under heavy load. Your job has been saved -- resume later with: i18nagent resume <jobId>');
72
+ }
73
+ throw error;
74
+ } finally {
75
+ clearTimeout(timer);
76
+ }
77
+ }
78
+
79
+ function handleHttpError(status, body, toolName) {
80
+ if (status === 401) {
81
+ throw new Error('Invalid or missing API key. Run: i18nagent login');
82
+ }
83
+ if (status === 402) {
84
+ throw new Error('Insufficient credits. Top up at: https://app.i18nagent.ai');
85
+ }
86
+ if (status === 404) {
87
+ throw new Error(`Resource not found (${toolName})`);
88
+ }
89
+ if (status === 503) {
90
+ throw new Error('Service temporarily unavailable. Try again later.');
91
+ }
92
+ throw new Error(`HTTP ${status}: ${body}`);
93
+ }
94
+
95
+ export async function uploadFile(endpoint, fields, fileField, apiKey, opts = {}) {
96
+ const timeout = opts.timeout || 60000;
97
+ const controller = new AbortController();
98
+ const timer = setTimeout(() => controller.abort(), timeout);
99
+
100
+ try {
101
+ const formData = new FormData();
102
+ for (const [key, value] of Object.entries(fields)) {
103
+ formData.append(key, value);
104
+ }
105
+ if (fileField) {
106
+ formData.append(fileField.name, new Blob([fileField.content]), fileField.fileName);
107
+ }
108
+
109
+ const response = await fetch(`${MCP_SERVER_URL}${endpoint}`, {
110
+ method: 'POST',
111
+ headers: {
112
+ 'Authorization': `Bearer ${apiKey}`,
113
+ },
114
+ body: formData,
115
+ signal: controller.signal,
116
+ });
117
+
118
+ if (!response.ok) {
119
+ const errorText = await response.text().catch(() => 'Unknown error');
120
+ handleHttpError(response.status, errorText, endpoint);
121
+ }
122
+
123
+ return await response.json();
124
+ } finally {
125
+ clearTimeout(timer);
126
+ }
127
+ }
@@ -0,0 +1,58 @@
1
+ import fs from 'fs';
2
+ import { requireApiKey } from '../config.js';
3
+ import { callTool } from '../api-client.js';
4
+ import { formatOutput, print } from '../output.js';
5
+
6
+ export async function analyzeAction(input, opts) {
7
+ if (!input) {
8
+ console.error('Error: provide text or file path to analyze');
9
+ console.error('Usage: i18nagent analyze <text-or-file> --lang <target>');
10
+ process.exit(1);
11
+ }
12
+
13
+ if (!opts.lang) {
14
+ console.error('Error: --lang is required (target language)');
15
+ process.exit(1);
16
+ }
17
+
18
+ const apiKey = requireApiKey();
19
+
20
+ let content = input;
21
+ if (fs.existsSync(input)) {
22
+ content = fs.readFileSync(input, 'utf8');
23
+ }
24
+
25
+ try {
26
+ const result = await callTool('analyze_content', {
27
+ content,
28
+ targetLanguage: opts.lang,
29
+ sourceLanguage: opts.source,
30
+ industry: opts.industry || 'general',
31
+ targetAudience: opts.audience || 'general',
32
+ }, apiKey);
33
+
34
+ print(formatOutput(result, {
35
+ json: opts.json,
36
+ template: (data) => {
37
+ const lines = ['Content Analysis\n'];
38
+ if (data.sourceLanguage) lines.push(` Source language: ${data.sourceLanguage} (${data.confidence || ''})`);
39
+ if (data.contentType) lines.push(` Content type: ${data.contentType}`);
40
+ if (data.readinessScore !== undefined) lines.push(` Readiness: ${data.readinessScore}/100`);
41
+ if (data.wordCount) lines.push(` Word count: ${data.wordCount}`);
42
+ if (data.estimatedCredits) lines.push(` Est. credits: ${data.estimatedCredits}`);
43
+ if (data.suggestions?.length) {
44
+ lines.push('\n Suggestions:');
45
+ data.suggestions.forEach(s => lines.push(` - ${s}`));
46
+ }
47
+ if (data.warnings?.length) {
48
+ lines.push('\n Warnings:');
49
+ data.warnings.forEach(w => lines.push(` ! ${w}`));
50
+ }
51
+ return lines.join('\n');
52
+ }
53
+ }));
54
+ } catch (error) {
55
+ console.error(`Error: ${error.message}`);
56
+ process.exit(1);
57
+ }
58
+ }
@@ -0,0 +1,27 @@
1
+ import { requireApiKey } from '../config.js';
2
+ import { callTool } from '../api-client.js';
3
+ import { formatOutput, print } from '../output.js';
4
+
5
+ export async function creditsAction(opts) {
6
+ const apiKey = requireApiKey();
7
+
8
+ try {
9
+ const result = await callTool('get_credits', {}, apiKey);
10
+
11
+ print(formatOutput(result, {
12
+ json: opts.json,
13
+ template: (data) => {
14
+ const credits = data.credits ?? data.remainingCredits ?? data;
15
+ if (typeof credits === 'object') {
16
+ const remaining = credits.remaining ?? credits.balance ?? 'unknown';
17
+ const wordsRemaining = credits.wordsRemaining ?? (typeof remaining === 'number' ? Math.floor(remaining / 0.01) : 'unknown');
18
+ return `Credits remaining: ${remaining}\nEstimated words: ${typeof wordsRemaining === 'number' ? wordsRemaining.toLocaleString() : wordsRemaining}\n\nTop up at: https://app.i18nagent.ai`;
19
+ }
20
+ return `Credits: ${JSON.stringify(credits)}`;
21
+ }
22
+ }));
23
+ } catch (error) {
24
+ console.error(`Error: ${error.message}`);
25
+ process.exit(1);
26
+ }
27
+ }
@@ -0,0 +1,53 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { requireApiKey } from '../config.js';
4
+ import { callTool } from '../api-client.js';
5
+ import { print, printErr } from '../output.js';
6
+
7
+ export async function downloadAction(jobId, opts) {
8
+ if (!jobId) {
9
+ console.error('Error: jobId is required');
10
+ console.error('Usage: i18nagent download <jobId>');
11
+ process.exit(1);
12
+ }
13
+
14
+ const apiKey = requireApiKey();
15
+ const outputDir = opts.output || `./i18n-translations-${jobId}`;
16
+
17
+ try {
18
+ printErr(`Downloading translations for job ${jobId}...`);
19
+
20
+ const result = await callTool('download_translations', { jobId }, apiKey, { timeout: 60000 });
21
+
22
+ fs.mkdirSync(outputDir, { recursive: true });
23
+ const filesWritten = [];
24
+
25
+ if (result.downloadUrls) {
26
+ for (const [lang, url] of Object.entries(result.downloadUrls)) {
27
+ printErr(` Downloading ${lang}...`);
28
+ const response = await fetch(url);
29
+ const content = await response.text();
30
+ const ext = result.fileName?.split('.').pop() || 'json';
31
+ const filePath = path.join(outputDir, `${lang}.${ext}`);
32
+ fs.writeFileSync(filePath, content);
33
+ filesWritten.push(filePath);
34
+ }
35
+ } else if (result.translations) {
36
+ for (const [lang, content] of Object.entries(result.translations)) {
37
+ const ext = result.fileName?.split('.').pop() || 'json';
38
+ const filePath = path.join(outputDir, `${lang}.${ext}`);
39
+ fs.writeFileSync(filePath, content);
40
+ filesWritten.push(filePath);
41
+ }
42
+ }
43
+
44
+ if (opts.json) {
45
+ print(JSON.stringify({ jobId, outputDir, filesWritten }, null, 2));
46
+ } else {
47
+ print(`Downloaded ${filesWritten.length} files to ${outputDir}:\n${filesWritten.map(f => ` ${f}`).join('\n')}`);
48
+ }
49
+ } catch (error) {
50
+ console.error(`Error: ${error.message}`);
51
+ process.exit(1);
52
+ }
53
+ }
@@ -0,0 +1,33 @@
1
+ import { requireApiKey } from '../config.js';
2
+ import { callTool } from '../api-client.js';
3
+ import { formatOutput, print } from '../output.js';
4
+
5
+ export async function languagesAction(opts) {
6
+ const apiKey = requireApiKey();
7
+
8
+ try {
9
+ const result = await callTool('list_supported_languages', {
10
+ includeQuality: opts.quality,
11
+ }, apiKey);
12
+
13
+ print(formatOutput(result, {
14
+ json: opts.json,
15
+ template: (data) => {
16
+ const languages = data.languages || data;
17
+ if (!Array.isArray(languages)) {
18
+ return JSON.stringify(data, null, 2);
19
+ }
20
+ const lines = ['Supported Languages:', ''];
21
+ for (const lang of languages) {
22
+ const quality = lang.quality ? ` (${lang.quality})` : '';
23
+ lines.push(` ${(lang.code || '').padEnd(12)} ${lang.name || ''}${quality}`);
24
+ }
25
+ lines.push('', `Total: ${languages.length} languages`);
26
+ return lines.join('\n');
27
+ }
28
+ }));
29
+ } catch (error) {
30
+ console.error(`Error: ${error.message}`);
31
+ process.exit(1);
32
+ }
33
+ }
@@ -0,0 +1,26 @@
1
+ import { getConfigPath, saveConfig, getApiKey } from '../config.js';
2
+ import { promptApiKey } from '../prompts.js';
3
+ import { print } from '../output.js';
4
+
5
+ export async function loginAction(opts) {
6
+ const configPath = getConfigPath();
7
+
8
+ const existingKey = getApiKey(configPath);
9
+ if (existingKey && !opts.force) {
10
+ print('Already logged in. Use --force to overwrite.');
11
+ return;
12
+ }
13
+
14
+ let apiKey = opts.key;
15
+ if (!apiKey) {
16
+ apiKey = await promptApiKey();
17
+ }
18
+
19
+ if (!apiKey) {
20
+ print('No API key provided. Aborting.');
21
+ return;
22
+ }
23
+
24
+ saveConfig(configPath, { apiKey });
25
+ print(`API key saved to ${configPath}`);
26
+ }
@@ -0,0 +1,19 @@
1
+ import { getConfigPath, loadConfig } from '../config.js';
2
+ import { print } from '../output.js';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+
6
+ export async function logoutAction() {
7
+ const configPath = getConfigPath();
8
+ const config = loadConfig(configPath);
9
+
10
+ if (!config.apiKey) {
11
+ print('Not logged in.');
12
+ return;
13
+ }
14
+
15
+ delete config.apiKey;
16
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
17
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
18
+ print('API key removed. Logged out.');
19
+ }
@@ -0,0 +1,30 @@
1
+ import { requireApiKey } from '../config.js';
2
+ import { callTool } from '../api-client.js';
3
+ import { formatOutput, print } from '../output.js';
4
+
5
+ export async function resumeAction(jobId, opts) {
6
+ if (!jobId) {
7
+ console.error('Error: jobId is required');
8
+ console.error('Usage: i18nagent resume <jobId>');
9
+ process.exit(1);
10
+ }
11
+
12
+ const apiKey = requireApiKey();
13
+
14
+ try {
15
+ const result = await callTool('resume_translation', { jobId }, apiKey, { timeout: 30000 });
16
+
17
+ print(formatOutput(result, {
18
+ json: opts.json,
19
+ template: (data) => {
20
+ if (data.status === 'processing' || data.status === 'resumed') {
21
+ return `Job ${jobId} resumed. Check progress with: i18nagent status ${jobId}`;
22
+ }
23
+ return `Resume result: ${JSON.stringify(data, null, 2)}`;
24
+ }
25
+ }));
26
+ } catch (error) {
27
+ console.error(`Error: ${error.message}`);
28
+ process.exit(1);
29
+ }
30
+ }