Merge e742a984ff into 4583f88626
This commit is contained in:
commit
bb8c4ae7ab
24
README.md
24
README.md
@ -23,6 +23,30 @@ If you want a personal, single-user assistant that feels local, fast, and always
|
||||
|
||||
[Website](https://molt.bot) · [Docs](https://docs.molt.bot) · [Getting Started](https://docs.molt.bot/start/getting-started) · [Updating](https://docs.molt.bot/install/updating) · [Showcase](https://docs.molt.bot/start/showcase) · [FAQ](https://docs.molt.bot/start/faq) · [Wizard](https://docs.molt.bot/start/wizard) · [Nix](https://github.com/moltbot/nix-clawdbot) · [Docker](https://docs.molt.bot/install/docker) · [Discord](https://discord.gg/clawd)
|
||||
|
||||
## 繁體中文(zh-TW)
|
||||
|
||||
此 fork 目標是把 **CLI 安裝/初始化流程(`moltbot onboard` / wizard)** 加入繁體中文介面。
|
||||
|
||||
### 啟用繁中介面
|
||||
|
||||
macOS / Linux(zsh/bash):
|
||||
|
||||
```bash
|
||||
export MOLTBOT_LANG=zh-TW
|
||||
moltbot onboard --install-daemon
|
||||
```
|
||||
|
||||
Windows(PowerShell):
|
||||
|
||||
```powershell
|
||||
$env:MOLTBOT_LANG = "zh-TW"
|
||||
moltbot onboard --install-daemon
|
||||
```
|
||||
|
||||
也支援從系統語系自動判斷(例如 `LANG=zh_TW.UTF-8`)。
|
||||
|
||||
---
|
||||
|
||||
Preferred setup: run the onboarding wizard (`moltbot onboard`). It walks through gateway, workspace, channels, and skills. The CLI wizard is the recommended path and works on **macOS, Linux, and Windows (via WSL2; strongly recommended)**.
|
||||
Works with npm, pnpm, or bun.
|
||||
New install? Start here: [Getting started](https://docs.molt.bot/start/getting-started)
|
||||
|
||||
@ -61,11 +61,13 @@ export async function onboardCommand(opts: OnboardOptions, runtime: RuntimeEnv =
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
const { resolveLocaleFromEnv, t } = await import("../i18n/i18n.js");
|
||||
const locale = resolveLocaleFromEnv();
|
||||
runtime.log(
|
||||
[
|
||||
"Windows detected.",
|
||||
"WSL2 is strongly recommended; native Windows is untested and more problematic.",
|
||||
"Guide: https://docs.molt.bot/windows",
|
||||
t(locale, "windows.detected"),
|
||||
t(locale, "windows.recommend"),
|
||||
t(locale, "windows.guide"),
|
||||
].join("\n"),
|
||||
);
|
||||
}
|
||||
|
||||
116
src/i18n/i18n.ts
Normal file
116
src/i18n/i18n.ts
Normal file
@ -0,0 +1,116 @@
|
||||
export type MoltbotLocale = "en" | "zh-TW";
|
||||
|
||||
export function resolveLocaleFromEnv(env: NodeJS.ProcessEnv = process.env): MoltbotLocale {
|
||||
const raw = (env.MOLTBOT_LANG ?? env.CLAWDBOT_LANG ?? env.LANG ?? env.LC_ALL ?? "").trim();
|
||||
const norm = raw.replace("_", "-");
|
||||
if (!norm) return "en";
|
||||
|
||||
// Common forms:
|
||||
// - zh_TW.UTF-8
|
||||
// - zh-TW
|
||||
// - zh-Hant
|
||||
// - zh-Hant-TW
|
||||
const lower = norm.toLowerCase();
|
||||
if (lower.startsWith("zh")) {
|
||||
if (lower.includes("tw") || lower.includes("hant")) return "zh-TW";
|
||||
// If user explicitly asked for Chinese, default to Traditional.
|
||||
return "zh-TW";
|
||||
}
|
||||
|
||||
return "en";
|
||||
}
|
||||
|
||||
const dict: Record<MoltbotLocale, Record<string, string>> = {
|
||||
en: {
|
||||
"onboard.intro": "Moltbot onboarding",
|
||||
"onboard.mode": "Onboarding mode",
|
||||
"onboard.mode.quickstart": "QuickStart",
|
||||
"onboard.mode.manual": "Manual",
|
||||
|
||||
"onboard.quickstart.title": "QuickStart",
|
||||
"onboard.quickstart.keepExisting": "Keeping your current gateway settings:",
|
||||
"onboard.quickstart.direct": "Direct to chat channels.",
|
||||
|
||||
"onboard.whatToSetup": "What do you want to set up?",
|
||||
"onboard.localGateway": "Local gateway (this machine)",
|
||||
"onboard.remoteGateway": "Remote gateway (info-only)",
|
||||
|
||||
"onboard.configExisting": "Existing config detected",
|
||||
"onboard.configInvalid": "Invalid config",
|
||||
"onboard.configIssues": "Config issues",
|
||||
"onboard.configHandling": "Config handling",
|
||||
"onboard.config.keep": "Use existing values",
|
||||
"onboard.config.modify": "Update values",
|
||||
"onboard.config.reset": "Reset",
|
||||
|
||||
"onboard.resetScope": "Reset scope",
|
||||
"onboard.reset.config": "Config only",
|
||||
"onboard.reset.configCredsSessions": "Config + creds + sessions",
|
||||
"onboard.reset.full": "Full reset (config + creds + sessions + workspace)",
|
||||
|
||||
"onboard.workspaceDir": "Workspace directory",
|
||||
|
||||
"security.title": "Security",
|
||||
"security.noteTitle": "Security warning — please read.",
|
||||
"security.confirm": "I understand this is powerful and inherently risky. Continue?",
|
||||
|
||||
"windows.detected": "Windows detected.",
|
||||
"windows.recommend": "WSL2 is strongly recommended; native Windows is untested and more problematic.",
|
||||
"windows.guide": "Guide: https://docs.molt.bot/windows",
|
||||
|
||||
"onboard.skipChannels": "Skipping channel setup.",
|
||||
"onboard.channelsTitle": "Channels",
|
||||
"onboard.skipSkills": "Skipping skills setup.",
|
||||
"onboard.skillsTitle": "Skills",
|
||||
|
||||
"onboard.remoteConfigured": "Remote gateway configured.",
|
||||
},
|
||||
"zh-TW": {
|
||||
"onboard.intro": "Moltbot 初始設定(Onboarding)",
|
||||
"onboard.mode": "安裝/初始化模式",
|
||||
"onboard.mode.quickstart": "快速開始(QuickStart)",
|
||||
"onboard.mode.manual": "手動設定(Manual)",
|
||||
|
||||
"onboard.quickstart.title": "快速開始(QuickStart)",
|
||||
"onboard.quickstart.keepExisting": "保留你目前的 Gateway 設定:",
|
||||
"onboard.quickstart.direct": "直接導向聊天管道設定。",
|
||||
|
||||
"onboard.whatToSetup": "你想要設定哪一種?",
|
||||
"onboard.localGateway": "本機 Gateway(這台機器)",
|
||||
"onboard.remoteGateway": "遠端 Gateway(僅寫入設定/不做本機安裝)",
|
||||
|
||||
"onboard.configExisting": "偵測到既有設定",
|
||||
"onboard.configInvalid": "設定檔不合法",
|
||||
"onboard.configIssues": "設定問題",
|
||||
"onboard.configHandling": "設定檔處理方式",
|
||||
"onboard.config.keep": "沿用既有值",
|
||||
"onboard.config.modify": "更新設定",
|
||||
"onboard.config.reset": "重置",
|
||||
|
||||
"onboard.resetScope": "重置範圍",
|
||||
"onboard.reset.config": "只重置設定檔",
|
||||
"onboard.reset.configCredsSessions": "設定檔 + 憑證 + sessions",
|
||||
"onboard.reset.full": "完整重置(設定檔 + 憑證 + sessions + workspace)",
|
||||
|
||||
"onboard.workspaceDir": "Workspace 目錄",
|
||||
|
||||
"security.title": "安全性",
|
||||
"security.noteTitle": "安全警告 — 請務必先閱讀",
|
||||
"security.confirm": "我了解這很強大且有風險,仍要繼續嗎?",
|
||||
|
||||
"windows.detected": "偵測到 Windows。",
|
||||
"windows.recommend": "強烈建議使用 WSL2;原生 Windows 尚未完整測試,問題也比較多。",
|
||||
"windows.guide": "指南:https://docs.molt.bot/windows",
|
||||
|
||||
"onboard.skipChannels": "略過聊天管道設定。",
|
||||
"onboard.channelsTitle": "聊天管道(Channels)",
|
||||
"onboard.skipSkills": "略過技能(Skills)安裝。",
|
||||
"onboard.skillsTitle": "技能(Skills)",
|
||||
|
||||
"onboard.remoteConfigured": "已完成遠端 Gateway 設定。",
|
||||
},
|
||||
};
|
||||
|
||||
export function t(locale: MoltbotLocale, key: string): string {
|
||||
return dict[locale]?.[key] ?? dict.en[key] ?? key;
|
||||
}
|
||||
@ -38,6 +38,7 @@ import { logConfigUpdated } from "../config/logging.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import { resolveLocaleFromEnv, t } from "../i18n/i18n.js";
|
||||
import { finalizeOnboardingWizard } from "./onboarding.finalize.js";
|
||||
import { configureGatewayForOnboarding } from "./onboarding.gateway-config.js";
|
||||
import type { QuickstartGatewayDefaults, WizardFlow } from "./onboarding.types.js";
|
||||
@ -49,34 +50,61 @@ async function requireRiskAcknowledgement(params: {
|
||||
}) {
|
||||
if (params.opts.acceptRisk === true) return;
|
||||
|
||||
const locale = resolveLocaleFromEnv();
|
||||
|
||||
const securityBodyEn = [
|
||||
"Security warning — please read.",
|
||||
"",
|
||||
"Moltbot is a hobby project and still in beta. Expect sharp edges.",
|
||||
"This bot can read files and run actions if tools are enabled.",
|
||||
"A bad prompt can trick it into doing unsafe things.",
|
||||
"",
|
||||
"If you’re not comfortable with basic security and access control, don’t run Moltbot.",
|
||||
"Ask someone experienced to help before enabling tools or exposing it to the internet.",
|
||||
"",
|
||||
"Recommended baseline:",
|
||||
"- Pairing/allowlists + mention gating.",
|
||||
"- Sandbox + least-privilege tools.",
|
||||
"- Keep secrets out of the agent’s reachable filesystem.",
|
||||
"- Use the strongest available model for any bot with tools or untrusted inboxes.",
|
||||
"",
|
||||
"Run regularly:",
|
||||
"moltbot security audit --deep",
|
||||
"moltbot security audit --fix",
|
||||
"",
|
||||
"Must read: https://docs.molt.bot/gateway/security",
|
||||
];
|
||||
|
||||
const securityBodyZhTw = [
|
||||
"安全警告 — 請務必先閱讀。",
|
||||
"",
|
||||
"Moltbot 是一個興趣專案,目前仍在 beta,請預期會有一些粗糙邊角。",
|
||||
"當你啟用工具(tools)後,這個 bot 可能具備讀檔/執行動作的能力。",
|
||||
"不良提示(prompt)可能誘導它做出不安全的操作。",
|
||||
"",
|
||||
"如果你不熟悉基本資安與存取控制,建議不要直接在生產環境跑 Moltbot。",
|
||||
"在啟用工具或把它暴露到網路前,請找有經驗的人協助檢視設定。",
|
||||
"",
|
||||
"建議底線(baseline):",
|
||||
"- Pairing/allowlists + mention gating(配對/白名單 + 只回應@提及)",
|
||||
"- Sandbox + 最小權限工具",
|
||||
"- 不要把機密放在 agent 可讀到的檔案系統",
|
||||
"- 有工具或會讀不受信任訊息時,請用你能用到的最強模型",
|
||||
"",
|
||||
"建議定期執行:",
|
||||
"moltbot security audit --deep",
|
||||
"moltbot security audit --fix",
|
||||
"",
|
||||
"必讀:https://docs.molt.bot/gateway/security",
|
||||
];
|
||||
|
||||
await params.prompter.note(
|
||||
[
|
||||
"Security warning — please read.",
|
||||
"",
|
||||
"Moltbot is a hobby project and still in beta. Expect sharp edges.",
|
||||
"This bot can read files and run actions if tools are enabled.",
|
||||
"A bad prompt can trick it into doing unsafe things.",
|
||||
"",
|
||||
"If you’re not comfortable with basic security and access control, don’t run Moltbot.",
|
||||
"Ask someone experienced to help before enabling tools or exposing it to the internet.",
|
||||
"",
|
||||
"Recommended baseline:",
|
||||
"- Pairing/allowlists + mention gating.",
|
||||
"- Sandbox + least-privilege tools.",
|
||||
"- Keep secrets out of the agent’s reachable filesystem.",
|
||||
"- Use the strongest available model for any bot with tools or untrusted inboxes.",
|
||||
"",
|
||||
"Run regularly:",
|
||||
"moltbot security audit --deep",
|
||||
"moltbot security audit --fix",
|
||||
"",
|
||||
"Must read: https://docs.molt.bot/gateway/security",
|
||||
].join("\n"),
|
||||
"Security",
|
||||
(locale === "zh-TW" ? securityBodyZhTw : securityBodyEn).join("\n"),
|
||||
t(locale, "security.title"),
|
||||
);
|
||||
|
||||
const ok = await params.prompter.confirm({
|
||||
message: "I understand this is powerful and inherently risky. Continue?",
|
||||
message: t(locale, "security.confirm"),
|
||||
initialValue: false,
|
||||
});
|
||||
if (!ok) {
|
||||
@ -89,23 +117,27 @@ export async function runOnboardingWizard(
|
||||
runtime: RuntimeEnv = defaultRuntime,
|
||||
prompter: WizardPrompter,
|
||||
) {
|
||||
const locale = resolveLocaleFromEnv();
|
||||
|
||||
printWizardHeader(runtime);
|
||||
await prompter.intro("Moltbot onboarding");
|
||||
await prompter.intro(t(locale, "onboard.intro"));
|
||||
await requireRiskAcknowledgement({ opts, prompter });
|
||||
|
||||
const snapshot = await readConfigFileSnapshot();
|
||||
let baseConfig: MoltbotConfig = snapshot.valid ? snapshot.config : {};
|
||||
|
||||
if (snapshot.exists && !snapshot.valid) {
|
||||
await prompter.note(summarizeExistingConfig(baseConfig), "Invalid config");
|
||||
await prompter.note(summarizeExistingConfig(baseConfig), t(locale, "onboard.configInvalid"));
|
||||
if (snapshot.issues.length > 0) {
|
||||
await prompter.note(
|
||||
[
|
||||
...snapshot.issues.map((iss) => `- ${iss.path}: ${iss.message}`),
|
||||
"",
|
||||
"Docs: https://docs.molt.bot/gateway/configuration",
|
||||
locale === "zh-TW"
|
||||
? "文件:https://docs.molt.bot/gateway/configuration"
|
||||
: "Docs: https://docs.molt.bot/gateway/configuration",
|
||||
].join("\n"),
|
||||
"Config issues",
|
||||
t(locale, "onboard.configIssues"),
|
||||
);
|
||||
}
|
||||
await prompter.outro(
|
||||
@ -135,47 +167,49 @@ export async function runOnboardingWizard(
|
||||
let flow: WizardFlow =
|
||||
explicitFlow ??
|
||||
((await prompter.select({
|
||||
message: "Onboarding mode",
|
||||
message: t(locale, "onboard.mode"),
|
||||
options: [
|
||||
{ value: "quickstart", label: "QuickStart", hint: quickstartHint },
|
||||
{ value: "advanced", label: "Manual", hint: manualHint },
|
||||
{ value: "quickstart", label: t(locale, "onboard.mode.quickstart"), hint: quickstartHint },
|
||||
{ value: "advanced", label: t(locale, "onboard.mode.manual"), hint: manualHint },
|
||||
],
|
||||
initialValue: "quickstart",
|
||||
})) as "quickstart" | "advanced");
|
||||
|
||||
if (opts.mode === "remote" && flow === "quickstart") {
|
||||
await prompter.note(
|
||||
"QuickStart only supports local gateways. Switching to Manual mode.",
|
||||
"QuickStart",
|
||||
locale === "zh-TW"
|
||||
? "快速開始(QuickStart)只支援本機 Gateway,將切換為手動模式(Manual)。"
|
||||
: "QuickStart only supports local gateways. Switching to Manual mode.",
|
||||
t(locale, "onboard.quickstart.title"),
|
||||
);
|
||||
flow = "advanced";
|
||||
}
|
||||
|
||||
if (snapshot.exists) {
|
||||
await prompter.note(summarizeExistingConfig(baseConfig), "Existing config detected");
|
||||
await prompter.note(summarizeExistingConfig(baseConfig), t(locale, "onboard.configExisting"));
|
||||
|
||||
const action = (await prompter.select({
|
||||
message: "Config handling",
|
||||
message: t(locale, "onboard.configHandling"),
|
||||
options: [
|
||||
{ value: "keep", label: "Use existing values" },
|
||||
{ value: "modify", label: "Update values" },
|
||||
{ value: "reset", label: "Reset" },
|
||||
{ value: "keep", label: t(locale, "onboard.config.keep") },
|
||||
{ value: "modify", label: t(locale, "onboard.config.modify") },
|
||||
{ value: "reset", label: t(locale, "onboard.config.reset") },
|
||||
],
|
||||
})) as "keep" | "modify" | "reset";
|
||||
|
||||
if (action === "reset") {
|
||||
const workspaceDefault = baseConfig.agents?.defaults?.workspace ?? DEFAULT_WORKSPACE;
|
||||
const resetScope = (await prompter.select({
|
||||
message: "Reset scope",
|
||||
message: t(locale, "onboard.resetScope"),
|
||||
options: [
|
||||
{ value: "config", label: "Config only" },
|
||||
{ value: "config", label: t(locale, "onboard.reset.config") },
|
||||
{
|
||||
value: "config+creds+sessions",
|
||||
label: "Config + creds + sessions",
|
||||
label: t(locale, "onboard.reset.configCredsSessions"),
|
||||
},
|
||||
{
|
||||
value: "full",
|
||||
label: "Full reset (config + creds + sessions + workspace)",
|
||||
label: t(locale, "onboard.reset.full"),
|
||||
},
|
||||
],
|
||||
})) as ResetScope;
|
||||
@ -254,7 +288,7 @@ export async function runOnboardingWizard(
|
||||
};
|
||||
const quickstartLines = quickstartGateway.hasExisting
|
||||
? [
|
||||
"Keeping your current gateway settings:",
|
||||
t(locale, "onboard.quickstart.keepExisting"),
|
||||
`Gateway port: ${quickstartGateway.port}`,
|
||||
`Gateway bind: ${formatBind(quickstartGateway.bind)}`,
|
||||
...(quickstartGateway.bind === "custom" && quickstartGateway.customBindHost
|
||||
@ -262,16 +296,16 @@ export async function runOnboardingWizard(
|
||||
: []),
|
||||
`Gateway auth: ${formatAuth(quickstartGateway.authMode)}`,
|
||||
`Tailscale exposure: ${formatTailscale(quickstartGateway.tailscaleMode)}`,
|
||||
"Direct to chat channels.",
|
||||
t(locale, "onboard.quickstart.direct"),
|
||||
]
|
||||
: [
|
||||
`Gateway port: ${DEFAULT_GATEWAY_PORT}`,
|
||||
"Gateway bind: Loopback (127.0.0.1)",
|
||||
"Gateway auth: Token (default)",
|
||||
"Tailscale exposure: Off",
|
||||
"Direct to chat channels.",
|
||||
t(locale, "onboard.quickstart.direct"),
|
||||
];
|
||||
await prompter.note(quickstartLines.join("\n"), "QuickStart");
|
||||
await prompter.note(quickstartLines.join("\n"), t(locale, "onboard.quickstart.title"));
|
||||
}
|
||||
|
||||
const localPort = resolveGatewayPort(baseConfig);
|
||||
@ -294,18 +328,18 @@ export async function runOnboardingWizard(
|
||||
(flow === "quickstart"
|
||||
? "local"
|
||||
: ((await prompter.select({
|
||||
message: "What do you want to set up?",
|
||||
message: t(locale, "onboard.whatToSetup"),
|
||||
options: [
|
||||
{
|
||||
value: "local",
|
||||
label: "Local gateway (this machine)",
|
||||
label: t(locale, "onboard.localGateway"),
|
||||
hint: localProbe.ok
|
||||
? `Gateway reachable (${localUrl})`
|
||||
: `No gateway detected (${localUrl})`,
|
||||
},
|
||||
{
|
||||
value: "remote",
|
||||
label: "Remote gateway (info-only)",
|
||||
label: t(locale, "onboard.remoteGateway"),
|
||||
hint: !remoteUrl
|
||||
? "No remote URL configured yet"
|
||||
: remoteProbe?.ok
|
||||
@ -320,7 +354,7 @@ export async function runOnboardingWizard(
|
||||
nextConfig = applyWizardMetadata(nextConfig, { command: "onboard", mode });
|
||||
await writeConfigFile(nextConfig);
|
||||
logConfigUpdated(runtime);
|
||||
await prompter.outro("Remote gateway configured.");
|
||||
await prompter.outro(t(locale, "onboard.remoteConfigured"));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -329,7 +363,7 @@ export async function runOnboardingWizard(
|
||||
(flow === "quickstart"
|
||||
? (baseConfig.agents?.defaults?.workspace ?? DEFAULT_WORKSPACE)
|
||||
: await prompter.text({
|
||||
message: "Workspace directory",
|
||||
message: t(locale, "onboard.workspaceDir"),
|
||||
initialValue: baseConfig.agents?.defaults?.workspace ?? DEFAULT_WORKSPACE,
|
||||
}));
|
||||
|
||||
@ -403,7 +437,7 @@ export async function runOnboardingWizard(
|
||||
const settings = gateway.settings;
|
||||
|
||||
if (opts.skipChannels ?? opts.skipProviders) {
|
||||
await prompter.note("Skipping channel setup.", "Channels");
|
||||
await prompter.note(t(locale, "onboard.skipChannels"), t(locale, "onboard.channelsTitle"));
|
||||
} else {
|
||||
const quickstartAllowFromChannels =
|
||||
flow === "quickstart"
|
||||
@ -427,7 +461,7 @@ export async function runOnboardingWizard(
|
||||
});
|
||||
|
||||
if (opts.skipSkills) {
|
||||
await prompter.note("Skipping skills setup.", "Skills");
|
||||
await prompter.note(t(locale, "onboard.skipSkills"), t(locale, "onboard.skillsTitle"));
|
||||
} else {
|
||||
nextConfig = await setupSkills(nextConfig, workspaceDir, runtime, prompter);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user