@lazyingart/agintiflow 0.20.8 → 0.20.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/README.md CHANGED
@@ -69,10 +69,15 @@ Start an interactive Codex-style CLI chat from any project folder:
69
69
  aginti
70
70
  # or explicitly:
71
71
  aginti chat
72
+ # choose a UI language, or omit it to follow your system locale:
73
+ aginti --language ja
74
+ aginti --language zh-Hans
72
75
  ```
73
76
 
74
77
  Inside chat, type normal requests such as `write a small Python CLI app with tests`. The default is Docker workspace mode with approved package installs, so coding, plotting, and LaTeX tasks can set up project-local tools without touching the host. Use `/help` for commands, `/login` or `/auth` to paste a provider key, `/instructions` to inspect `AGINTI.md`, `/latex on` for PDF work, `/docker off` only when you intentionally want host mode, `/sessions` to list project runs, and `/resume latest` or `/resume <session-id>` to continue work. Type `/` then Tab for command completion. `Ctrl+J` inserts a new line in the colored input panel, Enter sends, arrow keys move through wrapped multiline input, and `Ctrl+A`/`Ctrl+E` jump to the current line start/end. During an active run, Enter sends the draft as an ASAP pipe message (`→`) and Tab queues it for after the run (`↳`); ASAP messages are consumed before after-finish queued prompts, Alt+Up edits the last piped message, and Shift+Left edits the last after-finish queued message. Idle Esc is ignored so it does not disturb the input panel; during a run, Esc waits when `→` messages are pending and otherwise stops the run cleanly. Ctrl+C always stops and prints the resume command. The input panel always shows the current `cwd` footer and a single live status row, so long goals and tool updates are compacted instead of flooding the transcript. Assistant responses start on a fresh line after the `aginti>` header with a colored response gutter and render common Markdown, including headings, inline code, bold text, lists, quotes, code fences, tables, and red/green patch diff lines. Resuming a session prints the full saved chat history with wrapped messages before the prompt.
75
78
 
79
+ CLI and app language can follow the system locale or be set with `--language`, `--lang`, `-L`, or the interactive `/language` command. Supported codes are `en`, `ja`, `zh-Hans`, `zh-Hant`, `ko`, `fr`, `es`, `ar`, `vi`, `de`, and `ru`; longer names plus old `jp` and `cn-*` aliases still work. See [docs/cli-i18n.md](docs/cli-i18n.md).
80
+
76
81
  `aginti init` creates `AGINTI.md` at the project root. This is the editable project-instruction file for both CLI and web runs, similar to `AGENTS.md` or project memory in other agents. Keep durable preferences, commands, and constraints there, but never secrets. You can edit it manually or ask in chat, for example: `update AGINTI.md to remember that this project uses pytest and npm run check`.
77
82
 
78
83
  For code edits, AgInTiFlow routes patch/refactor/database-style tasks to DeepSeek v4 pro by default and exposes `apply_patch` as a deterministic workspace tool. It supports exact replacements, Codex-style patch envelopes, and unified diffs, with preflight checks, path guardrails, hashes, and compact per-file diffs. See [docs/patch-tools.md](docs/patch-tools.md).
@@ -0,0 +1,84 @@
1
+ # CLI And App Language
2
+
3
+ AgInTiFlow supports a shared language preference for the interactive CLI, one-shot CLI runs, web defaults, and model prompt context.
4
+
5
+ ## Supported Canonical Languages
6
+
7
+ | Canonical | Language | Common aliases |
8
+ | --- | --- |
9
+ | `en` | English | `english` |
10
+ | `ja` | Japanese | `jp`, `japanese` |
11
+ | `zh-Hans` | Simplified Chinese | `zh-s`, `zh s`, `cn`, `cn-s`, `cn s`, `zh-cn`, `simplified` |
12
+ | `zh-Hant` | Traditional Chinese | `zh-t`, `zh t`, `cn-t`, `cn t`, `zh-tw`, `zh-hk`, `traditional` |
13
+ | `ko` | Korean | `korean` |
14
+ | `fr` | French | `french` |
15
+ | `es` | Spanish | `spanish` |
16
+ | `ar` | Arabic | `arabic` |
17
+ | `vi` | Vietnamese | `vietnamese` |
18
+ | `de` | German | `deutsch`, `german` |
19
+ | `ru` | Russian | `russian` |
20
+
21
+ ## Defaults
22
+
23
+ If no language is passed, AgInTiFlow follows the system language from:
24
+
25
+ ```text
26
+ AGINTI_LANGUAGE -> LANGUAGE -> LC_ALL -> LC_MESSAGES -> LANG
27
+ ```
28
+
29
+ If none of those map to a supported locale, English is used.
30
+
31
+ ## CLI Usage
32
+
33
+ Start the interactive CLI in a specific language:
34
+
35
+ ```bash
36
+ aginti --language ja
37
+ aginti --language zh-Hans
38
+ aginti --language zh-Hant
39
+ aginti --language ko
40
+ aginti --language fr
41
+ aginti --language es
42
+ aginti --language ar
43
+ aginti --language vi
44
+ aginti --language de
45
+ aginti --language ru
46
+ ```
47
+
48
+ Inside interactive chat:
49
+
50
+ ```text
51
+ /language
52
+ /language auto
53
+ /language en
54
+ /language ja
55
+ /language zh-Hans
56
+ /language zh-Hant
57
+ /language ko
58
+ ```
59
+
60
+ `/lang` is an alias for `/language`.
61
+
62
+ One-shot tasks also accept the same option:
63
+
64
+ ```bash
65
+ aginti --language zh-Hans "list files and summarize this project"
66
+ ```
67
+
68
+ The selected language localizes the launch banner subtitle, prompt hint, help text, status labels, and language status. It also adds language guidance to the model context so the assistant usually replies in the selected UI language unless the user asks for another language.
69
+
70
+ ## Web App
71
+
72
+ The web app already has an 11-language dropdown. Launching web with a language seeds the project-local default:
73
+
74
+ ```bash
75
+ aginti web --language de --port 3210
76
+ ```
77
+
78
+ The web preference is stored in the project-local `.sessions/web-state.sqlite` database, so the CLI and web app can share sessions while each frontend keeps its own UI controls.
79
+
80
+ ## Notes
81
+
82
+ - `zh-Hans` and `zh-Hant` are the canonical Chinese values. `zh-s`, `zh-t`, `cn-s`, `cn-t`, `cn s`, and `cn t` remain compatibility aliases.
83
+ - API keys and `.aginti/.env` values are never translated, printed, or exposed through the language system.
84
+ - Model output language is guidance, not a hard safety filter. If the user asks in a different language or explicitly requests another output language, the model should follow that request.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.8",
3
+ "version": "0.20.9",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a web-first coding agent and CLI with DeepSeek routing, sandboxed tools, model providers, canvas artifacts, and optional wrappers.",
6
6
  "license": "Apache-2.0",
@@ -30,6 +30,7 @@ function runCli(args, inputText) {
30
30
  env: {
31
31
  ...process.env,
32
32
  AGINTIFLOW_RUNTIME_DIR: "",
33
+ AGINTI_LANGUAGE: "en",
33
34
  },
34
35
  });
35
36
 
@@ -153,6 +154,15 @@ try {
153
154
  ) {
154
155
  throw new Error("terminal prompt layout did not render visual-only input padding safely");
155
156
  }
157
+ const zhPromptLayout = buildPromptLayout("", 0, 90, 24, { language: "zh-Hans", commandCwd: "/tmp/aginti-project" });
158
+ const zhPromptText = zhPromptLayout.renderedRows.map((line) => line.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "")).join("\n");
159
+ if (!zhPromptText.includes("输入任务")) {
160
+ throw new Error("terminal prompt layout did not localize the empty input hint");
161
+ }
162
+ const jaLaunchHeader = buildLaunchHeaderLines({ width: 120, packageVersion: "0.0.0", animated: false, language: "ja" }).join("\n");
163
+ if (!jaLaunchHeader.includes("Web ファースト")) {
164
+ throw new Error("launch header did not localize by language option");
165
+ }
156
166
  const hugePromptLayout = buildPromptLayout(Array.from({ length: 30 }, (_unused, index) => `line ${index + 1}`).join("\n"), 120, 80, 20);
157
167
  if (hugePromptLayout.renderedRows.length > 12 || !hugePromptLayout.renderedRows.some((line) => line.includes("earlier input row"))) {
158
168
  throw new Error("terminal prompt layout did not bound redraw size for large prompts");
@@ -220,6 +230,10 @@ try {
220
230
  if (!helpResult.stdout.includes("/auxiliary") || helpResult.stdout.includes(misspelledAuxiliary)) {
221
231
  throw new Error("interactive help did not expose only the correctly spelled /auxiliary command");
222
232
  }
233
+ const zhHelpResult = await runCli(["chat", "--language", "zh-Hans"], "/help\n/exit\n");
234
+ if (!zhHelpResult.stdout.includes("命令:") || !zhHelpResult.stdout.includes("输入普通任务")) {
235
+ throw new Error("interactive --language zh-Hans did not localize CLI help");
236
+ }
223
237
  const skillsResult = await runChat("/skills website\n/exit\n");
224
238
  if (!skillsResult.stdout.includes("website-app") || !skillsResult.stdout.includes("Website And App Builder")) {
225
239
  throw new Error("interactive /skills did not show matching built-in skills");
@@ -283,6 +297,7 @@ try {
283
297
  "large-launch-header",
284
298
  "prompt-layout",
285
299
  "prompt-redraw-fast-path",
300
+ "cli-i18n",
286
301
  "user-prompt-label",
287
302
  "escape-policy",
288
303
  "live-input-status-layout",
@@ -25,6 +25,7 @@ import { readProjectInstructions } from "./project.js";
25
25
  import { formatSkillsForPrompt, selectSkillsForGoal } from "./skill-library.js";
26
26
  import { hostShellOption, platformInfo, platformLabel } from "./platform.js";
27
27
  import { captureTmuxPane, listTmuxSessions, sendTmuxKeys, startTmuxSession } from "./tmux-tools.js";
28
+ import { languageInstruction } from "./i18n.js";
28
29
 
29
30
  const exec = promisify(execCallback);
30
31
  const BROWSER_TOOLS = new Set(["open_url", "open_workspace_file", "preview_workspace", "click", "type", "scroll", "press", "back"]);
@@ -300,6 +301,7 @@ async function createInitialState(config, sessionId) {
300
301
  "Prefer short, deliberate actions over guessing.",
301
302
  "Never navigate outside the allowed domains when an allowlist exists.",
302
303
  "Avoid destructive actions, purchases, account changes, and sensitive workflows.",
304
+ languageInstruction(config.language || "en"),
303
305
  projectInstructionContext,
304
306
  "Treat AGINTI.md as durable project memory and operating instructions for this project. The user can edit it manually or ask you in chat to update it; use workspace file tools for that and never store secrets there.",
305
307
  config.allowShellTool
@@ -351,6 +353,7 @@ async function createInitialState(config, sessionId) {
351
353
  role: "user",
352
354
  content: [
353
355
  `Goal: ${config.goal}`,
356
+ languageInstruction(config.language || "en"),
354
357
  config.startUrl ? `Suggested start URL: ${config.startUrl}` : "",
355
358
  config.allowedDomains.length > 0 ? `Allowed domains: ${config.allowedDomains.join(", ")}` : "",
356
359
  config.allowShellTool
package/src/cli.js CHANGED
@@ -25,6 +25,7 @@ import { listTaskProfiles } from "./task-profiles.js";
25
25
  import { recommendedMaxStepsForTask } from "./engineering-guidance.js";
26
26
  import { normalizeAuthProvider, promptHidden, runAuthWizard, shouldPromptForDeepSeek } from "./auth-onboarding.js";
27
27
  import { listSkills, selectSkillsForGoal } from "./skill-library.js";
28
+ import { languageLabel, resolveLanguage } from "./i18n.js";
28
29
  import fs from "node:fs/promises";
29
30
  import path from "node:path";
30
31
  import { fileURLToPath } from "node:url";
@@ -87,6 +88,7 @@ export function parseArgs(argv) {
87
88
  listSkills: false,
88
89
  latex: false,
89
90
  image: false,
91
+ language: "",
90
92
  };
91
93
 
92
94
  const parts = [];
@@ -110,6 +112,18 @@ export function parseArgs(argv) {
110
112
  i += 1;
111
113
  continue;
112
114
  }
115
+ if (arg === "--language" || arg === "--lang" || arg === "-L") {
116
+ const first = readOption(argv, i);
117
+ const second = argv[i + 2] && !String(argv[i + 2]).startsWith("--") ? argv[i + 2] : "";
118
+ if (["cn", "zh"].includes(String(first || "").toLowerCase()) && ["s", "t"].includes(String(second || "").toLowerCase())) {
119
+ result.language = `${first}-${second}`;
120
+ i += 2;
121
+ } else {
122
+ result.language = first;
123
+ i += 1;
124
+ }
125
+ continue;
126
+ }
113
127
  if (arg === "--start-url") {
114
128
  result.startUrl = readOption(argv, i);
115
129
  i += 1;
@@ -345,8 +359,9 @@ export function parseArgs(argv) {
345
359
 
346
360
  function printUsage() {
347
361
  console.log(
348
- 'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti models OR aginti skills [query] OR aginti auth [deepseek|openai|qwen|venice|grsai] OR aginti resume [latest|<session-id>] ["prompt"] OR aginti queue <session-id> "message" OR aginti [--image] [--latex] [--routing smart|fast|complex|manual] [--provider deepseek|openai|qwen|venice|mock] [--model MODEL] [--route-model MODEL] [--main-model MODEL] [--spare-model MODEL --spare-reasoning medium] [--aux-provider grsai|venice --aux-model MODEL] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-file-tools|--no-file-tools] [--web-search|--no-web-search] [--parallel-scouts|--no-parallel-scouts --scout-count 1..10] [--allow-auxiliary-tools|--no-auxiliary-tools] [--allow-wrappers --wrapper codex --wrapper-model gpt-5.5] [--list-models|--list-routes] "your task"'
362
+ 'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti models OR aginti skills [query] OR aginti auth [deepseek|openai|qwen|venice|grsai] OR aginti resume [latest|<session-id>] ["prompt"] OR aginti queue <session-id> "message" OR aginti [--language en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru] [--image] [--latex] [--routing smart|fast|complex|manual] [--provider deepseek|openai|qwen|venice|mock] [--model MODEL] [--route-model MODEL] [--main-model MODEL] [--spare-model MODEL --spare-reasoning medium] [--aux-provider grsai|venice --aux-model MODEL] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-file-tools|--no-file-tools] [--web-search|--no-web-search] [--parallel-scouts|--no-parallel-scouts --scout-count 1..10] [--allow-auxiliary-tools|--no-auxiliary-tools] [--allow-wrappers --wrapper codex --wrapper-model gpt-5.5] [--list-models|--list-routes] "your task"'
349
363
  );
364
+ console.log(`Languages: ${["en", "ja", "zh-Hans", "zh-Hant", "ko", "fr", "es", "ar", "vi", "de", "ru"].map((code) => `${code}=${languageLabel(code)}`).join(", ")}`);
350
365
  }
351
366
 
352
367
  function providerLabel(provider) {
@@ -361,6 +376,7 @@ function providerLabel(provider) {
361
376
  function agentDefaults(args) {
362
377
  const defaults = {
363
378
  ...args,
379
+ language: resolveLanguage(args.language || process.env.AGINTI_LANGUAGE || ""),
364
380
  allowShellTool: args.allowShellTool ?? true,
365
381
  allowFileTools: args.allowFileTools ?? true,
366
382
  allowAuxiliaryTools: args.allowAuxiliaryTools ?? true,
@@ -728,6 +744,7 @@ export async function main(argv = process.argv.slice(2)) {
728
744
  if (args.web) {
729
745
  if (args.port) process.env.PORT = String(args.port);
730
746
  if (args.host) process.env.HOST = String(args.host);
747
+ if (args.language) process.env.AGINTI_LANGUAGE = resolveLanguage(args.language);
731
748
  process.env.AGINTIFLOW_PACKAGE_DIR = packageDir;
732
749
  await import("../web.js");
733
750
  return;
package/src/config.js CHANGED
@@ -6,6 +6,7 @@ import { normalizeWrapperName } from "./tool-wrappers.js";
6
6
  import { loadProjectEnv, resolveProjectRoot } from "./project.js";
7
7
  import { normalizeTaskProfile } from "./task-profiles.js";
8
8
  import { recommendedMaxStepsForTask } from "./engineering-guidance.js";
9
+ import { resolveLanguage } from "./i18n.js";
9
10
 
10
11
  function parseBoolean(value, fallback) {
11
12
  if (value === undefined) return fallback;
@@ -47,6 +48,7 @@ export function resolveRuntimeConfig(args, overrides = {}) {
47
48
  : "deepseek");
48
49
  const routingMode = normalizeRoutingMode(overrides.routingMode || args.routingMode || process.env.AGENT_ROUTING_MODE || "smart");
49
50
  const taskProfile = normalizeTaskProfile(overrides.taskProfile || args.taskProfile || process.env.AGINTI_TASK_PROFILE || "auto");
51
+ const language = resolveLanguage(overrides.language || args.language || process.env.AGINTI_LANGUAGE || "");
50
52
  const route = selectModelRoute({
51
53
  routingMode,
52
54
  provider: requestedProvider,
@@ -94,6 +96,7 @@ export function resolveRuntimeConfig(args, overrides = {}) {
94
96
  sessionId: overrides.sessionId || args.sessionId || process.env.SESSION_ID || `web-agent-${crypto.randomUUID()}`,
95
97
  routingMode,
96
98
  taskProfile,
99
+ language,
97
100
  routeReason: route.reason,
98
101
  routeComplexityScore: route.complexityScore,
99
102
  modelRoles,
package/src/i18n.js ADDED
@@ -0,0 +1,435 @@
1
+ export const SUPPORTED_LANGUAGES = [
2
+ "en",
3
+ "ja",
4
+ "zh-Hans",
5
+ "zh-Hant",
6
+ "ko",
7
+ "fr",
8
+ "es",
9
+ "ar",
10
+ "vi",
11
+ "de",
12
+ "ru",
13
+ ];
14
+
15
+ export const LANGUAGE_LABELS = {
16
+ en: "English",
17
+ ja: "日本語",
18
+ "zh-Hans": "简体中文",
19
+ "zh-Hant": "繁體中文",
20
+ ko: "한국어",
21
+ fr: "Français",
22
+ es: "Español",
23
+ ar: "العربية",
24
+ vi: "Tiếng Việt",
25
+ de: "Deutsch",
26
+ ru: "Русский",
27
+ };
28
+
29
+ const LANGUAGE_ALIASES = new Map(
30
+ Object.entries({
31
+ "": "",
32
+ auto: "",
33
+ system: "",
34
+ default: "",
35
+ en: "en",
36
+ eng: "en",
37
+ english: "en",
38
+ ja: "ja",
39
+ jp: "ja",
40
+ jpn: "ja",
41
+ japanese: "ja",
42
+ "日本語": "ja",
43
+ cn: "zh-Hans",
44
+ zh: "zh-Hans",
45
+ "cn-s": "zh-Hans",
46
+ "cn_s": "zh-Hans",
47
+ cns: "zh-Hans",
48
+ "zh-s": "zh-Hans",
49
+ "zh_s": "zh-Hans",
50
+ zhs: "zh-Hans",
51
+ simplified: "zh-Hans",
52
+ simplifiedchinese: "zh-Hans",
53
+ "zh-cn": "zh-Hans",
54
+ "zh_cn": "zh-Hans",
55
+ "zh-hans": "zh-Hans",
56
+ "zh_hans": "zh-Hans",
57
+ zhhans: "zh-Hans",
58
+ "zh-sg": "zh-Hans",
59
+ "zh_sg": "zh-Hans",
60
+ "简体": "zh-Hans",
61
+ "简体中文": "zh-Hans",
62
+ "cn-t": "zh-Hant",
63
+ "cn_t": "zh-Hant",
64
+ cnt: "zh-Hant",
65
+ "zh-t": "zh-Hant",
66
+ "zh_t": "zh-Hant",
67
+ zht: "zh-Hant",
68
+ traditional: "zh-Hant",
69
+ traditionalchinese: "zh-Hant",
70
+ "zh-tw": "zh-Hant",
71
+ "zh_tw": "zh-Hant",
72
+ "zh-hk": "zh-Hant",
73
+ "zh_hk": "zh-Hant",
74
+ "zh-mo": "zh-Hant",
75
+ "zh_mo": "zh-Hant",
76
+ "zh-hant": "zh-Hant",
77
+ "zh_hant": "zh-Hant",
78
+ zhhant: "zh-Hant",
79
+ "繁體": "zh-Hant",
80
+ "繁體中文": "zh-Hant",
81
+ ko: "ko",
82
+ kr: "ko",
83
+ kor: "ko",
84
+ korean: "ko",
85
+ "한국어": "ko",
86
+ fr: "fr",
87
+ fra: "fr",
88
+ french: "fr",
89
+ français: "fr",
90
+ es: "es",
91
+ spa: "es",
92
+ spanish: "es",
93
+ español: "es",
94
+ ar: "ar",
95
+ ara: "ar",
96
+ arabic: "ar",
97
+ "العربية": "ar",
98
+ vi: "vi",
99
+ vie: "vi",
100
+ vietnamese: "vi",
101
+ "tiếngviệt": "vi",
102
+ de: "de",
103
+ deu: "de",
104
+ ger: "de",
105
+ german: "de",
106
+ deutsch: "de",
107
+ ru: "ru",
108
+ rus: "ru",
109
+ russian: "ru",
110
+ русский: "ru",
111
+ })
112
+ );
113
+
114
+ const TRANSLATIONS = {
115
+ en: {
116
+ launchSubtitle: "web-first agent workspace",
117
+ launchTagline: "browser + shell + files + docker + web search + scouts",
118
+ interactiveIntro: "Interactive agent chat. Type /help for commands, /exit to quit.",
119
+ promptEmpty: "type a request, /help, Enter to send, Ctrl+J for newline",
120
+ helpTitle: "Commands:",
121
+ helpHelp: "Show this help.",
122
+ helpStatus: "Show active route, workspace, sandbox, and session.",
123
+ helpLogin: "Pick, paste, and save project-local API keys.",
124
+ helpInstructions: "Show AGINTI.md project instructions status.",
125
+ helpModels: "Show route/main/spare/wrapper/auxiliary model roles.",
126
+ helpVenice: "Pick Venice route/main models, or restore DeepSeek defaults.",
127
+ helpRoute: "Open route selector, or set routing/fast route model.",
128
+ helpModel: "Open main-model selector, or set the active/main model.",
129
+ helpSpare: "Open spare selector, or set e.g. /spare openai/gpt-5.4 medium.",
130
+ helpWrapper: "Configure optional external wrapper.",
131
+ helpAuxiliary: "Manage optional auxiliary skills, including image generation.",
132
+ helpNew: "Start a fresh session on the next message.",
133
+ helpResume: "Continue a saved session.",
134
+ helpSessions: "List recent sessions in this project.",
135
+ helpSkills: "List Markdown skills selected for a topic.",
136
+ helpProfile: "Set task profile, e.g. code, website, latex, maintenance.",
137
+ helpWebSearch: "Enable or disable the web_search tool.",
138
+ helpScouts: "Enable parallel DeepSeek scouts and set scout count.",
139
+ helpRouting: "Set routing: smart, fast, complex, manual.",
140
+ helpProvider: "Open provider selector, or set deepseek/openai/qwen/venice/mock.",
141
+ helpDockerOn: "Use docker-workspace with approved package installs.",
142
+ helpDockerOff: "Use host shell policy.",
143
+ helpLatex: "Use the LaTeX/PDF profile in Docker with a larger step budget.",
144
+ helpInstalls: "Set package install policy.",
145
+ helpCwd: "Change command workspace.",
146
+ helpLanguage: "Set CLI language, or use system locale with /language auto.",
147
+ helpExit: "Quit.",
148
+ helpNormalRequest: "Type a normal request to run the agent. Example: write a Python CLI app with tests",
149
+ helpAutocomplete: "Type / then Tab to autocomplete commands.",
150
+ helpQueue: "While a run is active, Enter pipes a message into the current run (→), Tab queues it after finish (↳).",
151
+ helpEditQueue: "Alt+Up edits the last piped message; Shift+Left edits the last queued message.",
152
+ helpEsc: "Esc is ignored while idle. During a run, Esc waits for pending → pipe messages or stops if none; Ctrl+C always stops.",
153
+ languageSet: "language={language} ({label})",
154
+ languageUsage: "Usage: /language auto|en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru",
155
+ project: "Project",
156
+ statusNew: "new",
157
+ statusIdle: "idle",
158
+ systemLanguage: "system",
159
+ },
160
+ ja: {
161
+ launchSubtitle: "Web ファーストのエージェント作業環境",
162
+ launchTagline: "ブラウザ + シェル + ファイル + Docker + Web 検索 + スカウト",
163
+ interactiveIntro: "対話型エージェントチャットです。/help でコマンド、/exit で終了します。",
164
+ promptEmpty: "依頼を入力。/help、Enter で送信、Ctrl+J で改行",
165
+ helpTitle: "コマンド:",
166
+ helpHelp: "このヘルプを表示します。",
167
+ helpStatus: "現在のルート、作業場所、サンドボックス、セッションを表示します。",
168
+ helpLogin: "プロジェクトローカルの API キーを選択・貼り付け・保存します。",
169
+ helpInstructions: "AGINTI.md のプロジェクト指示状態を表示します。",
170
+ helpModels: "route/main/spare/wrapper/auxiliary のモデル役割を表示します。",
171
+ helpVenice: "Venice の route/main モデルを選択、または DeepSeek 既定値に戻します。",
172
+ helpRoute: "ルート選択を開くか、ルーティング/高速ルートモデルを設定します。",
173
+ helpModel: "メインモデル選択を開くか、active/main モデルを設定します。",
174
+ helpSpare: "スペア選択を開くか、例: /spare openai/gpt-5.4 medium を設定します。",
175
+ helpWrapper: "任意の外部ラッパーを設定します。",
176
+ helpAuxiliary: "画像生成を含む補助スキルを管理します。",
177
+ helpNew: "次のメッセージで新しいセッションを開始します。",
178
+ helpResume: "保存済みセッションを続行します。",
179
+ helpSessions: "このプロジェクトの最近のセッションを一覧します。",
180
+ helpSkills: "トピックに合う Markdown スキルを一覧します。",
181
+ helpProfile: "code、website、latex、maintenance などのタスクプロファイルを設定します。",
182
+ helpWebSearch: "web_search ツールを有効/無効にします。",
183
+ helpScouts: "並列 DeepSeek スカウトを有効化し数を設定します。",
184
+ helpRouting: "routing を smart、fast、complex、manual に設定します。",
185
+ helpProvider: "プロバイダ選択を開くか deepseek/openai/qwen/venice/mock を設定します。",
186
+ helpDockerOn: "承認済みパッケージインストール付き docker-workspace を使います。",
187
+ helpDockerOff: "ホストシェルポリシーを使います。",
188
+ helpLatex: "Docker で LaTeX/PDF プロファイルと大きめのステップ数を使います。",
189
+ helpInstalls: "パッケージインストールポリシーを設定します。",
190
+ helpCwd: "コマンド作業場所を変更します。",
191
+ helpLanguage: "CLI 言語を設定、または /language auto でシステムロケールを使います。",
192
+ helpExit: "終了します。",
193
+ helpNormalRequest: "通常の依頼を入力するとエージェントが実行します。例: write a Python CLI app with tests",
194
+ helpAutocomplete: "/ を入力して Tab でコマンド補完します。",
195
+ helpQueue: "実行中は Enter で現在の実行へパイプ (→)、Tab で完了後キュー (↳)。",
196
+ helpEditQueue: "Alt+Up で最後のパイプ、Shift+Left で最後のキューを編集します。",
197
+ helpEsc: "アイドル中の Esc は無視。実行中は保留中の → を待つか停止します。Ctrl+C は常に停止します。",
198
+ languageSet: "language={language} ({label})",
199
+ languageUsage: "使用法: /language auto|en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru",
200
+ project: "プロジェクト",
201
+ statusNew: "new",
202
+ statusIdle: "idle",
203
+ systemLanguage: "システム",
204
+ },
205
+ "zh-Hans": {
206
+ launchSubtitle: "网页优先的智能体工作区",
207
+ launchTagline: "浏览器 + Shell + 文件 + Docker + 网页搜索 + Scouts",
208
+ interactiveIntro: "交互式智能体聊天。输入 /help 查看命令,/exit 退出。",
209
+ promptEmpty: "输入任务,/help 查看帮助,Enter 发送,Ctrl+J 换行",
210
+ helpTitle: "命令:",
211
+ helpHelp: "显示帮助。",
212
+ helpStatus: "显示当前路由、工作区、沙盒和会话。",
213
+ helpLogin: "选择、粘贴并保存项目本地 API Key。",
214
+ helpInstructions: "显示 AGINTI.md 项目指令状态。",
215
+ helpModels: "显示 route/main/spare/wrapper/auxiliary 模型角色。",
216
+ helpVenice: "选择 Venice route/main 模型,或恢复 DeepSeek 默认值。",
217
+ helpRoute: "打开路由选择器,或设置 routing/快速路由模型。",
218
+ helpModel: "打开主模型选择器,或设置 active/main 模型。",
219
+ helpSpare: "打开备用模型选择器,或设置例如 /spare openai/gpt-5.4 medium。",
220
+ helpWrapper: "配置可选外部 wrapper。",
221
+ helpAuxiliary: "管理可选辅助技能,包括图像生成。",
222
+ helpNew: "下一条消息开始新会话。",
223
+ helpResume: "继续已保存会话。",
224
+ helpSessions: "列出本项目最近会话。",
225
+ helpSkills: "列出为主题选择的 Markdown 技能。",
226
+ helpProfile: "设置任务 profile,例如 code、website、latex、maintenance。",
227
+ helpWebSearch: "启用或禁用 web_search 工具。",
228
+ helpScouts: "启用并设置并行 DeepSeek scouts 数量。",
229
+ helpRouting: "设置 routing: smart、fast、complex、manual。",
230
+ helpProvider: "打开 provider 选择器,或设置 deepseek/openai/qwen/venice/mock。",
231
+ helpDockerOn: "使用允许安装包的 docker-workspace。",
232
+ helpDockerOff: "使用主机 shell 策略。",
233
+ helpLatex: "在 Docker 中使用 LaTeX/PDF profile 和更大步骤数。",
234
+ helpInstalls: "设置包安装策略。",
235
+ helpCwd: "修改命令工作区。",
236
+ helpLanguage: "设置 CLI 语言,或用 /language auto 跟随系统语言。",
237
+ helpExit: "退出。",
238
+ helpNormalRequest: "输入普通任务即可运行智能体。例如: write a Python CLI app with tests",
239
+ helpAutocomplete: "输入 / 后按 Tab 自动补全命令。",
240
+ helpQueue: "运行中 Enter 会把消息立即传入当前任务 (→),Tab 会排到结束后执行 (↳)。",
241
+ helpEditQueue: "Alt+Up 编辑上一条立即消息;Shift+Left 编辑上一条排队消息。",
242
+ helpEsc: "空闲时 Esc 不操作。运行中 Esc 等待待处理 → 消息或停止;Ctrl+C 始终停止。",
243
+ languageSet: "language={language} ({label})",
244
+ languageUsage: "用法: /language auto|en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru",
245
+ project: "项目",
246
+ statusNew: "new",
247
+ statusIdle: "idle",
248
+ systemLanguage: "系统",
249
+ },
250
+ "zh-Hant": {
251
+ launchSubtitle: "網頁優先的智能體工作區",
252
+ launchTagline: "瀏覽器 + Shell + 檔案 + Docker + 網頁搜尋 + Scouts",
253
+ interactiveIntro: "互動式智能體聊天。輸入 /help 查看命令,/exit 離開。",
254
+ promptEmpty: "輸入任務,/help 查看說明,Enter 送出,Ctrl+J 換行",
255
+ helpTitle: "命令:",
256
+ helpHelp: "顯示說明。",
257
+ helpStatus: "顯示目前路由、工作區、沙盒與會話。",
258
+ helpLogin: "選擇、貼上並儲存專案本地 API Key。",
259
+ helpInstructions: "顯示 AGINTI.md 專案指令狀態。",
260
+ helpModels: "顯示 route/main/spare/wrapper/auxiliary 模型角色。",
261
+ helpVenice: "選擇 Venice route/main 模型,或恢復 DeepSeek 預設值。",
262
+ helpRoute: "開啟路由選擇器,或設定 routing/快速路由模型。",
263
+ helpModel: "開啟主模型選擇器,或設定 active/main 模型。",
264
+ helpSpare: "開啟備用模型選擇器,或設定例如 /spare openai/gpt-5.4 medium。",
265
+ helpWrapper: "設定可選外部 wrapper。",
266
+ helpAuxiliary: "管理可選輔助技能,包括圖像生成。",
267
+ helpNew: "下一則訊息開始新會話。",
268
+ helpResume: "繼續已儲存會話。",
269
+ helpSessions: "列出本專案最近會話。",
270
+ helpSkills: "列出為主題選擇的 Markdown 技能。",
271
+ helpProfile: "設定任務 profile,例如 code、website、latex、maintenance。",
272
+ helpWebSearch: "啟用或停用 web_search 工具。",
273
+ helpScouts: "啟用並設定並行 DeepSeek scouts 數量。",
274
+ helpRouting: "設定 routing: smart、fast、complex、manual。",
275
+ helpProvider: "開啟 provider 選擇器,或設定 deepseek/openai/qwen/venice/mock。",
276
+ helpDockerOn: "使用允許安裝套件的 docker-workspace。",
277
+ helpDockerOff: "使用主機 shell 策略。",
278
+ helpLatex: "在 Docker 中使用 LaTeX/PDF profile 和較大步數。",
279
+ helpInstalls: "設定套件安裝策略。",
280
+ helpCwd: "修改命令工作區。",
281
+ helpLanguage: "設定 CLI 語言,或用 /language auto 跟隨系統語言。",
282
+ helpExit: "離開。",
283
+ helpNormalRequest: "輸入普通任務即可執行智能體。例如: write a Python CLI app with tests",
284
+ helpAutocomplete: "輸入 / 後按 Tab 自動補全命令。",
285
+ helpQueue: "執行中 Enter 會把訊息立即傳入目前任務 (→),Tab 會排到結束後執行 (↳)。",
286
+ helpEditQueue: "Alt+Up 編輯上一條立即訊息;Shift+Left 編輯上一條排隊訊息。",
287
+ helpEsc: "空閒時 Esc 不操作。執行中 Esc 等待待處理 → 訊息或停止;Ctrl+C 始終停止。",
288
+ languageSet: "language={language} ({label})",
289
+ languageUsage: "用法: /language auto|en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru",
290
+ project: "專案",
291
+ statusNew: "new",
292
+ statusIdle: "idle",
293
+ systemLanguage: "系統",
294
+ },
295
+ };
296
+
297
+ const FALLBACKS = {
298
+ ko: {
299
+ launchSubtitle: "웹 우선 에이전트 작업공간",
300
+ launchTagline: "브라우저 + 셸 + 파일 + Docker + 웹 검색 + 스카우트",
301
+ interactiveIntro: "대화형 에이전트 채팅입니다. /help 명령, /exit 종료.",
302
+ promptEmpty: "요청을 입력하세요. /help, Enter 전송, Ctrl+J 줄바꿈",
303
+ helpTitle: "명령:",
304
+ languageSet: "language={language} ({label})",
305
+ languageUsage: "사용법: /language auto|en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru",
306
+ project: "프로젝트",
307
+ systemLanguage: "시스템",
308
+ },
309
+ fr: {
310
+ launchSubtitle: "espace agent web-first",
311
+ launchTagline: "navigateur + shell + fichiers + Docker + recherche web + scouts",
312
+ interactiveIntro: "Chat agent interactif. Tapez /help pour les commandes, /exit pour quitter.",
313
+ promptEmpty: "tapez une demande, /help, Entrée pour envoyer, Ctrl+J pour une nouvelle ligne",
314
+ helpTitle: "Commandes:",
315
+ languageSet: "language={language} ({label})",
316
+ languageUsage: "Usage: /language auto|en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru",
317
+ project: "Projet",
318
+ systemLanguage: "système",
319
+ },
320
+ es: {
321
+ launchSubtitle: "espacio de agente centrado en web",
322
+ launchTagline: "navegador + shell + archivos + Docker + búsqueda web + scouts",
323
+ interactiveIntro: "Chat interactivo del agente. Escribe /help para comandos, /exit para salir.",
324
+ promptEmpty: "escribe una solicitud, /help, Enter para enviar, Ctrl+J para nueva línea",
325
+ helpTitle: "Comandos:",
326
+ languageSet: "language={language} ({label})",
327
+ languageUsage: "Uso: /language auto|en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru",
328
+ project: "Proyecto",
329
+ systemLanguage: "sistema",
330
+ },
331
+ ar: {
332
+ launchSubtitle: "مساحة عمل وكيل تعتمد الويب أولاً",
333
+ launchTagline: "متصفح + صدفة + ملفات + Docker + بحث ويب + كشافة",
334
+ interactiveIntro: "محادثة وكيل تفاعلية. اكتب /help للأوامر و /exit للخروج.",
335
+ promptEmpty: "اكتب طلباً، /help، Enter للإرسال، Ctrl+J لسطر جديد",
336
+ helpTitle: "الأوامر:",
337
+ languageSet: "language={language} ({label})",
338
+ languageUsage: "الاستخدام: /language auto|en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru",
339
+ project: "المشروع",
340
+ systemLanguage: "النظام",
341
+ },
342
+ vi: {
343
+ launchSubtitle: "không gian agent ưu tiên web",
344
+ launchTagline: "trình duyệt + shell + tệp + Docker + tìm kiếm web + scouts",
345
+ interactiveIntro: "Chat agent tương tác. Gõ /help để xem lệnh, /exit để thoát.",
346
+ promptEmpty: "nhập yêu cầu, /help, Enter để gửi, Ctrl+J xuống dòng",
347
+ helpTitle: "Lệnh:",
348
+ languageSet: "language={language} ({label})",
349
+ languageUsage: "Cách dùng: /language auto|en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru",
350
+ project: "Dự án",
351
+ systemLanguage: "hệ thống",
352
+ },
353
+ de: {
354
+ launchSubtitle: "web-first Agent-Arbeitsbereich",
355
+ launchTagline: "Browser + Shell + Dateien + Docker + Websuche + Scouts",
356
+ interactiveIntro: "Interaktiver Agent-Chat. /help für Befehle, /exit zum Beenden.",
357
+ promptEmpty: "Anfrage eingeben, /help, Enter zum Senden, Ctrl+J für neue Zeile",
358
+ helpTitle: "Befehle:",
359
+ languageSet: "language={language} ({label})",
360
+ languageUsage: "Nutzung: /language auto|en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru",
361
+ project: "Projekt",
362
+ systemLanguage: "System",
363
+ },
364
+ ru: {
365
+ launchSubtitle: "web-first рабочая область агента",
366
+ launchTagline: "браузер + shell + файлы + Docker + веб-поиск + scouts",
367
+ interactiveIntro: "Интерактивный чат агента. /help для команд, /exit для выхода.",
368
+ promptEmpty: "введите запрос, /help, Enter отправить, Ctrl+J новая строка",
369
+ helpTitle: "Команды:",
370
+ languageSet: "language={language} ({label})",
371
+ languageUsage: "Использование: /language auto|en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru",
372
+ project: "Проект",
373
+ systemLanguage: "система",
374
+ },
375
+ };
376
+
377
+ for (const [language, values] of Object.entries(FALLBACKS)) {
378
+ TRANSLATIONS[language] = { ...TRANSLATIONS.en, ...values };
379
+ }
380
+
381
+ function cleanLanguage(value = "") {
382
+ return String(value || "")
383
+ .trim()
384
+ .replace(/\.(utf-?8|utf8)$/i, "")
385
+ .replace(/@.*$/, "")
386
+ .toLowerCase()
387
+ .replace(/\s+/g, "");
388
+ }
389
+
390
+ export function normalizeLanguage(value = "", fallback = "en") {
391
+ const direct = String(value || "").trim();
392
+ if (SUPPORTED_LANGUAGES.includes(direct)) return direct;
393
+ const cleaned = cleanLanguage(value);
394
+ if (LANGUAGE_ALIASES.has(cleaned)) return LANGUAGE_ALIASES.get(cleaned) || fallback;
395
+ const dash = cleaned.replace(/_/g, "-");
396
+ if (LANGUAGE_ALIASES.has(dash)) return LANGUAGE_ALIASES.get(dash) || fallback;
397
+ if (dash.startsWith("zh-tw") || dash.startsWith("zh-hk") || dash.startsWith("zh-mo")) return "zh-Hant";
398
+ if (dash.startsWith("zh")) return "zh-Hans";
399
+ if (dash.startsWith("ja")) return "ja";
400
+ if (dash.startsWith("ko")) return "ko";
401
+ if (dash.startsWith("fr")) return "fr";
402
+ if (dash.startsWith("es")) return "es";
403
+ if (dash.startsWith("ar")) return "ar";
404
+ if (dash.startsWith("vi")) return "vi";
405
+ if (dash.startsWith("de")) return "de";
406
+ if (dash.startsWith("ru")) return "ru";
407
+ if (dash.startsWith("en")) return "en";
408
+ return SUPPORTED_LANGUAGES.includes(fallback) ? fallback : "en";
409
+ }
410
+
411
+ export function detectSystemLanguage(env = process.env) {
412
+ return normalizeLanguage(env.AGINTI_LANGUAGE || env.LANGUAGE || env.LC_ALL || env.LC_MESSAGES || env.LANG || "en", "en");
413
+ }
414
+
415
+ export function resolveLanguage(value = "", env = process.env) {
416
+ const cleaned = cleanLanguage(value);
417
+ if (!cleaned || ["auto", "system", "default"].includes(cleaned)) return detectSystemLanguage(env);
418
+ return normalizeLanguage(value, detectSystemLanguage(env));
419
+ }
420
+
421
+ export function languageLabel(language = "en") {
422
+ return LANGUAGE_LABELS[normalizeLanguage(language)] || LANGUAGE_LABELS.en;
423
+ }
424
+
425
+ export function t(key, language = "en", values = {}) {
426
+ const normalized = normalizeLanguage(language);
427
+ const text = TRANSLATIONS[normalized]?.[key] || TRANSLATIONS.en[key] || key;
428
+ return Object.entries(values).reduce((result, [name, value]) => result.replaceAll(`{${name}}`, String(value)), text);
429
+ }
430
+
431
+ export function languageInstruction(language = "en") {
432
+ const normalized = normalizeLanguage(language);
433
+ if (normalized === "en") return "User interface language: English. Reply in the user's requested language; otherwise English is acceptable.";
434
+ return `User interface language: ${languageLabel(normalized)} (${normalized}). Prefer replying in this language unless the user explicitly requests another language.`;
435
+ }
@@ -17,6 +17,7 @@ import {
17
17
  getModelRoleDefaults,
18
18
  modelsForProviderGroup,
19
19
  } from "./model-routing.js";
20
+ import { languageLabel, resolveLanguage, t } from "./i18n.js";
20
21
 
21
22
  const useColor = Boolean(input.isTTY && output.isTTY && process.env.AGINTIFLOW_NO_COLOR !== "1");
22
23
  const ansi = {
@@ -74,6 +75,8 @@ const SLASH_COMMANDS = [
74
75
  "/routing",
75
76
  "/provider",
76
77
  "/model",
78
+ "/language",
79
+ "/lang",
77
80
  "/docker",
78
81
  "/latex",
79
82
  "/installs",
@@ -84,6 +87,16 @@ const SLASH_COMMANDS = [
84
87
  ];
85
88
  const promptHistory = [];
86
89
  let activeRunInput = null;
90
+ let cliLanguage = resolveLanguage();
91
+
92
+ function setCliLanguage(language = "") {
93
+ cliLanguage = resolveLanguage(language || "");
94
+ return cliLanguage;
95
+ }
96
+
97
+ function tr(key, values = {}) {
98
+ return t(key, cliLanguage, values);
99
+ }
87
100
 
88
101
  function color(value, ...codes) {
89
102
  if (!useColor || codes.length === 0) return String(value);
@@ -534,9 +547,9 @@ function launchTitleLines(contentWidth) {
534
547
  return largeWidth <= contentWidth ? LARGE_LAUNCH_TITLE : COMPACT_LAUNCH_TITLE;
535
548
  }
536
549
 
537
- export function buildLaunchHeaderLines({ packageVersion = "", frame = 2, width = terminalWidth(), animated = true } = {}) {
538
- const subtitle = "web-first agent workspace";
539
- const tagline = "browser + shell + files + docker + web search + scouts";
550
+ export function buildLaunchHeaderLines({ packageVersion = "", frame = 2, width = terminalWidth(), animated = true, language = cliLanguage } = {}) {
551
+ const subtitle = t("launchSubtitle", language);
552
+ const tagline = t("launchTagline", language);
540
553
  const version = packageVersion ? `v${packageVersion}` : "";
541
554
  const terminalColumns = Math.max(Number(width) || 80, 50);
542
555
  const contentWidth = Math.min(Math.max(terminalColumns - 8, 58), 112);
@@ -561,9 +574,9 @@ export function buildLaunchHeaderLines({ packageVersion = "", frame = 2, width =
561
574
  return boxLines.map((line) => `${indent}${line}`);
562
575
  }
563
576
 
564
- async function renderLaunchHeader(packageVersion = "") {
577
+ async function renderLaunchHeader(packageVersion = "", language = cliLanguage) {
565
578
  if (!useColor || process.env.AGINTIFLOW_NO_ANIMATION === "1") {
566
- console.log(buildLaunchHeaderLines({ packageVersion, animated: false }).join("\n"));
579
+ console.log(buildLaunchHeaderLines({ packageVersion, animated: false, language }).join("\n"));
567
580
  return;
568
581
  }
569
582
 
@@ -572,7 +585,7 @@ async function renderLaunchHeader(packageVersion = "") {
572
585
  let previousLineCount = 0;
573
586
  output.write(ansi.cursorHide);
574
587
  for (let frame = 0; frame < 18; frame += 1) {
575
- const lines = [...paddingLines, ...buildLaunchHeaderLines({ packageVersion, frame, animated: true })];
588
+ const lines = [...paddingLines, ...buildLaunchHeaderLines({ packageVersion, frame, animated: true, language })];
576
589
  if (previousLineCount > 0) output.write(`\x1b[${previousLineCount}A`);
577
590
  output.write(lines.map((line) => `\r${ansi.clearLine}${line}`).join("\n"));
578
591
  output.write("\n");
@@ -583,47 +596,45 @@ async function renderLaunchHeader(packageVersion = "") {
583
596
  }
584
597
 
585
598
  function printHelp() {
599
+ const command = (name, detail, key) => `${name.padEnd(28)} ${tr(key) || detail}`;
586
600
  printAgentMessage(
587
601
  [
588
- "Commands:",
589
- " /help Show this help.",
590
- " /status Show active route, workspace, sandbox, and session.",
591
- " /login [deepseek|openai|qwen|venice|grsai] Pick, paste, and save project-local API keys.",
592
- " /auth [deepseek|openai|qwen|venice|grsai] Alias for /login.",
593
- " /instructions Show AGINTI.md project instructions status.",
594
- " /memory Alias for /instructions.",
595
- " /models Show route/main/spare/wrapper/auxiliary model roles.",
596
- " /venice [off|model] Pick Venice route/main models, or restore DeepSeek defaults.",
597
- " /route [mode|provider/model]",
598
- " Open route selector, or set routing/fast route model.",
599
- " /model [provider/model] Open main-model selector, or set the active/main model.",
600
- " /spare [provider/model] [reasoning]",
601
- " Open spare selector, or set e.g. /spare openai/gpt-5.4 medium.",
602
- " /wrapper [on|off|codex model reasoning]",
603
- " Configure optional external wrapper.",
604
- " /auxiliary [status|grsai|venice|model [provider/model]|on|off|image]",
605
- " Manage optional auxiliary skills, including image generation.",
606
- " /new Start a fresh session on the next message.",
607
- " /resume <session-id> Continue a saved session.",
608
- " /sessions List recent sessions in this project.",
609
- " /skills [query] List Markdown skills selected for a topic.",
610
- " /profile <name> Set task profile, e.g. code, website, latex, maintenance.",
611
- " /web-search on|off Enable or disable the web_search tool.",
612
- " /scouts on|off|<1-10> Enable parallel DeepSeek scouts and set scout count.",
613
- " /routing <mode> Set routing: smart, fast, complex, manual.",
614
- " /provider [name] Open provider selector, or set deepseek/openai/qwen/venice/mock.",
615
- " /docker on Use docker-workspace with approved package installs.",
616
- " /docker off Use host shell policy.",
617
- " /latex on Use the LaTeX/PDF profile in Docker with a larger step budget.",
618
- " /installs block|prompt|allow",
619
- " /cwd <path> Change command workspace.",
620
- " /exit Quit.",
602
+ tr("helpTitle"),
603
+ ` ${command("/help", "Show this help.", "helpHelp")}`,
604
+ ` ${command("/status", "Show active route, workspace, sandbox, and session.", "helpStatus")}`,
605
+ ` ${command("/login [deepseek|openai|qwen|venice|grsai]", "Pick, paste, and save project-local API keys.", "helpLogin")}`,
606
+ ` ${command("/auth [deepseek|openai|qwen|venice|grsai]", "Alias for /login.", "helpLogin")}`,
607
+ ` ${command("/instructions", "Show AGINTI.md project instructions status.", "helpInstructions")}`,
608
+ ` ${command("/memory", "Alias for /instructions.", "helpInstructions")}`,
609
+ ` ${command("/models", "Show route/main/spare/wrapper/auxiliary model roles.", "helpModels")}`,
610
+ ` ${command("/venice [off|model]", "Pick Venice route/main models, or restore DeepSeek defaults.", "helpVenice")}`,
611
+ ` ${command("/route [mode|provider/model]", "Open route selector, or set routing/fast route model.", "helpRoute")}`,
612
+ ` ${command("/model [provider/model]", "Open main-model selector, or set the active/main model.", "helpModel")}`,
613
+ ` ${command("/spare [provider/model] [reasoning]", "Open spare selector, or set e.g. /spare openai/gpt-5.4 medium.", "helpSpare")}`,
614
+ ` ${command("/wrapper [on|off|codex model reasoning]", "Configure optional external wrapper.", "helpWrapper")}`,
615
+ ` ${command("/auxiliary [status|grsai|venice|model [provider/model]|on|off|image]", "Manage optional auxiliary skills, including image generation.", "helpAuxiliary")}`,
616
+ ` ${command("/new", "Start a fresh session on the next message.", "helpNew")}`,
617
+ ` ${command("/resume <session-id>", "Continue a saved session.", "helpResume")}`,
618
+ ` ${command("/sessions", "List recent sessions in this project.", "helpSessions")}`,
619
+ ` ${command("/skills [query]", "List Markdown skills selected for a topic.", "helpSkills")}`,
620
+ ` ${command("/profile <name>", "Set task profile, e.g. code, website, latex, maintenance.", "helpProfile")}`,
621
+ ` ${command("/web-search on|off", "Enable or disable the web_search tool.", "helpWebSearch")}`,
622
+ ` ${command("/scouts on|off|<1-10>", "Enable parallel DeepSeek scouts and set scout count.", "helpScouts")}`,
623
+ ` ${command("/routing <mode>", "Set routing: smart, fast, complex, manual.", "helpRouting")}`,
624
+ ` ${command("/provider [name]", "Open provider selector, or set deepseek/openai/qwen/venice/mock.", "helpProvider")}`,
625
+ ` ${command("/docker on", "Use docker-workspace with approved package installs.", "helpDockerOn")}`,
626
+ ` ${command("/docker off", "Use host shell policy.", "helpDockerOff")}`,
627
+ ` ${command("/latex on", "Use the LaTeX/PDF profile in Docker with a larger step budget.", "helpLatex")}`,
628
+ ` ${command("/installs block|prompt|allow", "Set package install policy.", "helpInstalls")}`,
629
+ ` ${command("/cwd <path>", "Change command workspace.", "helpCwd")}`,
630
+ ` ${command("/language [auto|en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru]", "Set CLI language.", "helpLanguage")}`,
631
+ ` ${command("/exit", "Quit.", "helpExit")}`,
621
632
  "",
622
- "Type a normal request to run the agent. Example: write a Python CLI app with tests",
623
- "Type / then Tab to autocomplete commands.",
624
- "While a run is active, Enter pipes a message into the current run (→), Tab queues it after finish (↳).",
625
- "Alt+Up edits the last piped message; Shift+Left edits the last queued message.",
626
- "Esc is ignored while idle. During a run, Esc waits for pending → pipe messages or stops if none; Ctrl+C always stops.",
633
+ tr("helpNormalRequest"),
634
+ tr("helpAutocomplete"),
635
+ tr("helpQueue"),
636
+ tr("helpEditQueue"),
637
+ tr("helpEsc"),
627
638
  ].join("\n")
628
639
  );
629
640
  }
@@ -754,7 +765,7 @@ export function buildPromptLayout(buffer = "", cursor = 0, width = terminalWidth
754
765
  ? options.suggestions
755
766
  : commandSuggestions(safeBuffer.split("\n")[0] || "");
756
767
  const suggestionIndex = clamp(Number(options.suggestionIndex) || 0, 0, Math.max(suggestions.length - 1, 0));
757
- const emptyHint = "type a request, /help, Enter to send, Ctrl+J for newline";
768
+ const emptyHint = t("promptEmpty", options.language || cliLanguage);
758
769
  const visible = promptVisibleWindow(rows, cursorRow, height);
759
770
  const renderedRows = [];
760
771
  let renderedCursorRow = cursorRow - visible.start;
@@ -973,6 +984,7 @@ function readTtyPrompt(options = {}) {
973
984
  const suggestions = commandSuggestions(suggestionAnchor || buffer.split("\n")[0] || "");
974
985
  rendered = renderPromptBuffer(buffer, cursor, rendered, {
975
986
  ...options,
987
+ language: options.language || cliLanguage,
976
988
  suggestions,
977
989
  suggestionIndex,
978
990
  });
@@ -1178,7 +1190,7 @@ function readTtyPrompt(options = {}) {
1178
1190
 
1179
1191
  async function readPromptAnswer(rl, state = {}) {
1180
1192
  if (input.isTTY && output.isTTY && typeof input.setRawMode === "function") {
1181
- return readTtyPrompt({ commandCwd: state.commandCwd || process.cwd() });
1193
+ return readTtyPrompt({ commandCwd: state.commandCwd || process.cwd(), language: state.language || cliLanguage });
1182
1194
  }
1183
1195
  return rl.question(userPrompt());
1184
1196
  }
@@ -1261,6 +1273,7 @@ class LiveRunInput {
1261
1273
  }
1262
1274
  this.rendered = renderPromptBuffer(this.buffer, this.cursor, this.rendered, {
1263
1275
  commandCwd: this.commandCwd,
1276
+ language: this.state.language || cliLanguage,
1264
1277
  statusLine: this.statusLine,
1265
1278
  pendingAsap: this.pendingAsap,
1266
1279
  pendingQueued: this.pendingQueued,
@@ -1292,6 +1305,7 @@ class LiveRunInput {
1292
1305
  moveVertical(direction) {
1293
1306
  const layout = buildPromptLayout(this.buffer, this.cursor, terminalWidth(), terminalHeight(), {
1294
1307
  commandCwd: this.commandCwd,
1308
+ language: this.state.language || cliLanguage,
1295
1309
  statusLine: this.statusLine,
1296
1310
  pendingAsap: this.pendingAsap,
1297
1311
  pendingQueued: this.pendingQueued,
@@ -1496,6 +1510,7 @@ function printStatus(state) {
1496
1510
  printSystemLine(`cwd=${state.commandCwd || process.cwd()}`);
1497
1511
  printSystemLine(`session=${state.sessionId || "new"}`);
1498
1512
  printSystemLine(`status=${state.status || "idle"}${state.activeGoal ? ` workingOn=${state.activeGoal}` : ""}`);
1513
+ printSystemLine(`language=${state.language || cliLanguage} (${languageLabel(state.language || cliLanguage)})`);
1499
1514
  if (state.lastEvent) printSystemLine(`last=${state.lastEvent}`);
1500
1515
  printSystemLine(`provider=${state.provider || "auto"} routing=${state.routingMode} model=${state.model || "auto"}`);
1501
1516
  printSystemLine(
@@ -1638,6 +1653,7 @@ function createState(args = {}) {
1638
1653
  auxiliaryProvider: args.auxiliaryProvider || "grsai",
1639
1654
  auxiliaryModel: args.auxiliaryModel || "nano-banana-2",
1640
1655
  taskProfile: normalizeTaskProfile(args.taskProfile || "auto"),
1656
+ language: resolveLanguage(args.language || process.env.AGINTI_LANGUAGE || ""),
1641
1657
  headless: args.headless ?? false,
1642
1658
  maxSteps:
1643
1659
  Number.isFinite(args.maxSteps) && args.maxSteps > 0
@@ -2202,6 +2218,18 @@ async function handleCommand(line, state, packageDir) {
2202
2218
  );
2203
2219
  return true;
2204
2220
  }
2221
+ if (command === "language" || command === "lang") {
2222
+ if (!value) {
2223
+ printSystemLine(tr("languageSet", { language: state.language || cliLanguage, label: languageLabel(state.language || cliLanguage) }));
2224
+ printAgentMessage(tr("languageUsage"));
2225
+ return true;
2226
+ }
2227
+ const nextLanguage = resolveLanguage(value);
2228
+ state.language = nextLanguage;
2229
+ setCliLanguage(nextLanguage);
2230
+ printSystemLine(tr("languageSet", { language: nextLanguage, label: languageLabel(nextLanguage) }));
2231
+ return true;
2232
+ }
2205
2233
  if (command === "login" || command === "auth") {
2206
2234
  await promptAndSaveProviderKey(value || "deepseek", state);
2207
2235
  return true;
@@ -2628,6 +2656,7 @@ async function runPrompt(prompt, state, packageDir) {
2628
2656
  auxiliaryProvider: state.auxiliaryProvider,
2629
2657
  auxiliaryModel: state.auxiliaryModel,
2630
2658
  taskProfile: state.taskProfile,
2659
+ language: state.language || cliLanguage,
2631
2660
  maxSteps: runMaxSteps,
2632
2661
  headless: state.headless,
2633
2662
  resume: state.sessionId,
@@ -2729,6 +2758,7 @@ async function runPrompt(prompt, state, packageDir) {
2729
2758
 
2730
2759
  export async function startInteractiveCli(args = {}, { packageDir, packageVersion } = {}) {
2731
2760
  const state = createState(args);
2761
+ setCliLanguage(state.language);
2732
2762
  const rl =
2733
2763
  input.isTTY && output.isTTY
2734
2764
  ? null
@@ -2739,10 +2769,10 @@ export async function startInteractiveCli(args = {}, { packageDir, packageVersio
2739
2769
  completer: commandCompleter,
2740
2770
  });
2741
2771
 
2742
- await renderLaunchHeader(packageVersion);
2743
- printSystemLine(`Project: ${process.cwd()}`);
2772
+ await renderLaunchHeader(packageVersion, state.language);
2773
+ printSystemLine(`${tr("project")}: ${process.cwd()}`);
2744
2774
  await maybeOnboardDeepSeekKey(state);
2745
- printAgentMessage("Interactive agent chat. Type /help for commands, /exit to quit.");
2775
+ printAgentMessage(tr("interactiveIntro"));
2746
2776
  printStatus(state);
2747
2777
  await printResumeHistory(state);
2748
2778
 
package/src/web-db.js CHANGED
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { DatabaseSync } from "node:sqlite";
4
4
  import { getModelPresets, getModelRoleDefaults } from "./model-routing.js";
5
+ import { resolveLanguage } from "./i18n.js";
5
6
 
6
7
  const PREFERENCES_SCHEMA_VERSION = 7;
7
8
 
@@ -44,7 +45,7 @@ function defaultPreferences(baseDir) {
44
45
  dockerSandboxImage: "agintiflow-sandbox:latest",
45
46
  allowPasswords: false,
46
47
  allowDestructive: false,
47
- language: "en",
48
+ language: resolveLanguage(process.env.AGINTI_LANGUAGE || ""),
48
49
  taskProfile: "auto",
49
50
  };
50
51
  }
package/web.js CHANGED
@@ -24,6 +24,7 @@ import { loadProjectEnv, projectPaths, providerKeyStatus, setProviderKey } from
24
24
  import { buildCapabilityReport } from "./src/capabilities.js";
25
25
  import { listSkills } from "./src/skill-library.js";
26
26
  import { platformInfo, platformLabel, platformSetupHints } from "./src/platform.js";
27
+ import { normalizeLanguage } from "./src/i18n.js";
27
28
  import {
28
29
  buildArtifacts,
29
30
  countUnreadArtifacts,
@@ -45,24 +46,6 @@ const port = Number(process.env.PORT || 3210);
45
46
  const host = process.env.HOST || "127.0.0.1";
46
47
  const runs = new Map();
47
48
  const db = new WebDatabase(baseDir);
48
- const supportedLanguages = new Set([
49
- "en",
50
- "ar",
51
- "es",
52
- "fr",
53
- "ja",
54
- "ko",
55
- "vi",
56
- "zh-Hans",
57
- "zh-Hant",
58
- "de",
59
- "ru",
60
- ]);
61
-
62
- function normalizeLanguage(language, fallback = "en") {
63
- if (supportedLanguages.has(language)) return language;
64
- return supportedLanguages.has(fallback) ? fallback : "en";
65
- }
66
49
 
67
50
  function sessionStore(sessionId) {
68
51
  return new SessionStore(sessionsDir, sessionId);
@@ -231,7 +214,7 @@ function normalizePreferencePayload(body = {}, current = db.getPreferences()) {
231
214
  allowPasswords: typeof body.allowPasswords === "boolean" ? body.allowPasswords : Boolean(current.allowPasswords),
232
215
  allowDestructive:
233
216
  typeof body.allowDestructive === "boolean" ? body.allowDestructive : Boolean(current.allowDestructive),
234
- language: normalizeLanguage(body.language, current.language),
217
+ language: normalizeLanguage(body.language || process.env.AGINTI_LANGUAGE || current.language, current.language || "en"),
235
218
  };
236
219
  }
237
220
 
@@ -310,6 +293,7 @@ function buildRunConfig(body, overrides = {}) {
310
293
  dockerSandboxImage: merged.dockerSandboxImage,
311
294
  commandCwd: merged.commandCwd,
312
295
  taskProfile: merged.taskProfile,
296
+ language: merged.language,
313
297
  baseDir,
314
298
  packageDir,
315
299
  sessionId: overrides.sessionId,