@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 +21 -0
- package/README.md +107 -0
- package/bin/i18nagent.js +6 -0
- package/docs/BINARY_DISTRIBUTION.md +108 -0
- package/package.json +44 -0
- package/src/api-client.js +127 -0
- package/src/commands/analyze.js +58 -0
- package/src/commands/credits.js +27 -0
- package/src/commands/download.js +53 -0
- package/src/commands/languages.js +33 -0
- package/src/commands/login.js +26 -0
- package/src/commands/logout.js +19 -0
- package/src/commands/resume.js +30 -0
- package/src/commands/status.js +44 -0
- package/src/commands/translate.js +301 -0
- package/src/commands/tui.js +91 -0
- package/src/commands/upload.js +115 -0
- package/src/config.js +45 -0
- package/src/index.js +156 -0
- package/src/namespace-detector.js +362 -0
- package/src/output.js +39 -0
- package/src/prompts.js +65 -0
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
|
package/bin/i18nagent.js
ADDED
|
@@ -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
|
+
}
|