@chinhae/atw-cli 0.2.10 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -5
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +243 -123
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +18 -3
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.d.ts +0 -3
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +45 -0
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/index.d.ts +8 -0
- package/dist/configurators/index.d.ts.map +1 -1
- package/dist/configurators/index.js +19 -0
- package/dist/configurators/index.js.map +1 -1
- package/dist/migrations/manifests/0.3.0.json +3410 -0
- package/dist/migrations/manifests/0.3.1.json +9 -0
- package/dist/templates/atw/config.yaml +4 -3
- package/dist/templates/atw/index.d.ts +2 -0
- package/dist/templates/atw/index.d.ts.map +1 -1
- package/dist/templates/atw/index.js +4 -0
- package/dist/templates/atw/index.js.map +1 -1
- package/dist/templates/atw/scripts/common/noob_mode.py +102 -0
- package/dist/templates/atw/scripts/common/session_context.py +7 -0
- package/dist/templates/atw/scripts/noob_mode.py +41 -0
- package/dist/templates/atw/scripts/tickets.py +66 -10
- package/dist/templates/atw/workflow.md +81 -17
- package/dist/templates/common/bundled-skills/atw-code-review/SKILL.md +4 -2
- package/dist/templates/common/bundled-skills/atw-codebase-design/DESIGN-IT-TWICE.md +1 -1
- package/dist/templates/common/bundled-skills/atw-diagnosing-bugs/SKILL.md +2 -2
- package/dist/templates/common/bundled-skills/atw-domain-modeling/{CONTEXT-FORMAT.md → GLOSSARY-FORMAT.md} +10 -10
- package/dist/templates/common/bundled-skills/atw-domain-modeling/SKILL.md +12 -12
- package/dist/templates/common/bundled-skills/atw-handoff/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-implement/SKILL.md +6 -4
- package/dist/templates/common/bundled-skills/atw-implement-spec/SKILL.md +79 -0
- package/dist/templates/common/bundled-skills/atw-implement-spec/agents/openai.yaml +5 -0
- package/dist/templates/common/bundled-skills/atw-improve-codebase-architecture/SKILL.md +5 -5
- package/dist/templates/common/bundled-skills/atw-init-repo/SKILL.md +6 -4
- package/dist/templates/common/bundled-skills/atw-init-repo/references/domain.md +8 -8
- package/dist/templates/common/bundled-skills/atw-init-repo/references/issue-tracker-atw-local.md +5 -2
- package/dist/templates/common/bundled-skills/atw-init-repo/references/issue-tracker-atw.md +5 -2
- package/dist/templates/common/bundled-skills/atw-init-repo/references/issue-tracker-github.md +5 -4
- package/dist/templates/common/bundled-skills/atw-init-repo/references/issue-tracker-gitlab.md +2 -2
- package/dist/templates/common/bundled-skills/atw-init-repo/references/triage-labels.md +1 -1
- package/dist/templates/common/bundled-skills/atw-interview/SKILL.md +9 -1
- package/dist/templates/common/bundled-skills/atw-map/SKILL.md +5 -5
- package/dist/templates/common/bundled-skills/atw-noob-mode/SKILL.md +101 -0
- package/dist/templates/common/bundled-skills/atw-noob-mode/agents/openai.yaml +3 -0
- package/dist/templates/common/bundled-skills/atw-spec/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-tdd/SKILL.md +2 -2
- package/dist/templates/common/bundled-skills/atw-tickets/SKILL.md +4 -4
- package/dist/templates/common/bundled-skills/atw-triage/SKILL.md +2 -2
- package/dist/templates/common/commands/continue.md +1 -1
- package/dist/templates/opencode/plugins/inject-workflow-state.js +29 -1
- package/dist/templates/shared-hooks/inject-subagent-context.py +29 -3
- package/dist/templates/shared-hooks/inject-workflow-state.py +27 -3
- package/dist/templates/shared-hooks/session-start.py +23 -0
- package/dist/templates/snow/SNOW.md +1 -1
- package/dist/utils/file-writer.d.ts.map +1 -1
- package/dist/utils/file-writer.js +29 -16
- package/dist/utils/file-writer.js.map +1 -1
- package/dist/utils/interactive-guard.d.ts +2 -2
- package/dist/utils/interactive-guard.js +2 -2
- package/dist/utils/lang.d.ts +14 -0
- package/dist/utils/lang.d.ts.map +1 -0
- package/dist/utils/lang.js +20 -0
- package/dist/utils/lang.js.map +1 -0
- package/dist/utils/optional-skills.d.ts +90 -0
- package/dist/utils/optional-skills.d.ts.map +1 -0
- package/dist/utils/optional-skills.js +266 -0
- package/dist/utils/optional-skills.js.map +1 -0
- package/dist/utils/template-fetcher.d.ts +6 -0
- package/dist/utils/template-fetcher.d.ts.map +1 -1
- package/dist/utils/template-fetcher.js +1 -1
- package/dist/utils/template-fetcher.js.map +1 -1
- package/package.json +2 -2
- package/dist/templates/common/bundled-skills/atw-merge/SKILL.md +0 -14
- package/dist/templates/common/bundled-skills/atw-merge/agents/openai.yaml +0 -3
- package/dist/templates/common/bundled-skills/dejargonizer/SKILL.md +0 -133
- package/dist/templates/common/bundled-skills/noob-mode/SKILL.md +0 -175
- package/dist/templates/common/bundled-skills/noob-mode/references/examples.md +0 -418
- package/dist/templates/common/bundled-skills/noob-mode/references/glossary.md +0 -368
- package/dist/templates/common/bundled-skills/oxy-explanation/SKILL.md +0 -80
- package/dist/templates/common/bundled-skills/oxy-learning-hub/SKILL.md +0 -179
- package/dist/templates/common/bundled-skills/oxy-learning-hub/assets/course.css +0 -1044
- package/dist/templates/common/bundled-skills/oxy-learning-hub/assets/course.js +0 -184
- package/dist/templates/common/bundled-skills/oxy-learning-hub/assets/lesson-template.html +0 -125
- package/dist/templates/common/bundled-skills/oxy-learning-hub/assets/summary-template.html +0 -659
- package/dist/templates/common/bundled-skills/oxy-learning-hub/references/publishing.md +0 -64
- package/dist/templates/common/bundled-skills/oxy-learning-hub/references/summary-mode.md +0 -96
- package/dist/templates/common/bundled-skills/oxy-learning-hub/references/teaching/GLOSSARY-FORMAT.md +0 -30
- package/dist/templates/common/bundled-skills/oxy-learning-hub/references/teaching/LEARNING-RECORD-FORMAT.md +0 -42
- package/dist/templates/common/bundled-skills/oxy-learning-hub/references/teaching/MISSION-FORMAT.md +0 -50
- package/dist/templates/common/bundled-skills/oxy-learning-hub/references/teaching/RESOURCES-FORMAT.md +0 -40
- package/dist/templates/common/bundled-skills/oxy-learning-hub/references/teaching-mode.md +0 -251
- package/dist/templates/common/bundled-skills/oxy-learning-hub/references/workspace.md +0 -112
- package/dist/templates/common/bundled-skills/oxy-learning-hub/scripts/validate_html.py +0 -200
- package/dist/templates/common/bundled-skills/show-me/SKILL.md +0 -127
package/dist/commands/init.js
CHANGED
|
@@ -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: "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
{
|
|
1045
|
-
|
|
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: "
|
|
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: "
|
|
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
|
-
{
|
|
1425
|
-
|
|
1426
|
-
|
|
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
|