@chinhae/atw-cli 0.2.10 → 0.3.0

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.
Files changed (97) hide show
  1. package/README.md +29 -5
  2. package/dist/commands/init.d.ts.map +1 -1
  3. package/dist/commands/init.js +243 -123
  4. package/dist/commands/init.js.map +1 -1
  5. package/dist/commands/uninstall.d.ts.map +1 -1
  6. package/dist/commands/uninstall.js +18 -3
  7. package/dist/commands/uninstall.js.map +1 -1
  8. package/dist/commands/update.d.ts +0 -3
  9. package/dist/commands/update.d.ts.map +1 -1
  10. package/dist/commands/update.js +45 -0
  11. package/dist/commands/update.js.map +1 -1
  12. package/dist/configurators/index.d.ts +8 -0
  13. package/dist/configurators/index.d.ts.map +1 -1
  14. package/dist/configurators/index.js +19 -0
  15. package/dist/configurators/index.js.map +1 -1
  16. package/dist/migrations/manifests/0.3.0.json +3410 -0
  17. package/dist/templates/atw/config.yaml +4 -3
  18. package/dist/templates/atw/index.d.ts +2 -0
  19. package/dist/templates/atw/index.d.ts.map +1 -1
  20. package/dist/templates/atw/index.js +4 -0
  21. package/dist/templates/atw/index.js.map +1 -1
  22. package/dist/templates/atw/scripts/common/noob_mode.py +102 -0
  23. package/dist/templates/atw/scripts/common/session_context.py +7 -0
  24. package/dist/templates/atw/scripts/noob_mode.py +41 -0
  25. package/dist/templates/atw/scripts/tickets.py +66 -10
  26. package/dist/templates/atw/workflow.md +81 -17
  27. package/dist/templates/common/bundled-skills/atw-code-review/SKILL.md +4 -2
  28. package/dist/templates/common/bundled-skills/atw-codebase-design/DESIGN-IT-TWICE.md +1 -1
  29. package/dist/templates/common/bundled-skills/atw-diagnosing-bugs/SKILL.md +2 -2
  30. package/dist/templates/common/bundled-skills/atw-domain-modeling/{CONTEXT-FORMAT.md → GLOSSARY-FORMAT.md} +10 -10
  31. package/dist/templates/common/bundled-skills/atw-domain-modeling/SKILL.md +12 -12
  32. package/dist/templates/common/bundled-skills/atw-handoff/SKILL.md +1 -1
  33. package/dist/templates/common/bundled-skills/atw-implement/SKILL.md +6 -4
  34. package/dist/templates/common/bundled-skills/atw-implement-spec/SKILL.md +79 -0
  35. package/dist/templates/common/bundled-skills/atw-implement-spec/agents/openai.yaml +5 -0
  36. package/dist/templates/common/bundled-skills/atw-improve-codebase-architecture/SKILL.md +5 -5
  37. package/dist/templates/common/bundled-skills/atw-init-repo/SKILL.md +6 -4
  38. package/dist/templates/common/bundled-skills/atw-init-repo/references/domain.md +8 -8
  39. package/dist/templates/common/bundled-skills/atw-init-repo/references/issue-tracker-atw-local.md +5 -2
  40. package/dist/templates/common/bundled-skills/atw-init-repo/references/issue-tracker-atw.md +5 -2
  41. package/dist/templates/common/bundled-skills/atw-init-repo/references/issue-tracker-github.md +5 -4
  42. package/dist/templates/common/bundled-skills/atw-init-repo/references/issue-tracker-gitlab.md +2 -2
  43. package/dist/templates/common/bundled-skills/atw-init-repo/references/triage-labels.md +1 -1
  44. package/dist/templates/common/bundled-skills/atw-interview/SKILL.md +9 -1
  45. package/dist/templates/common/bundled-skills/atw-map/SKILL.md +5 -5
  46. package/dist/templates/common/bundled-skills/atw-noob-mode/SKILL.md +101 -0
  47. package/dist/templates/common/bundled-skills/atw-noob-mode/agents/openai.yaml +3 -0
  48. package/dist/templates/common/bundled-skills/atw-spec/SKILL.md +1 -1
  49. package/dist/templates/common/bundled-skills/atw-tdd/SKILL.md +2 -2
  50. package/dist/templates/common/bundled-skills/atw-tickets/SKILL.md +4 -4
  51. package/dist/templates/common/bundled-skills/atw-triage/SKILL.md +2 -2
  52. package/dist/templates/common/commands/continue.md +1 -1
  53. package/dist/templates/opencode/plugins/inject-workflow-state.js +29 -1
  54. package/dist/templates/shared-hooks/inject-subagent-context.py +29 -3
  55. package/dist/templates/shared-hooks/inject-workflow-state.py +27 -3
  56. package/dist/templates/shared-hooks/session-start.py +23 -0
  57. package/dist/templates/snow/SNOW.md +1 -1
  58. package/dist/utils/file-writer.d.ts.map +1 -1
  59. package/dist/utils/file-writer.js +29 -16
  60. package/dist/utils/file-writer.js.map +1 -1
  61. package/dist/utils/interactive-guard.d.ts +2 -2
  62. package/dist/utils/interactive-guard.js +2 -2
  63. package/dist/utils/lang.d.ts +14 -0
  64. package/dist/utils/lang.d.ts.map +1 -0
  65. package/dist/utils/lang.js +20 -0
  66. package/dist/utils/lang.js.map +1 -0
  67. package/dist/utils/optional-skills.d.ts +90 -0
  68. package/dist/utils/optional-skills.d.ts.map +1 -0
  69. package/dist/utils/optional-skills.js +266 -0
  70. package/dist/utils/optional-skills.js.map +1 -0
  71. package/dist/utils/template-fetcher.d.ts +6 -0
  72. package/dist/utils/template-fetcher.d.ts.map +1 -1
  73. package/dist/utils/template-fetcher.js +1 -1
  74. package/dist/utils/template-fetcher.js.map +1 -1
  75. package/package.json +2 -2
  76. package/dist/templates/common/bundled-skills/atw-merge/SKILL.md +0 -14
  77. package/dist/templates/common/bundled-skills/atw-merge/agents/openai.yaml +0 -3
  78. package/dist/templates/common/bundled-skills/dejargonizer/SKILL.md +0 -133
  79. package/dist/templates/common/bundled-skills/noob-mode/SKILL.md +0 -175
  80. package/dist/templates/common/bundled-skills/noob-mode/references/examples.md +0 -418
  81. package/dist/templates/common/bundled-skills/noob-mode/references/glossary.md +0 -368
  82. package/dist/templates/common/bundled-skills/oxy-explanation/SKILL.md +0 -80
  83. package/dist/templates/common/bundled-skills/oxy-learning-hub/SKILL.md +0 -179
  84. package/dist/templates/common/bundled-skills/oxy-learning-hub/assets/course.css +0 -1044
  85. package/dist/templates/common/bundled-skills/oxy-learning-hub/assets/course.js +0 -184
  86. package/dist/templates/common/bundled-skills/oxy-learning-hub/assets/lesson-template.html +0 -125
  87. package/dist/templates/common/bundled-skills/oxy-learning-hub/assets/summary-template.html +0 -659
  88. package/dist/templates/common/bundled-skills/oxy-learning-hub/references/publishing.md +0 -64
  89. package/dist/templates/common/bundled-skills/oxy-learning-hub/references/summary-mode.md +0 -96
  90. package/dist/templates/common/bundled-skills/oxy-learning-hub/references/teaching/GLOSSARY-FORMAT.md +0 -30
  91. package/dist/templates/common/bundled-skills/oxy-learning-hub/references/teaching/LEARNING-RECORD-FORMAT.md +0 -42
  92. package/dist/templates/common/bundled-skills/oxy-learning-hub/references/teaching/MISSION-FORMAT.md +0 -50
  93. package/dist/templates/common/bundled-skills/oxy-learning-hub/references/teaching/RESOURCES-FORMAT.md +0 -40
  94. package/dist/templates/common/bundled-skills/oxy-learning-hub/references/teaching-mode.md +0 -251
  95. package/dist/templates/common/bundled-skills/oxy-learning-hub/references/workspace.md +0 -112
  96. package/dist/templates/common/bundled-skills/oxy-learning-hub/scripts/validate_html.py +0 -200
  97. package/dist/templates/common/bundled-skills/show-me/SKILL.md +0 -127
@@ -6,7 +6,7 @@ import chalk from "chalk";
6
6
  import figlet from "figlet";
7
7
  import { promptInteractive, refuseNonInteractivePrompt, } from "../utils/interactive-guard.js";
8
8
  import { createWorkflowStructure } from "../configurators/workflow.js";
9
- import { getInitToolChoices, resolveCliFlag, configurePlatform, getConfiguredPlatforms, getPlatformsWithPythonHooks, } from "../configurators/index.js";
9
+ import { getInitToolChoices, resolveCliFlag, configurePlatform, getConfiguredPlatforms, getPlatformsWithPythonHooks, getSkillRoots, } from "../configurators/index.js";
10
10
  import { getPythonCommandForPlatform, setResolvedPythonCommand, } from "../configurators/shared.js";
11
11
  import { AI_TOOLS } from "../types/ai-tools.js";
12
12
  import { DIR_NAMES, FILE_NAMES, PATHS } from "../constants/paths.js";
@@ -21,6 +21,8 @@ import { isCwdHomedir, homedirGuardMessage, homedirBypassEnabled, } from "../uti
21
21
  import { writeSpecRegistryConfig, } from "../utils/registry-config.js";
22
22
  import { fetchTemplateIndex, probeRegistryIndex, downloadTemplateById, downloadRegistryDirect, parseRegistrySource, TIMEOUTS, TEMPLATE_INDEX_URL, } from "../utils/template-fetcher.js";
23
23
  import { setupProxy, maskProxyUrl } from "../utils/proxy.js";
24
+ import { getLang, setLang, t } from "../utils/lang.js";
25
+ import { fetchOptionalSkills, installOptionalSkills, loadOptionalSkillRecord, } from "../utils/optional-skills.js";
24
26
  import { toPosix } from "../utils/posix.js";
25
27
  import { updateHashes } from "../utils/template-hash.js";
26
28
  const MIN_PYTHON_MAJOR = 3;
@@ -191,7 +193,7 @@ function getOsDisplayName(platform = process.platform) {
191
193
  }
192
194
  function logPythonAdaptationNotice(command) {
193
195
  const osName = getOsDisplayName();
194
- console.log(chalk.blue(`📌 ${osName} detected: ATW rendered Python commands as "${command}" in generated hooks, settings, and help text`));
196
+ console.log(chalk.blue(t(`📌 ${osName} detected: ATW rendered Python commands as "${command}" in generated hooks, settings, and help text`, `📌 检测到 ${osName}:生成的钩子、配置和帮助文本里,Python 命令统一写作 "${command}"`)));
195
197
  }
196
198
  // =============================================================================
197
199
  // Bootstrap Task Creation
@@ -654,19 +656,22 @@ async function handleReinit(cwd, options, developerName, pythonCmd) {
654
656
  console.log(chalk.gray("Use platform flags (e.g., --codex) or -u <name> to add platforms/developer."));
655
657
  return true;
656
658
  }
657
- console.log(chalk.gray(`\n Already initialized with: ${configuredNames}\n`));
659
+ console.log(chalk.gray(t(`\n Already initialized with: ${configuredNames}\n`, `\n 已配置的平台:${configuredNames}\n`)));
658
660
  const { action } = await promptInteractive("what to do with an already-initialized project", "Pass --yes, or name what to add with a platform flag (e.g. --codex) or -u <name>.", [
659
661
  {
660
662
  type: "list",
661
663
  name: "action",
662
- message: "ATW is already initialized. What would you like to do?",
664
+ message: t("ATW is already initialized. What would you like to do?", "ATW 已经初始化过了,接下来要做什么?"),
663
665
  choices: [
664
- { name: "Add AI platform(s)", value: "add-platform" },
665
666
  {
666
- name: "Set up developer identity on this device",
667
+ name: t("Add AI platform(s)", "添加 AI 平台"),
668
+ value: "add-platform",
669
+ },
670
+ {
671
+ name: t("Set up developer identity on this device", "在这台设备上设置开发者身份"),
667
672
  value: "add-developer",
668
673
  },
669
- { name: "Full re-initialize", value: "full" },
674
+ { name: t("Full re-initialize", "完整重新初始化"), value: "full" },
670
675
  ],
671
676
  },
672
677
  ]);
@@ -687,14 +692,14 @@ async function handleReinit(cwd, options, developerName, pythonCmd) {
687
692
  return pid && !configuredPlatforms.has(pid);
688
693
  });
689
694
  if (unconfigured.length === 0) {
690
- console.log(chalk.green("✓ All available platforms are already configured."));
695
+ console.log(chalk.green(t("✓ All available platforms are already configured.", "✓ 所有可用平台都已配置。")));
691
696
  }
692
697
  else {
693
698
  const answers = await promptInteractive("which platforms to add", "Name them with platform flags (e.g. --codex --cursor) instead.", [
694
699
  {
695
700
  type: "checkbox",
696
701
  name: "tools",
697
- message: "Select platforms to add:",
702
+ message: t("Select platforms to add:", "选择要添加的平台:"),
698
703
  choices: unconfigured.map((t) => ({
699
704
  name: t.name,
700
705
  value: t.key,
@@ -704,27 +709,21 @@ async function handleReinit(cwd, options, developerName, pythonCmd) {
704
709
  platformsToAdd = answers.tools;
705
710
  }
706
711
  }
707
- // Opt-in Claude Code statusLine: only for platforms actually being added
708
- // (already-configured ones are skipped in the loop below)
709
- await maybePromptStatuslineOptIn(options, platformsToAdd.filter((tool) => {
710
- const pid = resolveCliFlag(tool);
711
- return !!pid && !configuredPlatforms.has(pid);
712
- }));
713
712
  const reinitWritten = startRecordingWrites(cwd);
714
713
  try {
715
714
  for (const tool of platformsToAdd) {
716
715
  const platformId = resolveCliFlag(tool);
717
716
  if (platformId) {
718
717
  if (configuredPlatforms.has(platformId)) {
719
- console.log(chalk.gray(` ○ ${AI_TOOLS[platformId].name} already configured, skipping`));
718
+ console.log(chalk.gray(t(` ○ ${AI_TOOLS[platformId].name} already configured, skipping`, ` ○ ${AI_TOOLS[platformId].name} 已配置,跳过`)));
720
719
  }
721
720
  else {
722
- console.log(chalk.blue(`📝 Configuring ${AI_TOOLS[platformId].name}...`));
721
+ console.log(chalk.blue(t(`📝 Configuring ${AI_TOOLS[platformId].name}...`, `📝 正在配置 ${AI_TOOLS[platformId].name}...`)));
723
722
  await configurePlatform(platformId, cwd, {
724
723
  withStatusline: options.withStatusline,
725
724
  });
726
725
  if (platformId === "claude-code" && options.withStatusline) {
727
- console.log(chalk.gray(" ↳ ATW statusLine installed (--with-statusline)"));
726
+ console.log(chalk.gray(t(" ↳ ATW statusLine installed (--with-statusline)", " ↳ 已安装 ATW 状态栏(--with-statusline)")));
728
727
  }
729
728
  }
730
729
  }
@@ -740,17 +739,17 @@ async function handleReinit(cwd, options, developerName, pythonCmd) {
740
739
  merge: true,
741
740
  });
742
741
  if (hashedCount > 0) {
743
- console.log(chalk.gray(`📋 Tracking ${hashedCount} template files for updates`));
742
+ console.log(chalk.gray(t(`📋 Tracking ${hashedCount} template files for updates`, `📋 已记录 ${hashedCount} 个模板文件,供后续升级比对`)));
744
743
  }
745
744
  }
746
745
  // --- Add developer ---
747
746
  if (doAddDeveloper) {
748
747
  let devName = developerName;
749
748
  if (!devName) {
750
- devName = await askInput("Your name: ", "the developer name", "Pass -u <name> to set it.");
749
+ devName = await askInput(t("Your name: ", "你的名字:"), "the developer name", "Pass -u <name> to set it.");
751
750
  while (!devName) {
752
- console.log(chalk.yellow("Name is required"));
753
- devName = await askInput("Your name: ", "the developer name", "Pass -u <name> to set it.");
751
+ console.log(chalk.yellow(t("Name is required", "名字不能为空")));
752
+ devName = await askInput(t("Your name: ", "你的名字:"), "the developer name", "Pass -u <name> to set it.");
754
753
  }
755
754
  }
756
755
  // Capture pre-init state: if .developer did not exist before we ran
@@ -763,10 +762,10 @@ async function handleReinit(cwd, options, developerName, pythonCmd) {
763
762
  cwd,
764
763
  stdio: "pipe",
765
764
  });
766
- console.log(chalk.green(`✓ Developer "${devName}" initialized`));
765
+ console.log(chalk.green(t(`✓ Developer "${devName}" initialized`, `✓ 开发者 "${devName}" 已初始化`)));
767
766
  }
768
767
  catch {
769
- console.log(chalk.yellow("⚠ Could not initialize developer. Run manually:"));
768
+ console.log(chalk.yellow(t("⚠ Could not initialize developer. Run manually:", "⚠ 开发者身份初始化失败,请手动执行:")));
770
769
  console.log(chalk.gray(` ${pythonCmd} .atw/scripts/init_developer.py ${devName}`));
771
770
  }
772
771
  // Create joiner onboarding task for fresh checkouts (no prior .developer).
@@ -774,38 +773,17 @@ async function handleReinit(cwd, options, developerName, pythonCmd) {
774
773
  if (!hadDeveloperFileBefore) {
775
774
  try {
776
775
  if (!createJoinerOnboardingTask(cwd, devName, pythonCmd)) {
777
- console.warn(chalk.yellow("⚠ Failed to create joiner onboarding task"));
776
+ console.warn(chalk.yellow(t("⚠ Failed to create joiner onboarding task", "⚠ 创建新成员上手任务失败")));
778
777
  }
779
778
  }
780
779
  catch (err) {
781
- console.warn(chalk.yellow(`⚠ Joiner onboarding setup failed: ${err instanceof Error ? err.message : String(err)}`));
780
+ console.warn(chalk.yellow(t(`⚠ Joiner onboarding setup failed: ${err instanceof Error ? err.message : String(err)}`, `⚠ 新成员上手任务设置失败:${err instanceof Error ? err.message : String(err)}`)));
782
781
  }
783
782
  }
784
783
  }
784
+ await offerOptionalSkills(cwd, options, [...getConfiguredPlatforms(cwd)]);
785
785
  return true;
786
786
  }
787
- /**
788
- * Interactive opt-in for the Claude Code statusLine when `--with-statusline`
789
- * was not passed. Fires only when Claude Code is among the platforms about to
790
- * be configured and never in -y mode. Mutates `options.withStatusline` so the
791
- * configurePlatform call sites and the install hint read the same answer; the
792
- * `!== undefined` gate doubles as the asked-once-per-run guard.
793
- */
794
- async function maybePromptStatuslineOptIn(options, toolKeys) {
795
- if (options.yes || options.withStatusline !== undefined)
796
- return;
797
- if (!toolKeys.includes(AI_TOOLS["claude-code"].cliFlag))
798
- return;
799
- const answer = await promptInteractive("the Claude Code statusLine opt-in", "Pass --with-statusline to install it, or --yes to skip it.", [
800
- {
801
- type: "confirm",
802
- name: "withStatusline",
803
- message: "Install ATW statusLine for Claude Code? (status bar: model, context, branch, rate limits)",
804
- default: false,
805
- },
806
- ]);
807
- options.withStatusline = answer.withStatusline;
808
- }
809
787
  const _cliFlagCheck = true;
810
788
  /**
811
789
  * Write monorepo package configuration to config.yaml (non-destructive patch).
@@ -869,21 +847,22 @@ export async function init(options) {
869
847
  // Generate ASCII art banner dynamically using FIGlet "Rebel" font
870
848
  const banner = figlet.textSync("ATW", { font: "Rebel" });
871
849
  console.log(chalk.cyan(`\n${banner.trimEnd()}`));
872
- console.log(chalk.gray("\n Team conventions, encoded into your AI coding workflow\n"));
850
+ await chooseLanguage(options);
851
+ console.log(chalk.gray(t("\n Team conventions, encoded into your AI coding workflow\n", "\n 把团队规范写进你的 AI 编码工作流\n")));
873
852
  // Set up proxy before any network calls
874
853
  const proxyUrl = setupProxy();
875
854
  if (proxyUrl) {
876
- console.log(chalk.gray(` Using proxy: ${maskProxyUrl(proxyUrl)}\n`));
855
+ console.log(chalk.gray(t(` Using proxy: ${maskProxyUrl(proxyUrl)}\n`, ` 使用代理:${maskProxyUrl(proxyUrl)}\n`)));
877
856
  }
878
857
  // Set write mode based on options
879
858
  let writeMode = "ask";
880
859
  if (options.force) {
881
860
  writeMode = "force";
882
- console.log(chalk.gray("Mode: Force overwrite existing files\n"));
861
+ console.log(chalk.gray(t("Mode: Force overwrite existing files\n", "模式:强制覆盖已有文件\n")));
883
862
  }
884
863
  else if (options.skipExisting) {
885
864
  writeMode = "skip";
886
- console.log(chalk.gray("Mode: Skip existing files\n"));
865
+ console.log(chalk.gray(t("Mode: Skip existing files\n", "模式:跳过已有文件\n")));
887
866
  }
888
867
  else if (options.yes) {
889
868
  // -y implies non-interactive: never prompt on conflicts. Default to skip
@@ -910,7 +889,7 @@ export async function init(options) {
910
889
  }
911
890
  }
912
891
  if (developerName) {
913
- console.log(chalk.blue("👤 Developer:"), chalk.gray(developerName));
892
+ console.log(chalk.blue(t("👤 Developer:", "👤 开发者:")), chalk.gray(developerName));
914
893
  }
915
894
  const { command: pythonCmd } = resolveSupportedPython();
916
895
  // ==========================================================================
@@ -935,15 +914,17 @@ export async function init(options) {
935
914
  }
936
915
  if (!developerName && !options.yes) {
937
916
  // Ask for developer name if not detected and not in yes mode
938
- console.log(chalk.gray("\nATW supports team collaboration - each developer has their own\n" +
917
+ console.log(chalk.gray(t("\nATW supports team collaboration - each developer has their own\n" +
939
918
  `workspace directory (${PATHS.WORKSPACE}/{name}/) to track AI sessions.\n` +
940
- "Tip: Usually this is your git username (git config user.name).\n"));
941
- developerName = await askInput("Your name: ", "the developer name", "Pass -u <name> to set it.");
919
+ "Tip: Usually this is your git username (git config user.name).\n", "\nATW 支持团队协作:每位开发者都有自己的工作区目录\n" +
920
+ `(${PATHS.WORKSPACE}/{name}/),用来记录 AI 会话。\n` +
921
+ "提示:一般填你的 git 用户名(git config user.name)。\n")));
922
+ developerName = await askInput(t("Your name: ", "你的名字:"), "the developer name", "Pass -u <name> to set it.");
942
923
  while (!developerName) {
943
- console.log(chalk.yellow("Name is required"));
944
- developerName = await askInput("Your name: ", "the developer name", "Pass -u <name> to set it.");
924
+ console.log(chalk.yellow(t("Name is required", "名字不能为空")));
925
+ developerName = await askInput(t("Your name: ", "你的名字:"), "the developer name", "Pass -u <name> to set it.");
945
926
  }
946
- console.log(chalk.blue("👤 Developer:"), chalk.gray(developerName));
927
+ console.log(chalk.blue(t("👤 Developer:", "👤 开发者:")), chalk.gray(developerName));
947
928
  }
948
929
  // Detect project type (silent - no output)
949
930
  const detectedType = detectProjectType(cwd);
@@ -956,7 +937,9 @@ export async function init(options) {
956
937
  registrySourceForConfig = options.registry;
957
938
  }
958
939
  catch (error) {
959
- console.log(chalk.red(error instanceof Error ? error.message : "Invalid registry source"));
940
+ console.log(chalk.red(error instanceof Error
941
+ ? error.message
942
+ : t("Invalid registry source", "无效的模板源")));
960
943
  return;
961
944
  }
962
945
  }
@@ -977,9 +960,9 @@ export async function init(options) {
977
960
  // options.monorepo: true = --monorepo, false = --no-monorepo, undefined = auto
978
961
  const detected = detectMonorepo(cwd);
979
962
  if (options.monorepo === true && !detected) {
980
- console.log(chalk.red("Error: --monorepo specified but no multi-package layout detected."));
963
+ console.log(chalk.red(t("Error: --monorepo specified but no multi-package layout detected.", "错误:指定了 --monorepo,但没有检测到多包结构。")));
981
964
  console.log("");
982
- console.log(chalk.gray("Checked:"));
965
+ console.log(chalk.gray(t("Checked:", "已检查:")));
983
966
  console.log(chalk.gray(" ✗ pnpm-workspace.yaml"));
984
967
  console.log(chalk.gray(" ✗ package.json workspaces"));
985
968
  console.log(chalk.gray(" ✗ Cargo.toml [workspace]"));
@@ -988,7 +971,7 @@ export async function init(options) {
988
971
  console.log(chalk.gray(" ✗ .gitmodules"));
989
972
  console.log(chalk.gray(" ✗ sibling .git directories (need ≥ 2)"));
990
973
  console.log("");
991
- console.log("To configure manually, add to .atw/config.yaml:");
974
+ console.log(t("To configure manually, add to .atw/config.yaml:", "如需手动配置,请在 .atw/config.yaml 中加入:"));
992
975
  console.log("");
993
976
  console.log(chalk.cyan(" packages:"));
994
977
  console.log(chalk.cyan(" frontend:"));
@@ -1006,7 +989,7 @@ export async function init(options) {
1006
989
  }
1007
990
  else {
1008
991
  // Show detected packages and ask
1009
- console.log(chalk.blue("\n🔍 Detected monorepo packages:"));
992
+ console.log(chalk.blue(t("\n🔍 Detected monorepo packages:", "\n🔍 检测到 monorepo 子包:")));
1010
993
  for (const pkg of detected) {
1011
994
  const tag = pkg.isSubmodule
1012
995
  ? chalk.gray(" (submodule)")
@@ -1023,7 +1006,7 @@ export async function init(options) {
1023
1006
  {
1024
1007
  type: "confirm",
1025
1008
  name: "useMonorepo",
1026
- message: "Enable monorepo mode?",
1009
+ message: t("Enable monorepo mode?", "启用 monorepo 模式?"),
1027
1010
  default: true,
1028
1011
  },
1029
1012
  ]);
@@ -1039,10 +1022,16 @@ export async function init(options) {
1039
1022
  {
1040
1023
  type: "list",
1041
1024
  name: "specSource",
1042
- message: `Spec source for ${pkg.name} (${pkg.path}):`,
1025
+ message: t(`Spec source for ${pkg.name} (${pkg.path}):`, `${pkg.name}(${pkg.path})的规范来源:`),
1043
1026
  choices: [
1044
- { name: "From scratch (ATW default)", value: "blank" },
1045
- { name: "Download remote template", value: "remote" },
1027
+ {
1028
+ name: t("From scratch (ATW default)", "从零开始(ATW 默认)"),
1029
+ value: "blank",
1030
+ },
1031
+ {
1032
+ name: t("Download remote template", "下载远程模板"),
1033
+ value: "remote",
1034
+ },
1046
1035
  ],
1047
1036
  default: "blank",
1048
1037
  },
@@ -1050,7 +1039,7 @@ export async function init(options) {
1050
1039
  if (specSource === "remote") {
1051
1040
  // Use existing template download flow, targeting spec/<name>/
1052
1041
  const destDir = path.join(cwd, PATHS.SPEC, sanitizePkgName(pkg.name));
1053
- console.log(chalk.blue(`📦 Select template for ${pkg.name}...`));
1042
+ console.log(chalk.blue(t(`📦 Select template for ${pkg.name}...`, `📦 为 ${pkg.name} 选择模板...`)));
1054
1043
  // Fetch templates if not already done
1055
1044
  const templates = await fetchTemplateIndex();
1056
1045
  const specTemplates = templates
@@ -1064,7 +1053,7 @@ export async function init(options) {
1064
1053
  {
1065
1054
  type: "list",
1066
1055
  name: "templateId",
1067
- message: `Select template for ${pkg.name}:`,
1056
+ message: t(`Select template for ${pkg.name}:`, `为 ${pkg.name} 选择模板:`),
1068
1057
  choices: specTemplates,
1069
1058
  },
1070
1059
  ]);
@@ -1075,11 +1064,11 @@ export async function init(options) {
1075
1064
  }
1076
1065
  else {
1077
1066
  console.log(chalk.yellow(` ${result.message}`));
1078
- console.log(chalk.gray(" Falling back to blank spec..."));
1067
+ console.log(chalk.gray(t(" Falling back to blank spec...", " 改用空白规范...")));
1079
1068
  }
1080
1069
  }
1081
1070
  else {
1082
- console.log(chalk.gray(" No templates available. Using blank spec."));
1071
+ console.log(chalk.gray(t(" No templates available. Using blank spec.", " 没有可用模板,使用空白规范。")));
1083
1072
  }
1084
1073
  }
1085
1074
  }
@@ -1116,7 +1105,7 @@ export async function init(options) {
1116
1105
  {
1117
1106
  type: "checkbox",
1118
1107
  name: "tools",
1119
- message: "Select AI tools to configure:",
1108
+ message: t("Select AI tools to configure:", "选择要配置的 AI 工具:"),
1120
1109
  choices: TOOLS.map((t) => ({
1121
1110
  name: t.name,
1122
1111
  value: t.key,
@@ -1129,11 +1118,9 @@ export async function init(options) {
1129
1118
  // Treat unknown project type as fullstack
1130
1119
  const projectType = detectedType === "unknown" ? "fullstack" : detectedType;
1131
1120
  if (tools.length === 0) {
1132
- console.log(chalk.yellow("No tools selected. At least one tool is required."));
1121
+ console.log(chalk.yellow(t("No tools selected. At least one tool is required.", "没有选择任何工具,至少需要选一个。")));
1133
1122
  return;
1134
1123
  }
1135
- // Opt-in Claude Code statusLine: confirm interactively when the flag wasn't passed
1136
- await maybePromptStatuslineOptIn(options, tools);
1137
1124
  // ==========================================================================
1138
1125
  // Template Selection (single-repo only; monorepo handles templates above)
1139
1126
  // ==========================================================================
@@ -1155,11 +1142,11 @@ export async function init(options) {
1155
1142
  const probeResult = await probeRegistryIndex(indexUrl, registry);
1156
1143
  registryBackend = probeResult.backend;
1157
1144
  if (probeResult.error) {
1158
- console.log(chalk.red(`Error: ${probeResult.error.message}`));
1145
+ console.log(chalk.red(t(`Error: ${probeResult.error.message}`, `错误:${probeResult.error.message}`)));
1159
1146
  return;
1160
1147
  }
1161
1148
  if (probeResult.isNotFound) {
1162
- console.log(chalk.red("Error: Registry has no index.json. Remove --template to use direct download mode."));
1149
+ console.log(chalk.red(t("Error: Registry has no index.json. Remove --template to use direct download mode.", "错误:模板源里没有 index.json。去掉 --template 即可使用直接下载模式。")));
1163
1150
  return;
1164
1151
  }
1165
1152
  fetchedTemplates = probeResult.templates;
@@ -1169,13 +1156,13 @@ export async function init(options) {
1169
1156
  // Interactive mode: show template selection
1170
1157
  const timeoutSec = TIMEOUTS.INDEX_FETCH_MS / 1000;
1171
1158
  const sourceLabel = registry ? registry.gigetSource : TEMPLATE_INDEX_URL;
1172
- console.log(chalk.gray(` Fetching available templates from ${sourceLabel}`));
1159
+ console.log(chalk.gray(t(` Fetching available templates from ${sourceLabel}`, ` 正在从 ${sourceLabel} 获取可用模板`)));
1173
1160
  let elapsed = 0;
1174
1161
  const ticker = setInterval(() => {
1175
1162
  elapsed++;
1176
- process.stdout.write(`\r${chalk.gray(` Loading... ${elapsed}s/${timeoutSec}s`)}`);
1163
+ process.stdout.write(`\r${chalk.gray(t(` Loading... ${elapsed}s/${timeoutSec}s`, ` 加载中... ${elapsed}s/${timeoutSec}s`))}`);
1177
1164
  }, 1000);
1178
- process.stdout.write(chalk.gray(` Loading... 0s/${timeoutSec}s`));
1165
+ process.stdout.write(chalk.gray(t(` Loading... 0s/${timeoutSec}s`, ` 加载中... 0s/${timeoutSec}s`)));
1179
1166
  let templates;
1180
1167
  let registryProbeNotFound = false;
1181
1168
  let registryProbeError;
@@ -1195,16 +1182,16 @@ export async function init(options) {
1195
1182
  fetchedTemplates = templates;
1196
1183
  if (templates.length === 0 && registry && registryProbeNotFound) {
1197
1184
  // Custom registry: confirmed no index.json — will try direct download later
1198
- console.log(chalk.gray(" No index.json found at registry. Will download as direct spec template."));
1185
+ console.log(chalk.gray(t(" No index.json found at registry. Will download as direct spec template.", " 模板源里没有 index.json,将作为规范模板直接下载。")));
1199
1186
  }
1200
1187
  else if (templates.length === 0 && registry) {
1201
1188
  // Custom registry: transient error (not a 404) — abort, don't misclassify
1202
- console.log(chalk.red(` ${registryProbeError?.message ?? "Could not reach registry. Check your connection and try again."}`));
1189
+ console.log(chalk.red(` ${registryProbeError?.message ?? t("Could not reach registry. Check your connection and try again.", "无法连接模板源,请检查网络后重试。")}`));
1203
1190
  return;
1204
1191
  }
1205
1192
  else if (templates.length === 0) {
1206
- console.log(chalk.gray(" Could not fetch templates (offline or server unavailable)."));
1207
- console.log(chalk.gray(" Using blank templates.\n"));
1193
+ console.log(chalk.gray(t(" Could not fetch templates (offline or server unavailable).", " 无法获取模板(离线或服务器不可用)。")));
1194
+ console.log(chalk.gray(t(" Using blank templates.\n", " 使用空白模板。\n")));
1208
1195
  }
1209
1196
  if (templates.length > 0) {
1210
1197
  // Build template choices
@@ -1218,12 +1205,12 @@ export async function init(options) {
1218
1205
  ? specTemplates
1219
1206
  : [
1220
1207
  {
1221
- name: "from scratch (default)",
1208
+ name: t("from scratch (default)", "从零开始(默认)"),
1222
1209
  value: "blank",
1223
1210
  },
1224
1211
  ...specTemplates,
1225
1212
  {
1226
- name: "custom (enter a registry source)",
1213
+ name: t("custom (enter a registry source)", "自定义(输入模板源)"),
1227
1214
  value: "__custom__",
1228
1215
  },
1229
1216
  ];
@@ -1234,14 +1221,14 @@ export async function init(options) {
1234
1221
  {
1235
1222
  type: "list",
1236
1223
  name: "template",
1237
- message: "Select a spec template:",
1224
+ message: t("Select a spec template:", "选择规范模板:"),
1238
1225
  choices: templateChoices,
1239
1226
  default: registry ? undefined : "blank",
1240
1227
  },
1241
1228
  ]);
1242
1229
  if (templateAnswer.template === "__custom__") {
1243
1230
  // Prompt for custom registry source (empty → back to picker)
1244
- const customSource = await askInput("Enter registry source (e.g., gh:myorg/myrepo/specs), or press Enter to go back: ", "a custom registry source", "Pass --registry <source> instead.");
1231
+ const customSource = await askInput(t("Enter registry source (e.g., gh:myorg/myrepo/specs), or press Enter to go back: ", "输入模板源(例如 gh:myorg/myrepo/specs),直接回车返回:"), "a custom registry source", "Pass --registry <source> instead.");
1245
1232
  if (!customSource) {
1246
1233
  continue; // Back to picker
1247
1234
  }
@@ -1251,7 +1238,7 @@ export async function init(options) {
1251
1238
  fetchedTemplates = []; // Reset so direct-download guard works correctly
1252
1239
  // Probe index.json to detect marketplace vs direct download
1253
1240
  const customIndexUrl = `${registry.rawBaseUrl}/index.json`;
1254
- console.log(chalk.gray(` Checking for templates at ${registry.gigetSource}...`));
1241
+ console.log(chalk.gray(t(` Checking for templates at ${registry.gigetSource}...`, ` 正在检查 ${registry.gigetSource} 里的模板...`)));
1255
1242
  const customProbe = await probeRegistryIndex(customIndexUrl, registry);
1256
1243
  const customTemplates = customProbe.templates;
1257
1244
  registryBackend = customProbe.backend;
@@ -1269,7 +1256,7 @@ export async function init(options) {
1269
1256
  {
1270
1257
  type: "list",
1271
1258
  name: "template",
1272
- message: "Select a spec template:",
1259
+ message: t("Select a spec template:", "选择规范模板:"),
1273
1260
  choices: customChoices,
1274
1261
  },
1275
1262
  ]);
@@ -1283,15 +1270,18 @@ export async function init(options) {
1283
1270
  {
1284
1271
  type: "list",
1285
1272
  name: "action",
1286
- message: `Directory ${PATHS.SPEC} already exists. What do you want to do?`,
1273
+ message: t(`Directory ${PATHS.SPEC} already exists. What do you want to do?`, `目录 ${PATHS.SPEC} 已存在,要怎么处理?`),
1287
1274
  choices: [
1288
- { name: "Skip (keep existing)", value: "skip" },
1289
1275
  {
1290
- name: "Overwrite (replace all)",
1276
+ name: t("Skip (keep existing)", "跳过(保留现有内容)"),
1277
+ value: "skip",
1278
+ },
1279
+ {
1280
+ name: t("Overwrite (replace all)", "覆盖(全部替换)"),
1291
1281
  value: "overwrite",
1292
1282
  },
1293
1283
  {
1294
- name: "Append (add missing files only)",
1284
+ name: t("Append (add missing files only)", "追加(只补缺失的文件)"),
1295
1285
  value: "append",
1296
1286
  },
1297
1287
  ],
@@ -1309,7 +1299,7 @@ export async function init(options) {
1309
1299
  }
1310
1300
  else {
1311
1301
  // Transient error (not 404) — loop back, don't misclassify
1312
- console.log(chalk.yellow(` ${customProbe.error?.message ?? "Could not reach registry. Try again or enter a different source."}`));
1302
+ console.log(chalk.yellow(` ${customProbe.error?.message ?? t("Could not reach registry. Try again or enter a different source.", "无法连接模板源,请重试或换一个模板源。")}`));
1313
1303
  registry = undefined; // Reset so we don't fall through to direct download
1314
1304
  registrySourceForConfig = undefined;
1315
1305
  }
@@ -1317,7 +1307,7 @@ export async function init(options) {
1317
1307
  catch (error) {
1318
1308
  console.log(chalk.red(error instanceof Error
1319
1309
  ? error.message
1320
- : "Invalid registry source"));
1310
+ : t("Invalid registry source", "无效的模板源")));
1321
1311
  // Loop back to picker
1322
1312
  }
1323
1313
  }
@@ -1334,12 +1324,18 @@ export async function init(options) {
1334
1324
  {
1335
1325
  type: "list",
1336
1326
  name: "action",
1337
- message: `Directory ${PATHS.SPEC} already exists. What do you want to do?`,
1327
+ message: t(`Directory ${PATHS.SPEC} already exists. What do you want to do?`, `目录 ${PATHS.SPEC} 已存在,要怎么处理?`),
1338
1328
  choices: [
1339
- { name: "Skip (keep existing)", value: "skip" },
1340
- { name: "Overwrite (replace all)", value: "overwrite" },
1341
1329
  {
1342
- name: "Append (add missing files only)",
1330
+ name: t("Skip (keep existing)", "跳过(保留现有内容)"),
1331
+ value: "skip",
1332
+ },
1333
+ {
1334
+ name: t("Overwrite (replace all)", "覆盖(全部替换)"),
1335
+ value: "overwrite",
1336
+ },
1337
+ {
1338
+ name: t("Append (add missing files only)", "追加(只补缺失的文件)"),
1343
1339
  value: "append",
1344
1340
  },
1345
1341
  ],
@@ -1366,7 +1362,7 @@ export async function init(options) {
1366
1362
  }
1367
1363
  if (!probeResult.isNotFound) {
1368
1364
  // Transient error (not 404) — abort, don't misclassify as direct-download
1369
- console.log(chalk.red(`Error: ${probeResult.error?.message ?? "Could not reach registry. Check your connection and try again."}`));
1365
+ console.log(chalk.red(`Error: ${probeResult.error?.message ?? t("Could not reach registry. Check your connection and try again.", "无法连接模板源,请检查网络后重试。")}`));
1370
1366
  return;
1371
1367
  }
1372
1368
  // isNotFound=true → no index.json, proceed with direct download (fetchedTemplates stays empty)
@@ -1378,8 +1374,8 @@ export async function init(options) {
1378
1374
  let registrySpecConfigToPersist = null;
1379
1375
  if (selectedTemplate) {
1380
1376
  // Marketplace mode: download specific template by ID
1381
- console.log(chalk.blue(`📦 Downloading template "${selectedTemplate}"...`));
1382
- console.log(chalk.gray(" This may take a moment on slow connections."));
1377
+ console.log(chalk.blue(t(`📦 Downloading template "${selectedTemplate}"...`, `📦 正在下载模板 "${selectedTemplate}"...`)));
1378
+ console.log(chalk.gray(t(" This may take a moment on slow connections.", " 网络较慢时可能需要等一会儿。")));
1383
1379
  // Find pre-fetched SpecTemplate to avoid double-fetch
1384
1380
  const prefetched = fetchedTemplates.find((t) => t.id === selectedTemplate);
1385
1381
  const result = await downloadTemplateById(cwd, selectedTemplate, templateStrategy, prefetched, registry, undefined, registryBackend);
@@ -1400,17 +1396,17 @@ export async function init(options) {
1400
1396
  }
1401
1397
  else {
1402
1398
  console.log(chalk.yellow(` ${result.message}`));
1403
- console.log(chalk.gray(" Falling back to blank templates..."));
1399
+ console.log(chalk.gray(t(" Falling back to blank templates...", " 改用空白模板...")));
1404
1400
  const retryCmd = registry
1405
1401
  ? `atw init --registry ${registry.gigetSource} --template ${selectedTemplate}`
1406
1402
  : `atw init --template ${selectedTemplate}`;
1407
- console.log(chalk.gray(` You can retry later: ${retryCmd}`));
1403
+ console.log(chalk.gray(t(` You can retry later: ${retryCmd}`, ` 之后可以重试:${retryCmd}`)));
1408
1404
  }
1409
1405
  }
1410
1406
  else if (registry && fetchedTemplates.length === 0) {
1411
1407
  // Direct download mode: registry has no index.json, download directory directly
1412
- console.log(chalk.blue(`📦 Downloading spec from ${registry.gigetSource}...`));
1413
- console.log(chalk.gray(" This may take a moment on slow connections."));
1408
+ console.log(chalk.blue(t(`📦 Downloading spec from ${registry.gigetSource}...`, `📦 正在从 ${registry.gigetSource} 下载规范...`)));
1409
+ console.log(chalk.gray(t(" This may take a moment on slow connections.", " 网络较慢时可能需要等一会儿。")));
1414
1410
  // Ask about existing spec dir in interactive mode
1415
1411
  if (!options.yes && !options.overwrite && !options.append) {
1416
1412
  const specDir = path.join(cwd, PATHS.SPEC);
@@ -1419,11 +1415,20 @@ export async function init(options) {
1419
1415
  {
1420
1416
  type: "list",
1421
1417
  name: "action",
1422
- message: `Directory ${PATHS.SPEC} already exists. What do you want to do?`,
1418
+ message: t(`Directory ${PATHS.SPEC} already exists. What do you want to do?`, `目录 ${PATHS.SPEC} 已存在,要怎么处理?`),
1423
1419
  choices: [
1424
- { name: "Skip (keep existing)", value: "skip" },
1425
- { name: "Overwrite (replace all)", value: "overwrite" },
1426
- { name: "Append (add missing files only)", value: "append" },
1420
+ {
1421
+ name: t("Skip (keep existing)", "跳过(保留现有内容)"),
1422
+ value: "skip",
1423
+ },
1424
+ {
1425
+ name: t("Overwrite (replace all)", "覆盖(全部替换)"),
1426
+ value: "overwrite",
1427
+ },
1428
+ {
1429
+ name: t("Append (add missing files only)", "追加(只补缺失的文件)"),
1430
+ value: "append",
1431
+ },
1427
1432
  ],
1428
1433
  default: "skip",
1429
1434
  },
@@ -1446,8 +1451,8 @@ export async function init(options) {
1446
1451
  }
1447
1452
  else {
1448
1453
  console.log(chalk.yellow(` ${result.message}`));
1449
- console.log(chalk.gray(" Falling back to blank templates..."));
1450
- console.log(chalk.gray(` You can retry later: atw init --registry ${registry.gigetSource}`));
1454
+ console.log(chalk.gray(t(" Falling back to blank templates...", " 改用空白模板...")));
1455
+ console.log(chalk.gray(t(` You can retry later: atw init --registry ${registry.gigetSource}`, ` 之后可以重试:atw init --registry ${registry.gigetSource}`)));
1451
1456
  }
1452
1457
  }
1453
1458
  // ==========================================================================
@@ -1464,7 +1469,7 @@ export async function init(options) {
1464
1469
  });
1465
1470
  if (resolved.id !== NATIVE_WORKFLOW_ID) {
1466
1471
  workflowMdOverride = resolved.content;
1467
- console.log(chalk.blue(`🧭 Using workflow template: ${chalk.cyan(resolved.id)}`));
1472
+ console.log(chalk.blue(t(`🧭 Using workflow template: ${chalk.cyan(resolved.id)}`, `🧭 使用工作流模板:${chalk.cyan(resolved.id)}`)));
1468
1473
  }
1469
1474
  }
1470
1475
  // ==========================================================================
@@ -1478,7 +1483,7 @@ export async function init(options) {
1478
1483
  const writtenPaths = startRecordingWrites(cwd);
1479
1484
  try {
1480
1485
  // Create workflow structure with project type
1481
- console.log(chalk.blue("📁 Creating workflow structure..."));
1486
+ console.log(chalk.blue(t("📁 Creating workflow structure...", "📁 正在创建工作流目录结构...")));
1482
1487
  await createWorkflowStructure(cwd, {
1483
1488
  projectType,
1484
1489
  skipSpecTemplates: useRemoteTemplate,
@@ -1489,7 +1494,7 @@ export async function init(options) {
1489
1494
  // Write monorepo packages to config.yaml (non-destructive patch)
1490
1495
  if (monorepoPackages) {
1491
1496
  writeMonorepoConfig(cwd, monorepoPackages);
1492
- console.log(chalk.blue("📦 Monorepo packages written to config.yaml"));
1497
+ console.log(chalk.blue(t("📦 Monorepo packages written to config.yaml", "📦 已把 monorepo 子包写入 config.yaml")));
1493
1498
  }
1494
1499
  // Write version file for update tracking
1495
1500
  const versionPath = path.join(cwd, DIR_NAMES.WORKFLOW, ".version");
@@ -1498,12 +1503,12 @@ export async function init(options) {
1498
1503
  for (const tool of tools) {
1499
1504
  const platformId = resolveCliFlag(tool);
1500
1505
  if (platformId) {
1501
- console.log(chalk.blue(`📝 Configuring ${AI_TOOLS[platformId].name}...`));
1506
+ console.log(chalk.blue(t(`📝 Configuring ${AI_TOOLS[platformId].name}...`, `📝 正在配置 ${AI_TOOLS[platformId].name}...`)));
1502
1507
  await configurePlatform(platformId, cwd, {
1503
1508
  withStatusline: options.withStatusline,
1504
1509
  });
1505
1510
  if (platformId === "claude-code" && options.withStatusline) {
1506
- console.log(chalk.gray(" ↳ ATW statusLine installed (--with-statusline)"));
1511
+ console.log(chalk.gray(t(" ↳ ATW statusLine installed (--with-statusline)", " ↳ 已安装 ATW 状态栏(--with-statusline)")));
1507
1512
  }
1508
1513
  }
1509
1514
  }
@@ -1534,7 +1539,7 @@ export async function init(options) {
1534
1539
  }
1535
1540
  }
1536
1541
  if (hashedCount > 0) {
1537
- console.log(chalk.gray(`📋 Tracking ${hashedCount} template files for updates`));
1542
+ console.log(chalk.gray(t(`📋 Tracking ${hashedCount} template files for updates`, `📋 已记录 ${hashedCount} 个模板文件,供后续升级比对`)));
1538
1543
  }
1539
1544
  // Non-native workflow is user-managed local content. Drop the
1540
1545
  // `.atw/workflow.md` hash entry so `atw update` classifies it as
@@ -1578,14 +1583,129 @@ export async function init(options) {
1578
1583
  else if (!hadDeveloperFileAtStart) {
1579
1584
  try {
1580
1585
  if (!createJoinerOnboardingTask(cwd, developerName, pythonCmd)) {
1581
- console.warn(chalk.yellow("⚠ Failed to create joiner onboarding task"));
1586
+ console.warn(chalk.yellow(t("⚠ Failed to create joiner onboarding task", "⚠ 创建新成员上手任务失败")));
1582
1587
  }
1583
1588
  }
1584
1589
  catch (err) {
1585
- console.warn(chalk.yellow(`⚠ Joiner onboarding setup failed: ${err instanceof Error ? err.message : String(err)}`));
1590
+ console.warn(chalk.yellow(t(`⚠ Joiner onboarding setup failed: ${err instanceof Error ? err.message : String(err)}`, `⚠ 新成员上手任务设置失败:${err instanceof Error ? err.message : String(err)}`)));
1586
1591
  }
1587
1592
  }
1588
1593
  }
1594
+ await offerOptionalSkills(cwd, options, tools.flatMap((tool) => resolveCliFlag(tool) ?? []));
1595
+ }
1596
+ /**
1597
+ * Ask which language the rest of this `atw init` run talks in. Asked first so
1598
+ * every later prompt and message follows it.
1599
+ *
1600
+ * English when nobody is there to answer (-y, non-TTY). Unlike the other
1601
+ * prompts this one falls back instead of refusing: it picks wording only and
1602
+ * decides nothing about the user's files.
1603
+ */
1604
+ async function chooseLanguage(options) {
1605
+ setLang("en");
1606
+ if (options.yes || !process.stdin.isTTY)
1607
+ return;
1608
+ console.log("");
1609
+ const { lang } = await promptInteractive("the display language", "Pass --yes to use English.", [
1610
+ {
1611
+ type: "list",
1612
+ name: "lang",
1613
+ message: "选择语言 / Select language:",
1614
+ choices: [
1615
+ { name: "中文", value: "zh" },
1616
+ { name: "English", value: "en" },
1617
+ ],
1618
+ },
1619
+ ]);
1620
+ if (lang === "zh")
1621
+ setLang("zh");
1622
+ }
1623
+ /**
1624
+ * Offer the optional skills from the oxy-Tools repo and bring the chosen ones
1625
+ * to the listed version in every skill directory of the given platforms. Runs
1626
+ * at the end of a full init and of a re-init, so an existing project can pick
1627
+ * up more skills — or a newer version of one it already has.
1628
+ *
1629
+ * Skipped without a word in -y and non-TTY runs: init has already succeeded by
1630
+ * now, and refusing here would fail an invocation whose flags answered every
1631
+ * question it needed.
1632
+ */
1633
+ async function offerOptionalSkills(cwd, options, platformIds) {
1634
+ if (options.yes || !process.stdin.isTTY)
1635
+ return;
1636
+ const skillRoots = getSkillRoots(platformIds);
1637
+ if (skillRoots.length === 0)
1638
+ return;
1639
+ const installed = loadOptionalSkillRecord(cwd);
1640
+ const hasInstalled = Object.keys(installed).length > 0;
1641
+ console.log("");
1642
+ const { installExtraSkills } = await promptInteractive("whether to install extra skills", "Pass --yes to skip them.", [
1643
+ {
1644
+ type: "confirm",
1645
+ name: "installExtraSkills",
1646
+ message: hasInstalled
1647
+ ? t("Install or update extra skills? (optional, downloaded from GitHub)", "是否安装或更新额外的 skill?(可选,从 GitHub 下载)")
1648
+ : t("Install extra skills? (optional, downloaded from GitHub)", "是否安装额外的 skill?(可选,从 GitHub 下载)"),
1649
+ default: false,
1650
+ },
1651
+ ]);
1652
+ if (!installExtraSkills)
1653
+ return;
1654
+ console.log(chalk.gray(t(" Fetching the skill list...", " 正在获取 skill 列表...")));
1655
+ const available = await fetchOptionalSkills();
1656
+ if (available.length === 0) {
1657
+ console.log(chalk.gray(t(" Could not fetch the skill list (offline or GitHub unreachable). Skipped.", " 无法获取 skill 列表(离线或连不上 GitHub),已跳过。")));
1658
+ return;
1659
+ }
1660
+ const lang = getLang();
1661
+ /** What the picker says about a skill this project already has. */
1662
+ const status = (skill) => {
1663
+ const have = installed[skill.name]?.version;
1664
+ if (have === undefined)
1665
+ return "";
1666
+ return have === skill.version
1667
+ ? t(` [installed ${have}]`, ` [已安装 ${have}]`)
1668
+ : t(` [installed ${have}, ${skill.version} available]`, ` [已安装 ${have},可更新到 ${skill.version}]`);
1669
+ };
1670
+ const { extraSkills } = await promptInteractive("which extra skills to install", "Pass --yes to skip them.", [
1671
+ {
1672
+ type: "checkbox",
1673
+ name: "extraSkills",
1674
+ message: t("Select skills to install:", "选择要安装的 skill:"),
1675
+ choices: available.map((skill) => ({
1676
+ name: `${skill.name} — ${skill.description[lang]}${status(skill)}`,
1677
+ value: skill.name,
1678
+ // 已装且有新版本的默认勾上
1679
+ checked: installed[skill.name] !== undefined &&
1680
+ installed[skill.name].version !== skill.version,
1681
+ })),
1682
+ pageSize: available.length,
1683
+ },
1684
+ ]);
1685
+ const selected = available.filter((skill) => extraSkills?.includes(skill.name));
1686
+ if (selected.length === 0)
1687
+ return;
1688
+ console.log(chalk.blue(t(`📦 Downloading ${selected.length} skill(s)...`, `📦 正在下载 ${selected.length} 个 skill...`)));
1689
+ console.log(chalk.gray(t(" This may take a moment on slow connections.", " 网络较慢时可能需要等一会儿。")));
1690
+ for await (const result of installOptionalSkills(cwd, selected, skillRoots)) {
1691
+ const { name, version } = result;
1692
+ if (result.error) {
1693
+ console.log(chalk.yellow(t(` ✗ ${name}: download failed (${result.error})`, ` ✗ ${name}:下载失败(${result.error})`)));
1694
+ continue;
1695
+ }
1696
+ if (result.installed.length > 0) {
1697
+ console.log(chalk.green(` ✓ ${name} ${version} → ${result.installed.join(", ")}`));
1698
+ }
1699
+ if (result.updated.length > 0) {
1700
+ console.log(chalk.green(` ↻ ${name} ${result.previousVersion} → ${version} (${result.updated.join(", ")})`));
1701
+ }
1702
+ if (result.current.length > 0) {
1703
+ console.log(chalk.gray(t(` ○ ${name}: already at ${version} in ${result.current.join(", ")}`, ` ○ ${name}:${result.current.join(", ")} 里已是 ${version}`)));
1704
+ }
1705
+ if (result.foreign.length > 0) {
1706
+ console.log(chalk.gray(t(` ○ ${name}: ${result.foreign.join(", ")} already has one ATW did not install, left untouched`, ` ○ ${name}:${result.foreign.join(", ")} 里已有一份不是 ATW 装的,未改动`)));
1707
+ }
1708
+ }
1589
1709
  }
1590
1710
  /**
1591
1711
  * Simple readline-based input (no flickering like inquirer)
@@ -1615,7 +1735,7 @@ async function createRootFiles(cwd) {
1615
1735
  // Write AGENTS.md from template
1616
1736
  const agentsWritten = await writeFile(agentsPath, agentsMdContent);
1617
1737
  if (agentsWritten) {
1618
- console.log(chalk.blue("📄 Created AGENTS.md"));
1738
+ console.log(chalk.blue(t("📄 Created AGENTS.md", "📄 已创建 AGENTS.md")));
1619
1739
  }
1620
1740
  }
1621
1741
  //# sourceMappingURL=init.js.map