@baryonlabs/cli 0.3.5 → 0.3.7

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/bin/baryon.js CHANGED
@@ -16,10 +16,11 @@ import {
16
16
  import { loadConfig, piProviderConfigured, hasConfig, prunePiPackages } from "../src/config.js";
17
17
  import { DEPRECATED_EXTENSIONS } from "../src/constants.js";
18
18
  import { runPi, resolvePiEntry } from "../src/pi.js";
19
- import { checkLatest } from "../src/api.js";
19
+ import { checkLatest, whoami } from "../src/api.js";
20
20
  import { spawnSync } from "node:child_process";
21
21
  import { createRequire } from "node:module";
22
22
  import { c, err, log, sym } from "../src/ui.js";
23
+ import { t } from "../src/i18n.js";
23
24
 
24
25
  /** Best-effort: warn loudly when a newer CLI exists. The gateway enforces the
25
26
  * minimum version (426), so cloud use is blocked until you update; this is the
@@ -28,8 +29,8 @@ async function warnIfOutdated() {
28
29
  const r = await checkLatest();
29
30
  if (r?.outdated) {
30
31
  log(
31
- `\n ${sym.warn} ${c.yellow(`업데이트 필요: @baryonlabs/cli ${r.current} ${r.latest}`)}\n` +
32
- ` ${c.dim("baryon.ai 사용에 최신 버전이 필요합니다.")} ${c.lime("baryon update")} ${c.dim(" 실행하세요.\n")}`,
32
+ `\n ${sym.warn} ${c.yellow(t("bin.outdated", { current: r.current, latest: r.latest }))}\n` +
33
+ ` ${c.dim(t("bin.outdatedSub"))} ${c.lime("baryon update")} ${c.dim(t("bin.outdatedRun") + "\n")}`,
33
34
  );
34
35
  }
35
36
  }
@@ -44,7 +45,7 @@ function showVersion() {
44
45
  const r = spawnSync(process.execPath, [entry, "--version"], {
45
46
  encoding: "utf8",
46
47
  });
47
- if (r.stdout) log(`pi ${c.dim(r.stdout.trim())}`);
48
+ if (r.stdout) log(t("bin.piVersion", { version: c.dim(r.stdout.trim()) }));
48
49
  }
49
50
  return 0;
50
51
  }
@@ -97,12 +98,12 @@ async function main() {
97
98
  if (unconfigured && !userTargets) {
98
99
  if (process.stdin.isTTY) {
99
100
  log(
100
- ` ${sym.warn} ${c.yellow("아직 설정되지 않았습니다.")} ${c.lime("baryon setup")} ${c.dim("을 먼저 실행합니다.")}\n`,
101
+ ` ${sym.warn} ${c.yellow(t("bin.unconfiguredTTY"))} ${c.lime("baryon setup")} ${c.dim(t("bin.unconfiguredTTYRun"))}\n`,
101
102
  );
102
103
  return setup([]);
103
104
  }
104
105
  log(
105
- ` ${sym.warn} ${c.yellow("설정이 없습니다.")} ${c.lime("baryon setup")} ${c.dim("을 먼저 실행하세요.")}`,
106
+ ` ${sym.warn} ${c.yellow(t("bin.unconfigured"))} ${c.lime("baryon setup")} ${c.dim(t("bin.unconfiguredRun"))}`,
106
107
  );
107
108
  return 1;
108
109
  }
@@ -112,11 +113,21 @@ async function main() {
112
113
  try {
113
114
  const pruned = prunePiPackages(DEPRECATED_EXTENSIONS);
114
115
  if (pruned.length)
115
- log(` ${sym.info} ${c.dim(`충돌 확장 정리됨: ${pruned.join(", ")}`)}`);
116
+ log(` ${sym.info} ${c.dim(t("bin.prunedConflicts", { names: pruned.join(", ") }))}`);
116
117
  } catch {
117
118
  /* best-effort */
118
119
  }
119
120
  const cfg = loadConfig();
121
+ // Show which room (project/분반 · seat) this key is in — best-effort.
122
+ try {
123
+ const who = await whoami(cfg.baseUrl, cfg.apiKey);
124
+ if (who?.project) {
125
+ const key = who.project_status === "active" ? "bin.room" : "bin.roomInactive";
126
+ log(` ${c.teal(t(key, { project: who.project, seat: who.seat || "—" }))}`);
127
+ }
128
+ } catch {
129
+ /* best-effort */
130
+ }
120
131
  return runPi(argv, cfg);
121
132
  }
122
133
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baryonlabs/cli",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Baryon CLI — AI 코딩·학습 에이전트. baryon.ai API에 기본 연결된 pi 코딩 에이전트 래퍼. 한 줄 설치, 상용·로컬 모델 전환.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/api.js CHANGED
@@ -99,4 +99,29 @@ export async function ping(baseUrl, apiKey, { timeoutMs = 6000 } = {}) {
99
99
  }
100
100
  }
101
101
 
102
+ /**
103
+ * Resolve the connected project (분반) + seat for the configured key, so the CLI
104
+ * can show which "room" it's in. Best-effort: null on any failure / offline.
105
+ */
106
+ export async function whoami(baseUrl, apiKey, { timeoutMs = 4000 } = {}) {
107
+ if (!apiKey) return null;
108
+ const ctrl = new AbortController();
109
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs);
110
+ try {
111
+ const res = await fetch(joinUrl(baseUrl, "/whoami"), {
112
+ headers: {
113
+ Authorization: `Bearer ${apiKey}`,
114
+ "X-Baryon-Client": `baryon-cli/${CLIENT_VERSION}`,
115
+ },
116
+ signal: ctrl.signal,
117
+ });
118
+ if (!res.ok) return null;
119
+ return await res.json();
120
+ } catch {
121
+ return null;
122
+ } finally {
123
+ clearTimeout(timer);
124
+ }
125
+ }
126
+
102
127
  export { DEFAULT_MODELS };
package/src/commands.js CHANGED
@@ -33,6 +33,7 @@ import {
33
33
  } from "./constants.js";
34
34
  import { runPi, resolvePiEntry } from "./pi.js";
35
35
  import { banner, c, info, log, ok, err, warn, prompt, promptHidden, sym } from "./ui.js";
36
+ import { t, normalizeLocale, getLocale } from "./i18n.js";
36
37
 
37
38
  /** Parse `--flag value` / `--flag=value` pairs out of argv. */
38
39
  function parseFlags(args) {
@@ -53,7 +54,7 @@ function parseFlags(args) {
53
54
  export async function setup(args) {
54
55
  const flags = parseFlags(args);
55
56
  banner();
56
- log(c.bold(" baryon.ai 연결 설정\n"));
57
+ log(c.bold(` ${t("setup.title")}\n`));
57
58
 
58
59
  const existing = loadConfig();
59
60
  const baseUrl = flags["base-url"] || existing.baseUrl || DEFAULT_BASE_URL;
@@ -61,103 +62,107 @@ export async function setup(args) {
61
62
  let apiKey = flags.key || flags["api-key"] || process.env.BARYON_API_KEY || "";
62
63
  if (!apiKey) {
63
64
  // Tell the user where keys come from before asking for one.
64
- log(` ${sym.info} 발급·관리: ${c.lime(KEYS_URL)}`);
65
- log(` ${c.dim(` (vibecamp.us 대시보드에서 발급/회수 · 형식 ${KEY_PREFIX})`)}\n`);
66
- apiKey = await promptHidden(` ${sym.info} baryon.ai API key: `);
65
+ log(` ${sym.info} ${t("setup.keyHint", { url: c.lime(KEYS_URL) })}`);
66
+ log(` ${c.dim(t("setup.keyHintSub", { prefix: KEY_PREFIX }))}\n`);
67
+ apiKey = await promptHidden(` ${sym.info} ${t("setup.keyPrompt")}`);
67
68
  }
68
69
  if (!apiKey) {
69
- warn("API 키 없이 저장합니다. 나중에 `baryon setup --key <KEY>` 로 추가하세요.");
70
+ warn(t("setup.noKey"));
70
71
  } else if (!apiKey.startsWith(KEY_PREFIX)) {
71
- warn(`키 형식이 ${KEY_PREFIX}… 아닙니다. 로컬/상용 키라면 무시하세요.`);
72
+ warn(t("setup.badKeyFormat", { prefix: KEY_PREFIX }));
72
73
  }
73
74
 
74
75
  saveConfig({ apiKey, baseUrl });
75
- ok(`config 저장 ${c.dim(BARYON_CONFIG)}`);
76
+ ok(t("setup.configSaved", { file: c.dim(BARYON_CONFIG) }));
76
77
 
77
78
  // Try live model discovery; fall back to defaults offline.
78
79
  let models = null;
79
80
  if (apiKey) {
80
- info("모델 목록을 조회하는 중…");
81
+ info(t("setup.discovering"));
81
82
  models = await discoverModels(baseUrl, apiKey);
82
83
  }
83
84
  if (models) {
84
- ok(`${models.length}개 모델 발견 (${c.lime(models.map((m) => m.id).slice(0, 4).join(", "))}${models.length > 4 ? "…" : ""})`);
85
+ ok(t("setup.modelsFound", {
86
+ count: models.length,
87
+ list: c.lime(models.map((m) => m.id).slice(0, 4).join(", ")),
88
+ more: models.length > 4 ? "…" : "",
89
+ }));
85
90
  } else {
86
91
  models = DEFAULT_MODELS;
87
- warn(`모델 자동 조회 실패 — 기본 모델 ${models.length}개로 구성 (오프라인/폐쇄망 정상)`);
92
+ warn(t("setup.modelsFailed", { count: models.length }));
88
93
  }
89
94
 
90
95
  saveConfig({ defaultModel: models[0].id });
91
96
  const file = syncPiModels({ baseUrl, models });
92
- ok(`pi 프로바이더 ${c.lime(PROVIDER)} 구성 → ${c.dim(file)}`);
97
+ ok(t("setup.providerConfigured", { provider: c.lime(PROVIDER), file: c.dim(file) }));
93
98
 
94
99
  if (flags["no-extensions"]) {
95
- info("기본 확장 설치 건너뜀 (--no-extensions)");
100
+ info(t("setup.skipExtensions"));
96
101
  } else {
97
102
  installDefaults();
98
103
  }
99
104
 
100
105
  if (flags["no-skills"]) {
101
- info("기본 스킬 설치 건너뜀 (--no-skills)");
106
+ info(t("setup.skipSkills"));
102
107
  } else {
103
108
  installSkills();
104
109
  }
105
110
 
106
111
  if (flags["no-browser"]) {
107
- info("agent-browser 설치 건너뜀 (--no-browser)");
112
+ info(t("setup.skipBrowser"));
108
113
  } else {
109
114
  installBrowser();
110
115
  }
111
116
 
112
- log(`\n ${sym.ok} 준비 완료. ${c.lime("baryon")} 으로 시작하세요.\n`);
117
+ log(`\n ${sym.ok} ${t("setup.done", { cmd: c.lime("baryon") })}\n`);
113
118
  return 0;
114
119
  }
115
120
 
116
121
  export async function doctor() {
117
122
  banner();
118
- log(c.bold(" 진단 (baryon doctor)\n"));
123
+ log(c.bold(` ${t("doctor.title")}\n`));
119
124
  let problems = 0;
120
125
 
121
126
  // node
122
127
  const major = Number(process.versions.node.split(".")[0]);
123
- if (major >= 22) ok(`Node.js ${process.version}`);
128
+ if (major >= 22) ok(t("doctor.node", { version: process.version }));
124
129
  else {
125
- err(`Node.js ${process.version} — 22 이상 필요`);
130
+ err(t("doctor.nodeOld", { version: process.version }));
126
131
  problems++;
127
132
  }
128
133
 
129
134
  // pi installed?
130
135
  const entry = resolvePiEntry();
131
- if (entry) ok(`${PI_PACKAGE} 설치됨`);
136
+ if (entry) ok(t("doctor.piInstalled", { pkg: PI_PACKAGE }));
132
137
  else {
133
- err(`${PI_PACKAGE} 미설치 npm install -g @baryonlabs/cli`);
138
+ err(t("doctor.piMissing", { pkg: PI_PACKAGE }));
134
139
  problems++;
135
140
  }
136
141
 
137
142
  // config?
138
- if (hasConfig()) ok(`config 존재 ${c.dim(BARYON_CONFIG)}`);
143
+ if (hasConfig()) ok(t("doctor.configFound", { file: c.dim(BARYON_CONFIG) }));
139
144
  else {
140
- warn("config 없음 — `baryon setup` 실행 필요");
145
+ warn(t("doctor.configMissing"));
141
146
  problems++;
142
147
  }
143
148
 
144
149
  const cfg = loadConfig();
145
- if (cfg.apiKey) ok(`API 설정됨 (${cfg.apiKey.slice(0, 4)}${"•".repeat(6)})`);
146
- else warn("API 키 없음");
150
+ if (cfg.apiKey) ok(t("doctor.apiKeySet", { masked: `${cfg.apiKey.slice(0, 4)}${"•".repeat(6)}` }));
151
+ else warn(t("doctor.apiKeyMissing"));
147
152
 
148
153
  // CLI version currency (best-effort; silent offline)
149
154
  const ver = await checkLatest();
150
155
  if (ver?.outdated) {
151
- warn(`CLI 구버전 ${ver.current} 최신 ${ver.latest}. baryon.ai 사용에 업데이트 필요 (\`baryon update\`)`);
156
+ warn(t("doctor.cliOutdated", { current: ver.current, latest: ver.latest }));
152
157
  problems++;
153
158
  } else if (ver) {
154
- ok(`CLI 최신 버전 (${ver.current})`);
159
+ ok(t("doctor.cliCurrent", { current: ver.current }));
155
160
  }
156
161
 
157
162
  // pi provider registered?
158
- if (piProviderConfigured()) ok(`pi 프로바이더 ${c.lime(PROVIDER)} 등록됨 → ${c.dim(PI_MODELS_JSON)}`);
163
+ if (piProviderConfigured()) ok(t("doctor.providerRegistered", { provider: c.lime(PROVIDER), file: c.dim(PI_MODELS_JSON) }));
159
164
  else {
160
- warn("pi 프로바이더 미등록 — `baryon setup` 실행");
165
+ warn(t("doctor.providerMissing"));
161
166
  problems++;
162
167
  }
163
168
 
@@ -166,26 +171,26 @@ export async function doctor() {
166
171
  fs.existsSync(path.join(PI_SKILLS_DIR, s.name, "SKILL.md")),
167
172
  ).length;
168
173
  if (haveSkills === DEFAULT_SKILLS.length)
169
- ok(`기본 스킬 ${haveSkills}/${DEFAULT_SKILLS.length} 설치됨 (pdf·pptx·xlsx·agent-browser)`);
174
+ ok(t("doctor.skillsAll", { have: haveSkills, total: DEFAULT_SKILLS.length }));
170
175
  else
171
- info(`기본 스킬 ${haveSkills}/${DEFAULT_SKILLS.length} — \`baryon skills\` 로 설치`);
176
+ info(t("doctor.skillsSome", { have: haveSkills, total: DEFAULT_SKILLS.length }));
172
177
 
173
178
  // agent-browser (web/ERP automation) — informational
174
179
  const ab = spawnSync("agent-browser", ["--version"], { encoding: "utf8" });
175
- if (ab.status === 0) ok(`agent-browser 설치됨 (${(ab.stdout || "").trim() || "ok"})`);
176
- else info("agent-browser 미설치 — `baryon setup`(자동) 또는 `npm i -g agent-browser`");
180
+ if (ab.status === 0) ok(t("doctor.browserInstalled", { version: (ab.stdout || "").trim() || "ok" }));
181
+ else info(t("doctor.browserMissing"));
177
182
 
178
183
  // connectivity
179
- info(`연결 확인 → ${c.dim(cfg.baseUrl)}`);
184
+ info(t("doctor.checkingConn", { url: c.dim(cfg.baseUrl) }));
180
185
  const r = await ping(cfg.baseUrl, cfg.apiKey);
181
- if (r.ok) ok(`baryon.ai 연결 정상 (HTTP ${r.status})`);
182
- else if (r.status) warn(`엔드포인트 응답 HTTP ${r.status} — 키/권한 확인`);
183
- else warn(`연결 불가 (${r.error}) — 오프라인이면 로컬 LLM 사용 가능`);
186
+ if (r.ok) ok(t("doctor.connOk", { status: r.status }));
187
+ else if (r.status) warn(t("doctor.connStatus", { status: r.status }));
188
+ else warn(t("doctor.connFail", { error: r.error }));
184
189
 
185
190
  log(
186
191
  problems === 0
187
- ? `\n ${sym.ok} ${c.teal("모든 점검 통과")}\n`
188
- : `\n ${sym.warn} ${c.yellow(`${problems}건 확인 필요`)}\n`,
192
+ ? `\n ${sym.ok} ${c.teal(t("doctor.allPass"))}\n`
193
+ : `\n ${sym.warn} ${c.yellow(t("doctor.problems", { count: problems }))}\n`,
189
194
  );
190
195
  return problems === 0 ? 0 : 1;
191
196
  }
@@ -198,33 +203,35 @@ export async function models(args) {
198
203
 
199
204
  export async function configCmd(args) {
200
205
  const flags = parseFlags(args);
201
- if (flags.key || flags["api-key"] || flags["base-url"] || flags.model) {
206
+ if (flags.key || flags["api-key"] || flags["base-url"] || flags.model || flags.lang) {
202
207
  const patch = {};
203
208
  if (flags.key || flags["api-key"]) patch.apiKey = flags.key || flags["api-key"];
204
209
  if (flags["base-url"]) patch.baseUrl = flags["base-url"];
205
210
  if (flags.model) patch.defaultModel = flags.model;
211
+ if (flags.lang) patch.lang = normalizeLocale(flags.lang);
206
212
  saveConfig(patch);
207
- ok("config 업데이트됨");
213
+ ok(t("config.updated"));
208
214
  return 0;
209
215
  }
210
216
  const cfg = loadConfig();
211
217
  banner();
212
- log(c.bold(" 현재 설정\n"));
213
- info(`base URL ${c.lime(cfg.baseUrl)}`);
214
- info(`default ${c.lime(cfg.defaultModel)}`);
215
- info(`API key ${cfg.apiKey ? cfg.apiKey.slice(0, 4) + "•".repeat(6) : c.dim("(없음)")}`);
216
- info(`키 관리 ${c.lime(KEYS_URL)}`);
217
- info(`config 파일 ${c.dim(BARYON_CONFIG)}`);
218
- info(`pi models ${c.dim(PI_MODELS_JSON)}`);
218
+ log(c.bold(` ${t("config.title")}\n`));
219
+ info(t("config.baseUrl", { url: c.lime(cfg.baseUrl) }));
220
+ info(t("config.default", { model: c.lime(cfg.defaultModel) }));
221
+ info(t("config.apiKey", { masked: cfg.apiKey ? c.lime(cfg.apiKey.slice(0, 4) + "•".repeat(6)) : c.dim(t("config.apiKeyNone")) }));
222
+ info(t("config.lang", { lang: c.lime(getLocale()) }));
223
+ info(t("config.keysMgmt", { url: c.lime(KEYS_URL) }));
224
+ info(t("config.file", { file: c.dim(BARYON_CONFIG) }));
225
+ info(t("config.piModels", { file: c.dim(PI_MODELS_JSON) }));
219
226
  log("");
220
227
  return 0;
221
228
  }
222
229
 
223
230
  export function keys() {
224
- log(` ${sym.info} 키 발급·관리 (vibecamp.us 대시보드):`);
231
+ log(` ${sym.info} ${t("keys.title")}`);
225
232
  log(` ${c.lime(KEYS_URL)}`);
226
- log(` ${c.dim(` 발급/회수/쿼터는 vibecamp.us 관리 · 형식 ${KEY_PREFIX}…`)}`);
227
- log(` ${c.dim(" 발급 후:")} ${c.lime("baryon setup")} ${c.dim("또는")} ${c.lime("baryon config --key vc_live_…")}`);
233
+ log(` ${c.dim(t("keys.sub", { prefix: KEY_PREFIX }))}`);
234
+ log(` ${c.dim(t("keys.after"))} ${c.lime("baryon setup")} ${c.dim(t("keys.or"))} ${c.lime("baryon config --key vc_live_…")}`);
228
235
  // best-effort open in browser
229
236
  const opener =
230
237
  process.platform === "darwin"
@@ -246,17 +253,32 @@ export function keys() {
246
253
 
247
254
  export function update() {
248
255
  return new Promise((resolve) => {
249
- log(` ${sym.info} 업데이트: ${c.lime(`npm install -g @baryonlabs/cli ${PI_PACKAGE}`)}\n`);
250
- const child = spawn(
251
- "npm",
252
- ["install", "-g", "@baryonlabs/cli", PI_PACKAGE],
253
- { stdio: "inherit", shell: process.platform === "win32" },
254
- );
255
- child.on("exit", (code) => resolve(code ?? 0));
256
- child.on("error", () => {
257
- err("npm 실행 실패 — 수동으로 위 명령을 실행하세요.");
256
+ // 1/2 — CLI + pi core via npm (gets the latest pi binary, e.g. 0.80.x).
257
+ log(` ${sym.info} ${t("update.stage1", { cmd: c.lime(`npm install -g @baryonlabs/cli ${PI_PACKAGE}`) })}\n`);
258
+ const npm = spawn("npm", ["install", "-g", "@baryonlabs/cli", PI_PACKAGE], {
259
+ stdio: "inherit",
260
+ shell: process.platform === "win32",
261
+ });
262
+
263
+ npm.on("error", () => {
264
+ err(t("update.npmFail"));
258
265
  resolve(1);
259
266
  });
267
+
268
+ npm.on("exit", (code) => {
269
+ // 2/2 — pi's own packages (pi-mcp-adapter, pi-dynamic-workflows, …) are
270
+ // managed by `pi update`, not npm. Run it so those notices clear too.
271
+ const entry = resolvePiEntry();
272
+ if (!entry) return resolve(code ?? 0);
273
+
274
+ log(`\n ${sym.info} ${t("update.stage2", { cmd: c.lime("pi update") })}\n`);
275
+ const pu = spawn(process.execPath, [entry, "update"], { stdio: "inherit" });
276
+ pu.on("error", () => resolve(code ?? 0));
277
+ pu.on("exit", () => {
278
+ log(`\n ${sym.ok} ${t("update.done", { hint: c.dim(t("update.doneHint")) })}`);
279
+ resolve(code ?? 0);
280
+ });
281
+ });
260
282
  });
261
283
  }
262
284
 
@@ -264,7 +286,7 @@ export function installDefaults() {
264
286
  const entry = resolvePiEntry();
265
287
 
266
288
  if (!entry) {
267
- warn(`${PI_PACKAGE} 미설치 확장 건너뜀`);
289
+ warn(t("ext.piMissing", { pkg: PI_PACKAGE }));
268
290
  return 0;
269
291
  }
270
292
 
@@ -272,9 +294,9 @@ export function installDefaults() {
272
294
  // (e.g. pi-search ↔ pi-web-fetch both registering `web_fetch`, which hard-fails
273
295
  // every run). Remove them from pi's registry + disk before (re)installing.
274
296
  const pruned = prunePiPackages(DEPRECATED_EXTENSIONS);
275
- if (pruned.length) warn(`충돌 확장 제거: ${pruned.join(", ")} (자가 치유)`);
297
+ if (pruned.length) warn(t("ext.pruned", { names: pruned.join(", ") }));
276
298
 
277
- log(` ${sym.info} 기본 확장 설치 중 (${DEFAULT_EXTENSIONS.length}종 · git clone, 잠시 걸립니다)…`);
299
+ log(` ${sym.info} ${t("ext.installing", { count: DEFAULT_EXTENSIONS.length })}`);
278
300
  let okc = 0;
279
301
 
280
302
  for (const e of DEFAULT_EXTENSIONS) {
@@ -291,14 +313,14 @@ export function installDefaults() {
291
313
  }
292
314
 
293
315
  if (status === 0) {
294
- ok(`${e.name} ${e.note}`);
316
+ ok(t("ext.itemOk", { name: e.name, note: e.note }));
295
317
  okc++;
296
318
  } else {
297
- warn(`${e.name} 설치 실패(네트워크/git 확인) — 건너뜀`);
319
+ warn(t("ext.itemFail", { name: e.name }));
298
320
  }
299
321
  }
300
322
 
301
- log(` ${sym.ok} 확장 ${okc}/${DEFAULT_EXTENSIONS.length} 설치`);
323
+ log(` ${sym.ok} ${t("ext.summary", { ok: okc, total: DEFAULT_EXTENSIONS.length })}`);
302
324
  return okc;
303
325
  }
304
326
 
@@ -312,11 +334,11 @@ export function installSkills() {
312
334
  let okc = DEFAULT_SKILLS.length - missing.length;
313
335
 
314
336
  if (missing.length === 0) {
315
- log(` ${sym.ok} 기본 스킬 ${DEFAULT_SKILLS.length}/${DEFAULT_SKILLS.length} (이미 설치됨)`);
337
+ log(` ${sym.ok} ${t("skills.alreadyAll", { total: DEFAULT_SKILLS.length })}`);
316
338
  return DEFAULT_SKILLS.length;
317
339
  }
318
340
 
319
- log(` ${sym.info} 기본 스킬 설치 중 (${missing.length})…`);
341
+ log(` ${sym.info} ${t("skills.installing", { count: missing.length })}`);
320
342
  fs.mkdirSync(PI_SKILLS_DIR, { recursive: true });
321
343
 
322
344
  // Bundled skills ship inside this package under ../skills/<name>/.
@@ -325,12 +347,12 @@ export function installSkills() {
325
347
  const copySkill = (srcDir, s) => {
326
348
  const dst = path.join(PI_SKILLS_DIR, s.name);
327
349
  if (!fs.existsSync(path.join(srcDir, "SKILL.md"))) {
328
- warn(`${s.name} SKILL.md 없음(${srcDir}), 건너뜀`);
350
+ warn(t("skills.noSkillMd", { name: s.name, dir: srcDir }));
329
351
  return false;
330
352
  }
331
353
  fs.rmSync(dst, { recursive: true, force: true });
332
354
  fs.cpSync(srcDir, dst, { recursive: true });
333
- ok(`skill: ${s.name} ${s.note}`);
355
+ ok(t("skills.itemOk", { name: s.name, note: s.note }));
334
356
  return true;
335
357
  };
336
358
 
@@ -339,7 +361,7 @@ export function installSkills() {
339
361
  try {
340
362
  if (copySkill(path.join(bundledRoot, s.name), s)) okc++;
341
363
  } catch (e) {
342
- warn(`${s.name} 스킬 설치 실패 — 건너뜀 (${e.message})`);
364
+ warn(t("skills.itemFail", { name: s.name, error: e.message }));
343
365
  }
344
366
  }
345
367
 
@@ -361,13 +383,13 @@ export function installSkills() {
361
383
  }
362
384
 
363
385
  if (!cloned) {
364
- warn("스킬 저장소 clone 실패(네트워크/git 확인) — 일부 스킬 건너뜀");
386
+ warn(t("skills.cloneFail"));
365
387
  } else {
366
388
  for (const s of repoSkills) {
367
389
  try {
368
390
  if (copySkill(path.join(tmp, ...s.subdir.split("/")), s)) okc++;
369
391
  } catch (e) {
370
- warn(`${s.name} 스킬 설치 실패 — 건너뜀 (${e.message})`);
392
+ warn(t("skills.itemFail", { name: s.name, error: e.message }));
371
393
  }
372
394
  }
373
395
  }
@@ -376,7 +398,7 @@ export function installSkills() {
376
398
  }
377
399
  }
378
400
 
379
- log(` ${sym.ok} 기본 스킬 ${okc}/${DEFAULT_SKILLS.length} 설치 ${PI_SKILLS_DIR}`);
401
+ log(` ${sym.ok} ${t("skills.summary", { ok: okc, total: DEFAULT_SKILLS.length, dir: PI_SKILLS_DIR })}`);
380
402
  return okc;
381
403
  }
382
404
 
@@ -388,11 +410,11 @@ export function installBrowser() {
388
410
  // Already present?
389
411
  const probe = spawnSync("agent-browser", ["--version"], { encoding: "utf8" });
390
412
  if (probe.status === 0) {
391
- ok(`agent-browser 설치됨 (${(probe.stdout || "").trim() || "ok"})`);
413
+ ok(t("browser.installed", { version: (probe.stdout || "").trim() || "ok" }));
392
414
  return true;
393
415
  }
394
416
 
395
- log(` ${sym.info} agent-browser 설치 중 (웹/ERP 자동화 · 최초 1회, 브라우저 다운로드 포함)…`);
417
+ log(` ${sym.info} ${t("browser.installing")}`);
396
418
 
397
419
  let ok1 = false;
398
420
  for (let attempt = 1; attempt <= 2 && !ok1; attempt++) {
@@ -402,14 +424,14 @@ export function installBrowser() {
402
424
  }
403
425
 
404
426
  if (!ok1) {
405
- warn("agent-browser 설치 실패(네트워크/npm 확인) — 건너뜀. 나중에 `npm i -g agent-browser && agent-browser install`");
427
+ warn(t("browser.installFail"));
406
428
  return false;
407
429
  }
408
430
 
409
431
  // Download the browser engine (Chrome for Testing). Best-effort.
410
432
  const inst = spawnSync("agent-browser", ["install"], { encoding: "utf8", stdio: "ignore" });
411
- if (inst.status === 0) ok("agent-browser — 웹/ERP 브라우저 자동화 준비됨");
412
- else warn("agent-browser 바이너리는 설치됨 · 브라우저 다운로드 미완 — 최초 사용 시 `agent-browser install`");
433
+ if (inst.status === 0) ok(t("browser.ready"));
434
+ else warn(t("browser.engineMissing"));
413
435
 
414
436
  return true;
415
437
  }
@@ -422,9 +444,9 @@ export function extensions(args) {
422
444
  }
423
445
 
424
446
  banner();
425
- log(c.bold(" Baryon 기본 확장\n"));
447
+ log(c.bold(` ${t("ext.bannerTitle")}\n`));
426
448
  installDefaults()
427
- log(`\n ${sym.info} 목록: ${c.lime("baryon extensions list")} · 제거: ${c.lime("baryon -- remove <src>")}\n`)
449
+ log(`\n ${sym.info} ${t("ext.footer", { list: c.lime("baryon extensions list"), remove: c.lime("baryon -- remove <src>") })}\n`)
428
450
  return 0;
429
451
  }
430
452
 
@@ -434,45 +456,46 @@ export function skills(args) {
434
456
  banner();
435
457
 
436
458
  if (sub === "list" || sub === "ls") {
437
- log(c.bold(" Baryon 기본 스킬 팩\n"));
459
+ log(c.bold(` ${t("skills.bannerListTitle")}\n`));
438
460
  for (const s of DEFAULT_SKILLS) {
439
461
  const here = fs.existsSync(path.join(PI_SKILLS_DIR, s.name, "SKILL.md"));
440
- log(` ${here ? sym.ok : sym.info} ${c.lime(s.name)} — ${s.note} ${here ? "" : c.dim("(미설치)")}`);
462
+ log(` ${here ? sym.ok : sym.info} ${c.lime(s.name)} — ${s.note} ${here ? "" : c.dim(t("skills.notInstalled"))}`);
441
463
  }
442
- log(`\n ${sym.info} 설치/동기화: ${c.lime("baryon skills")} · 위치: ${c.dim(PI_SKILLS_DIR)}\n`);
464
+ log(`\n ${sym.info} ${t("skills.listFooter", { cmd: c.lime("baryon skills"), dir: c.dim(PI_SKILLS_DIR) })}\n`);
443
465
  return 0;
444
466
  }
445
467
 
446
- log(c.bold(" Baryon 기본 스킬 설치\n"));
468
+ log(c.bold(` ${t("skills.bannerInstallTitle")}\n`));
447
469
  installSkills();
448
- log(`\n ${sym.info} 사용: pi 에이전트에서 ${c.lime("/skill pdf")} 처럼 호출 · 목록: ${c.lime("baryon skills list")}\n`);
470
+ log(`\n ${sym.info} ${t("skills.installFooter", { call: c.lime("/skill pdf"), list: c.lime("baryon skills list") })}\n`);
449
471
  return 0;
450
472
  }
451
473
 
452
474
  export function help() {
453
475
  banner();
454
476
  log(`${c.bold("USAGE")}
455
- ${c.lime("baryon")} ${c.dim("[options] [@files...] [messages...]")} 코딩 에이전트 시작 (baryon.ai 기본)
477
+ ${c.lime("baryon")} ${c.dim("[options] [@files...] [messages...]")} ${t("help.usageDesc")}
456
478
 
457
479
  ${c.bold("COMMANDS")}
458
- ${c.lime("baryon setup")} baryon.ai API 키 등록 + pi 프로바이더 구성
459
- ${c.lime("baryon keys")} 발급·관리 대시보드 열기 ${c.dim("(vibecamp.us)")}
460
- ${c.lime("baryon config")} 현재 설정 보기 ${c.dim("(--key/--base-url/--model 로 변경)")}
461
- ${c.lime("baryon models")} 사용 가능한 모델 목록
462
- ${c.lime("baryon extensions")} 기본 확장 설치(서브에이전트·캔버스·셸·웹) ${c.dim("· list 로 목록")}
463
- ${c.lime("baryon skills")} 기본 스킬 설치(pdf·pptx·xlsx) ${c.dim("· list 로 목록")}
464
- ${c.lime("baryon doctor")} 설치·연결 진단
465
- ${c.lime("baryon update")} CLI + pi 에이전트 업데이트
466
- ${c.lime("baryon help")} 이 도움말
480
+ ${c.lime("baryon setup")} ${t("help.cmd.setup")}
481
+ ${c.lime("baryon keys")} ${t("help.cmd.keys")} ${c.dim(t("help.cmd.keysNote"))}
482
+ ${c.lime("baryon config")} ${t("help.cmd.config")} ${c.dim(t("help.cmd.configNote"))}
483
+ ${c.lime("baryon models")} ${t("help.cmd.models")}
484
+ ${c.lime("baryon extensions")} ${t("help.cmd.extensions")} ${c.dim(t("help.cmd.extensionsNote"))}
485
+ ${c.lime("baryon skills")} ${t("help.cmd.skills")} ${c.dim(t("help.cmd.skillsNote"))}
486
+ ${c.lime("baryon doctor")} ${t("help.cmd.doctor")}
487
+ ${c.lime("baryon update")} ${t("help.cmd.update")}
488
+ ${c.lime("baryon help")} ${t("help.cmd.help")}
467
489
 
468
490
  ${c.bold("EXAMPLES")}
469
- ${c.dim("$")} baryon ${c.dim("# 대화형 시작")}
470
- ${c.dim("$")} baryon -p "CSV 분석해 차트 만들어줘" ${c.dim("# 단발 실행")}
471
- ${c.dim("$")} baryon --provider openai ${c.dim("# 다른 모델로 전환·비교")}
472
- ${c.dim("$")} baryon --list-models ${c.dim("# pi 패스스루")}
473
-
474
- ${c.dim(`그 모든 옵션은 pi 에이전트로 그대로 전달됩니다.`)}
475
- ${c.dim(`문서: ${HOMEPAGE} · 문의: ${SUPPORT_EMAIL}`)}
491
+ ${c.dim("$")} baryon ${c.dim(t("help.ex.interactive"))}
492
+ ${c.dim("$")} baryon -p "${t("help.ex.oneShotMsg")}" ${c.dim(t("help.ex.oneShot"))}
493
+ ${c.dim("$")} baryon --provider openai ${c.dim(t("help.ex.switch"))}
494
+ ${c.dim("$")} baryon --list-models ${c.dim(t("help.ex.passthrough"))}
495
+
496
+ ${c.dim(t("help.lang", { env: "BARYON_LANG", cmd: "baryon config --lang" }))}
497
+ ${c.dim(t("help.passthrough"))}
498
+ ${c.dim(t("help.docs", { homepage: HOMEPAGE, email: SUPPORT_EMAIL }))}
476
499
  `);
477
500
  return 0;
478
501
  }
@@ -480,8 +503,8 @@ ${c.dim(`문서: ${HOMEPAGE} · 문의: ${SUPPORT_EMAIL}`)}
480
503
  /** Quiet first-run hint shown by postinstall (never fails the install). */
481
504
  export function welcome() {
482
505
  if (!process.stdout.isTTY) return 0;
483
- log(`\n${c.lime("✔")} ${c.bold("@baryonlabs/cli")} 설치 완료`);
484
- log(` ${c.dim("다음 단계:")} ${c.lime("baryon setup")} ${c.dim("→")} ${c.lime("baryon")}`);
506
+ log(`\n${c.lime("✔")} ${t("welcome.installed", { pkg: c.bold("@baryonlabs/cli") })}`);
507
+ log(` ${c.dim(t("welcome.nextLabel"))} ${c.lime("baryon setup")} ${c.dim("→")} ${c.lime("baryon")}`);
485
508
  log(` ${c.dim(HOMEPAGE)}\n`);
486
509
  return 0;
487
510
  }
package/src/i18n.js ADDED
@@ -0,0 +1,359 @@
1
+ // Tiny zero-dependency ko/en i18n layer for OUR user-facing CLI messages.
2
+ //
3
+ // import { t } from "./i18n.js";
4
+ // t("setup.saved", { file }); // → localized, interpolated string
5
+ //
6
+ // Locale resolution (first hit wins): BARYON_LANG env → `lang` in
7
+ // ~/.baryon/config.json → "ko". Korean is the default & the fallback for any
8
+ // missing key/locale. Resolution NEVER throws.
9
+ import fs from "node:fs";
10
+ import { BARYON_CONFIG } from "./constants.js";
11
+
12
+ /** Normalize "ko", "ko-KR", "en", "en-US", "EN" … → "ko" | "en". Unknown → "ko". */
13
+ export function normalizeLocale(value) {
14
+ const v = String(value || "").trim().toLowerCase();
15
+ if (v.startsWith("en")) return "en";
16
+ if (v.startsWith("ko")) return "ko";
17
+ return "ko";
18
+ }
19
+
20
+ /** Read `lang` from ~/.baryon/config.json defensively (never throws). */
21
+ function langFromConfig() {
22
+ try {
23
+ const cfg = JSON.parse(fs.readFileSync(BARYON_CONFIG, "utf8"));
24
+ return cfg && typeof cfg.lang === "string" ? cfg.lang : "";
25
+ } catch {
26
+ return "";
27
+ }
28
+ }
29
+
30
+ let override = null; // explicit setLocale() wins over env/config when set.
31
+
32
+ /** Resolve the active locale: explicit override → env → config → "ko". */
33
+ export function getLocale() {
34
+ if (override) return override;
35
+ if (process.env.BARYON_LANG) return normalizeLocale(process.env.BARYON_LANG);
36
+ const fromCfg = langFromConfig();
37
+ if (fromCfg) return normalizeLocale(fromCfg);
38
+ return "ko";
39
+ }
40
+
41
+ /** Force a locale for the current process (mainly for tests / programmatic use). */
42
+ export function setLocale(value) {
43
+ override = value ? normalizeLocale(value) : null;
44
+ return override;
45
+ }
46
+
47
+ const MESSAGES = {
48
+ ko: {
49
+ // ── setup ────────────────────────────────────────────────────────────
50
+ "setup.title": "baryon.ai 연결 설정",
51
+ "setup.keyHint": "키 발급·관리: {url}",
52
+ "setup.keyHintSub": " (vibecamp.us 대시보드에서 발급/회수 · 형식 {prefix}…)",
53
+ "setup.keyPrompt": "baryon.ai API key: ",
54
+ "setup.noKey": "API 키 없이 저장합니다. 나중에 `baryon setup --key <KEY>` 로 추가하세요.",
55
+ "setup.badKeyFormat": "키 형식이 {prefix}… 가 아닙니다. 로컬/상용 키라면 무시하세요.",
56
+ "setup.configSaved": "config 저장 → {file}",
57
+ "setup.discovering": "모델 목록을 조회하는 중…",
58
+ "setup.modelsFound": "{count}개 모델 발견 ({list}{more})",
59
+ "setup.modelsFailed": "모델 자동 조회 실패 — 기본 모델 {count}개로 구성 (오프라인/폐쇄망 정상)",
60
+ "setup.providerConfigured": "pi 프로바이더 {provider} 구성 → {file}",
61
+ "setup.skipExtensions": "기본 확장 설치 건너뜀 (--no-extensions)",
62
+ "setup.skipSkills": "기본 스킬 설치 건너뜀 (--no-skills)",
63
+ "setup.skipBrowser": "agent-browser 설치 건너뜀 (--no-browser)",
64
+ "setup.done": "준비 완료. {cmd} 으로 시작하세요.",
65
+
66
+ // ── doctor ───────────────────────────────────────────────────────────
67
+ "doctor.title": "진단 (baryon doctor)",
68
+ "doctor.node": "Node.js {version}",
69
+ "doctor.nodeOld": "Node.js {version} — 22 이상 필요",
70
+ "doctor.piInstalled": "{pkg} 설치됨",
71
+ "doctor.piMissing": "{pkg} 미설치 — npm install -g @baryonlabs/cli",
72
+ "doctor.configFound": "config 존재 → {file}",
73
+ "doctor.configMissing": "config 없음 — `baryon setup` 실행 필요",
74
+ "doctor.apiKeySet": "API 키 설정됨 ({masked})",
75
+ "doctor.apiKeyMissing": "API 키 없음",
76
+ "doctor.cliOutdated": "CLI 구버전 {current} — 최신 {latest}. baryon.ai 사용에 업데이트 필요 (`baryon update`)",
77
+ "doctor.cliCurrent": "CLI 최신 버전 ({current})",
78
+ "doctor.providerRegistered": "pi 프로바이더 {provider} 등록됨 → {file}",
79
+ "doctor.providerMissing": "pi 프로바이더 미등록 — `baryon setup` 실행",
80
+ "doctor.skillsAll": "기본 스킬 {have}/{total} 설치됨 (pdf·pptx·xlsx·agent-browser)",
81
+ "doctor.skillsSome": "기본 스킬 {have}/{total} — `baryon skills` 로 설치",
82
+ "doctor.browserInstalled": "agent-browser 설치됨 ({version})",
83
+ "doctor.browserMissing": "agent-browser 미설치 — `baryon setup`(자동) 또는 `npm i -g agent-browser`",
84
+ "doctor.checkingConn": "연결 확인 중 → {url}",
85
+ "doctor.connOk": "baryon.ai 연결 정상 (HTTP {status})",
86
+ "doctor.connStatus": "엔드포인트 응답 HTTP {status} — 키/권한 확인",
87
+ "doctor.connFail": "연결 불가 ({error}) — 오프라인이면 로컬 LLM 사용 가능",
88
+ "doctor.allPass": "모든 점검 통과",
89
+ "doctor.problems": "{count}건 확인 필요",
90
+
91
+ // ── config ───────────────────────────────────────────────────────────
92
+ "config.updated": "config 업데이트됨",
93
+ "config.title": "현재 설정",
94
+ "config.baseUrl": "base URL {url}",
95
+ "config.default": "default {model}",
96
+ "config.apiKey": "API key {masked}",
97
+ "config.apiKeyNone": "(없음)",
98
+ "config.lang": "language {lang}",
99
+ "config.keysMgmt": "키 관리 {url}",
100
+ "config.file": "config 파일 {file}",
101
+ "config.piModels": "pi models {file}",
102
+
103
+ // ── keys ─────────────────────────────────────────────────────────────
104
+ "keys.title": "키 발급·관리 (vibecamp.us 대시보드):",
105
+ "keys.sub": " 발급/회수/쿼터는 vibecamp.us 가 관리 · 형식 {prefix}…",
106
+ "keys.after": " 발급 후:",
107
+ "keys.or": "또는",
108
+
109
+ // ── update ───────────────────────────────────────────────────────────
110
+ "update.stage1": "1/2 CLI·코어 업데이트: {cmd}",
111
+ "update.npmFail": "npm 실행 실패 — 수동으로 위 명령을 실행하세요.",
112
+ "update.stage2": "2/2 pi 패키지 업데이트: {cmd}",
113
+ "update.done": "업데이트 완료. {hint}",
114
+ "update.doneHint": "baryon doctor 로 점검 가능",
115
+
116
+ // ── extensions install ───────────────────────────────────────────────
117
+ "ext.piMissing": "{pkg} 미설치 — 확장 건너뜀",
118
+ "ext.pruned": "충돌 확장 제거: {names} (자가 치유)",
119
+ "ext.installing": "기본 확장 설치 중 ({count}종 · git clone, 잠시 걸립니다)…",
120
+ "ext.itemOk": "{name} — {note}",
121
+ "ext.itemFail": "{name} 설치 실패(네트워크/git 확인) — 건너뜀",
122
+ "ext.summary": "확장 {ok}/{total} 설치",
123
+ "ext.bannerTitle": "Baryon 기본 확장",
124
+ "ext.footer": "목록: {list} · 제거: {remove}",
125
+
126
+ // ── skills install ───────────────────────────────────────────────────
127
+ "skills.alreadyAll": "기본 스킬 {total}/{total} (이미 설치됨)",
128
+ "skills.installing": "기본 스킬 설치 중 ({count}종)…",
129
+ "skills.noSkillMd": "{name} — SKILL.md 없음({dir}), 건너뜀",
130
+ "skills.itemOk": "skill: {name} — {note}",
131
+ "skills.itemFail": "{name} 스킬 설치 실패 — 건너뜀 ({error})",
132
+ "skills.cloneFail": "스킬 저장소 clone 실패(네트워크/git 확인) — 일부 스킬 건너뜀",
133
+ "skills.summary": "기본 스킬 {ok}/{total} 설치 → {dir}",
134
+ "skills.bannerListTitle": "Baryon 기본 스킬 팩",
135
+ "skills.notInstalled": "(미설치)",
136
+ "skills.listFooter": "설치/동기화: {cmd} · 위치: {dir}",
137
+ "skills.bannerInstallTitle": "Baryon 기본 스킬 설치",
138
+ "skills.installFooter": "사용: pi 에이전트에서 {call} 처럼 호출 · 목록: {list}",
139
+
140
+ // ── browser install ──────────────────────────────────────────────────
141
+ "browser.installed": "agent-browser 설치됨 ({version})",
142
+ "browser.installing": "agent-browser 설치 중 (웹/ERP 자동화 · 최초 1회, 브라우저 다운로드 포함)…",
143
+ "browser.installFail": "agent-browser 설치 실패(네트워크/npm 확인) — 건너뜀. 나중에 `npm i -g agent-browser && agent-browser install`",
144
+ "browser.ready": "agent-browser — 웹/ERP 브라우저 자동화 준비됨",
145
+ "browser.engineMissing": "agent-browser 바이너리는 설치됨 · 브라우저 다운로드 미완 — 최초 사용 시 `agent-browser install`",
146
+
147
+ // ── help ─────────────────────────────────────────────────────────────
148
+ "help.usageDesc": "코딩 에이전트 시작 (baryon.ai 기본)",
149
+ "help.cmd.setup": "baryon.ai API 키 등록 + pi 프로바이더 구성",
150
+ "help.cmd.keys": "키 발급·관리 대시보드 열기",
151
+ "help.cmd.keysNote": "(vibecamp.us)",
152
+ "help.cmd.config": "현재 설정 보기",
153
+ "help.cmd.configNote": "(--key/--base-url/--model/--lang 로 변경)",
154
+ "help.cmd.models": "사용 가능한 모델 목록",
155
+ "help.cmd.extensions": "기본 확장 설치(서브에이전트·캔버스·셸·웹)",
156
+ "help.cmd.extensionsNote": "· list 로 목록",
157
+ "help.cmd.skills": "기본 스킬 설치(pdf·pptx·xlsx)",
158
+ "help.cmd.skillsNote": "· list 로 목록",
159
+ "help.cmd.doctor": "설치·연결 진단",
160
+ "help.cmd.update": "CLI + pi 에이전트 업데이트",
161
+ "help.cmd.help": "이 도움말",
162
+ "help.ex.interactive": "# 대화형 시작",
163
+ "help.ex.oneShot": "# 단발 실행",
164
+ "help.ex.oneShotMsg": "CSV 분석해 차트 만들어줘",
165
+ "help.ex.switch": "# 다른 모델로 전환·비교",
166
+ "help.ex.passthrough": "# pi 패스스루",
167
+ "help.lang": "언어 설정: 환경변수 {env} 또는 {cmd} (ko·en)",
168
+ "help.passthrough": "그 외 모든 옵션은 pi 에이전트로 그대로 전달됩니다.",
169
+ "help.docs": "문서: {homepage} · 문의: {email}",
170
+
171
+ // ── welcome (postinstall) ────────────────────────────────────────────
172
+ "welcome.installed": "{pkg} 설치 완료",
173
+ "welcome.nextLabel": "다음 단계:",
174
+
175
+ // ── runtime / bin ────────────────────────────────────────────────────
176
+ "bin.outdated": "업데이트 필요: @baryonlabs/cli {current} → {latest}",
177
+ "bin.outdatedSub": "baryon.ai 사용에 최신 버전이 필요합니다.",
178
+ "bin.outdatedRun": "를 실행하세요.",
179
+ "pi.notInstalled": "{pkg} is not installed. Run: npm install -g @baryonlabs/cli",
180
+ "bin.piVersion": "pi {version}",
181
+ "bin.unconfiguredTTY": "아직 설정되지 않았습니다.",
182
+ "bin.unconfiguredTTYRun": "을 먼저 실행합니다.",
183
+ "bin.unconfigured": "설정이 없습니다.",
184
+ "bin.unconfiguredRun": "을 먼저 실행하세요.",
185
+ "bin.prunedConflicts": "충돌 확장 정리됨: {names}",
186
+ "bin.room": "🏫 {project} · 좌석 {seat}",
187
+ "bin.roomInactive": "🚫 {project} (비활성) · 좌석 {seat}",
188
+ },
189
+
190
+ en: {
191
+ // ── setup ────────────────────────────────────────────────────────────
192
+ "setup.title": "baryon.ai connection setup",
193
+ "setup.keyHint": "Issue / manage keys: {url}",
194
+ "setup.keyHintSub": " (issue/revoke at the vibecamp.us dashboard · format {prefix}…)",
195
+ "setup.keyPrompt": "baryon.ai API key: ",
196
+ "setup.noKey": "Saving without an API key. Add one later with `baryon setup --key <KEY>`.",
197
+ "setup.badKeyFormat": "Key does not match {prefix}… — ignore this if it's a local/commercial key.",
198
+ "setup.configSaved": "config saved → {file}",
199
+ "setup.discovering": "Fetching model list…",
200
+ "setup.modelsFound": "Found {count} models ({list}{more})",
201
+ "setup.modelsFailed": "Model auto-discovery failed — using {count} default models (normal when offline/air-gapped)",
202
+ "setup.providerConfigured": "pi provider {provider} configured → {file}",
203
+ "setup.skipExtensions": "Skipping default extensions (--no-extensions)",
204
+ "setup.skipSkills": "Skipping default skills (--no-skills)",
205
+ "setup.skipBrowser": "Skipping agent-browser (--no-browser)",
206
+ "setup.done": "All set. Start with {cmd}.",
207
+
208
+ // ── doctor ───────────────────────────────────────────────────────────
209
+ "doctor.title": "Diagnostics (baryon doctor)",
210
+ "doctor.node": "Node.js {version}",
211
+ "doctor.nodeOld": "Node.js {version} — 22 or newer required",
212
+ "doctor.piInstalled": "{pkg} installed",
213
+ "doctor.piMissing": "{pkg} not installed — npm install -g @baryonlabs/cli",
214
+ "doctor.configFound": "config found → {file}",
215
+ "doctor.configMissing": "config missing — run `baryon setup`",
216
+ "doctor.apiKeySet": "API key set ({masked})",
217
+ "doctor.apiKeyMissing": "No API key",
218
+ "doctor.cliOutdated": "CLI outdated {current} — latest {latest}. Update required to use baryon.ai (`baryon update`)",
219
+ "doctor.cliCurrent": "CLI up to date ({current})",
220
+ "doctor.providerRegistered": "pi provider {provider} registered → {file}",
221
+ "doctor.providerMissing": "pi provider not registered — run `baryon setup`",
222
+ "doctor.skillsAll": "Default skills {have}/{total} installed (pdf·pptx·xlsx·agent-browser)",
223
+ "doctor.skillsSome": "Default skills {have}/{total} — install with `baryon skills`",
224
+ "doctor.browserInstalled": "agent-browser installed ({version})",
225
+ "doctor.browserMissing": "agent-browser not installed — `baryon setup` (auto) or `npm i -g agent-browser`",
226
+ "doctor.checkingConn": "Checking connection → {url}",
227
+ "doctor.connOk": "baryon.ai reachable (HTTP {status})",
228
+ "doctor.connStatus": "Endpoint returned HTTP {status} — check key/permissions",
229
+ "doctor.connFail": "Cannot connect ({error}) — a local LLM works if you're offline",
230
+ "doctor.allPass": "All checks passed",
231
+ "doctor.problems": "{count} item(s) need attention",
232
+
233
+ // ── config ───────────────────────────────────────────────────────────
234
+ "config.updated": "config updated",
235
+ "config.title": "Current settings",
236
+ "config.baseUrl": "base URL {url}",
237
+ "config.default": "default {model}",
238
+ "config.apiKey": "API key {masked}",
239
+ "config.apiKeyNone": "(none)",
240
+ "config.lang": "language {lang}",
241
+ "config.keysMgmt": "key mgmt {url}",
242
+ "config.file": "config file {file}",
243
+ "config.piModels": "pi models {file}",
244
+
245
+ // ── keys ─────────────────────────────────────────────────────────────
246
+ "keys.title": "Issue / manage keys (vibecamp.us dashboard):",
247
+ "keys.sub": " issuance/revocation/quota managed by vibecamp.us · format {prefix}…",
248
+ "keys.after": " after issuing:",
249
+ "keys.or": "or",
250
+
251
+ // ── update ───────────────────────────────────────────────────────────
252
+ "update.stage1": "1/2 update CLI & core: {cmd}",
253
+ "update.npmFail": "npm failed — run the command above manually.",
254
+ "update.stage2": "2/2 update pi packages: {cmd}",
255
+ "update.done": "Update complete. {hint}",
256
+ "update.doneHint": "run baryon doctor to verify",
257
+
258
+ // ── extensions install ───────────────────────────────────────────────
259
+ "ext.piMissing": "{pkg} not installed — skipping extensions",
260
+ "ext.pruned": "Removed conflicting extensions: {names} (self-heal)",
261
+ "ext.installing": "Installing default extensions ({count} · git clone, may take a moment)…",
262
+ "ext.itemOk": "{name} — {note}",
263
+ "ext.itemFail": "{name} install failed (check network/git) — skipped",
264
+ "ext.summary": "Extensions {ok}/{total} installed",
265
+ "ext.bannerTitle": "Baryon default extensions",
266
+ "ext.footer": "list: {list} · remove: {remove}",
267
+
268
+ // ── skills install ───────────────────────────────────────────────────
269
+ "skills.alreadyAll": "Default skills {total}/{total} (already installed)",
270
+ "skills.installing": "Installing default skills ({count})…",
271
+ "skills.noSkillMd": "{name} — no SKILL.md ({dir}), skipped",
272
+ "skills.itemOk": "skill: {name} — {note}",
273
+ "skills.itemFail": "{name} skill install failed — skipped ({error})",
274
+ "skills.cloneFail": "Skill repo clone failed (check network/git) — some skills skipped",
275
+ "skills.summary": "Default skills {ok}/{total} installed → {dir}",
276
+ "skills.bannerListTitle": "Baryon default skills pack",
277
+ "skills.notInstalled": "(not installed)",
278
+ "skills.listFooter": "install/sync: {cmd} · location: {dir}",
279
+ "skills.bannerInstallTitle": "Baryon default skills install",
280
+ "skills.installFooter": "use: invoke like {call} in the pi agent · list: {list}",
281
+
282
+ // ── browser install ──────────────────────────────────────────────────
283
+ "browser.installed": "agent-browser installed ({version})",
284
+ "browser.installing": "Installing agent-browser (web/ERP automation · first run, includes browser download)…",
285
+ "browser.installFail": "agent-browser install failed (check network/npm) — skipped. Later: `npm i -g agent-browser && agent-browser install`",
286
+ "browser.ready": "agent-browser — web/ERP browser automation ready",
287
+ "browser.engineMissing": "agent-browser binary installed · browser download incomplete — on first use run `agent-browser install`",
288
+
289
+ // ── help ─────────────────────────────────────────────────────────────
290
+ "help.usageDesc": "Start the coding agent (baryon.ai by default)",
291
+ "help.cmd.setup": "Register baryon.ai API key + configure pi provider",
292
+ "help.cmd.keys": "Open the key issuance/management dashboard",
293
+ "help.cmd.keysNote": "(vibecamp.us)",
294
+ "help.cmd.config": "Show current settings",
295
+ "help.cmd.configNote": "(change with --key/--base-url/--model/--lang)",
296
+ "help.cmd.models": "List available models",
297
+ "help.cmd.extensions": "Install default extensions (sub-agents·canvas·shell·web)",
298
+ "help.cmd.extensionsNote": "· list to view",
299
+ "help.cmd.skills": "Install default skills (pdf·pptx·xlsx)",
300
+ "help.cmd.skillsNote": "· list to view",
301
+ "help.cmd.doctor": "Diagnose install & connectivity",
302
+ "help.cmd.update": "Update CLI + pi agent",
303
+ "help.cmd.help": "This help",
304
+ "help.ex.interactive": "# interactive start",
305
+ "help.ex.oneShot": "# one-shot run",
306
+ "help.ex.oneShotMsg": "analyze the CSV and make a chart",
307
+ "help.ex.switch": "# switch/compare other models",
308
+ "help.ex.passthrough": "# pi passthrough",
309
+ "help.lang": "Language: env var {env} or {cmd} (ko·en)",
310
+ "help.passthrough": "All other options are passed straight through to the pi agent.",
311
+ "help.docs": "Docs: {homepage} · Support: {email}",
312
+
313
+ // ── welcome (postinstall) ────────────────────────────────────────────
314
+ "welcome.installed": "{pkg} installed",
315
+ "welcome.nextLabel": "Next:",
316
+
317
+ // ── runtime / bin ────────────────────────────────────────────────────
318
+ "bin.outdated": "Update required: @baryonlabs/cli {current} → {latest}",
319
+ "bin.outdatedSub": "baryon.ai requires the latest version.",
320
+ "bin.outdatedRun": "to update.",
321
+ "pi.notInstalled": "{pkg} is not installed. Run: npm install -g @baryonlabs/cli",
322
+ "bin.piVersion": "pi {version}",
323
+ "bin.unconfiguredTTY": "Not configured yet.",
324
+ "bin.unconfiguredTTYRun": "will run first.",
325
+ "bin.unconfigured": "No configuration found.",
326
+ "bin.unconfiguredRun": "first.",
327
+ "bin.prunedConflicts": "Cleaned up conflicting extensions: {names}",
328
+ "bin.room": "🏫 {project} · seat {seat}",
329
+ "bin.roomInactive": "🚫 {project} (inactive) · seat {seat}",
330
+ },
331
+ };
332
+
333
+ /** Interpolate {name}-style placeholders from `vars`. Missing → left as-is. */
334
+ function interpolate(str, vars) {
335
+ return str.replace(/\{(\w+)\}/g, (m, k) =>
336
+ Object.prototype.hasOwnProperty.call(vars, k) ? String(vars[k]) : m,
337
+ );
338
+ }
339
+
340
+ /**
341
+ * Look up `key` for the active locale, interpolate `vars`, and fall back to
342
+ * Korean (then to the key string itself) if missing. Never throws.
343
+ */
344
+ export function t(key, vars = {}) {
345
+ let locale;
346
+ try {
347
+ locale = getLocale();
348
+ } catch {
349
+ locale = "ko";
350
+ }
351
+ const fromLocale = MESSAGES[locale]?.[key];
352
+ const fromKo = MESSAGES.ko[key];
353
+ const template = fromLocale != null ? fromLocale : fromKo != null ? fromKo : key;
354
+ try {
355
+ return interpolate(template, vars || {});
356
+ } catch {
357
+ return template;
358
+ }
359
+ }
package/src/pi.js CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  CLIENT_VERSION,
17
17
  } from "./constants.js";
18
18
  import { ensurePiSessionHeader } from "./config.js";
19
+ import { t } from "./i18n.js";
19
20
 
20
21
  const require = createRequire(import.meta.url);
21
22
 
@@ -84,11 +85,7 @@ function userOverridesTargeting(args) {
84
85
  export function runPi(args, config, { injectTargeting = true } = {}) {
85
86
  const entry = resolvePiEntry();
86
87
  if (!entry) {
87
- return Promise.reject(
88
- new Error(
89
- `${PI_PACKAGE} is not installed. Run: npm install -g @baryonlabs/cli`,
90
- ),
91
- );
88
+ return Promise.reject(new Error(t("pi.notInstalled", { pkg: PI_PACKAGE })));
92
89
  }
93
90
 
94
91
  const finalArgs = [...args];