@nalvietnam/avatar-cli 1.2.11 → 1.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.
package/dist/index.js CHANGED
@@ -546,19 +546,19 @@ function applyUseGlobal(existing, source) {
546
546
  ...sourceModel ? { model: sourceModel } : {}
547
547
  };
548
548
  }
549
- async function writeClaudeSettings(workspacePath, input5) {
549
+ async function writeClaudeSettings(workspacePath, input6) {
550
550
  const path = getClaudeSettingsPath(workspacePath);
551
551
  const existing = await readExistingSettings(path);
552
552
  let merged;
553
- switch (input5.provider) {
553
+ switch (input6.provider) {
554
554
  case "subscription":
555
- merged = applySubscription(existing, input5.model);
555
+ merged = applySubscription(existing, input6.model);
556
556
  break;
557
557
  case "llmlite":
558
- merged = applyLLMLite(existing, input5.apiKey, input5.baseUrl, input5.model);
558
+ merged = applyLLMLite(existing, input6.apiKey, input6.baseUrl, input6.model);
559
559
  break;
560
560
  case "use-global":
561
- merged = applyUseGlobal(existing, input5.sourceSettings);
561
+ merged = applyUseGlobal(existing, input6.sourceSettings);
562
562
  break;
563
563
  }
564
564
  await writeJsonAtomic(path, merged, SECRET_FILE_MODE2);
@@ -843,47 +843,186 @@ function registerAiCommand(program2) {
843
843
  });
844
844
  }
845
845
 
846
- // src/lib/not-implemented-stub.ts
847
- function notImplementedYet(commandName, milestone) {
848
- return () => {
849
- process.stdout.write(
850
- `${chalk.yellow("\u23F3")} ${chalk.bold(`avatar ${commandName}`)} \u2014 ch\u01B0a implement \u1EDF milestone hi\u1EC7n t\u1EA1i.
851
- `
846
+ // src/commands/commit.ts
847
+ import { input as input2 } from "@inquirer/prompts";
848
+
849
+ // src/lib/execute-commit-with-target-selection.ts
850
+ import { spawnSync as spawnSync5 } from "child_process";
851
+ import { existsSync } from "fs";
852
+ import { join as join6 } from "path";
853
+ function assertAvatarWorkspaceRoot(cwd) {
854
+ const srcGit = join6(cwd, "src", ".git");
855
+ const workspaceGit = join6(cwd, ".git");
856
+ const claudeDir = join6(cwd, ".claude");
857
+ if (!existsSync(workspaceGit)) {
858
+ throw new Error(
859
+ `Kh\xF4ng ph\u1EA3i workspace root: ${cwd}
860
+ Ch\u1EA1y 'avatar commit' trong workspace dir (c\xF3 .git v\xE0 .claude/).`
852
861
  );
853
- if (milestone) {
854
- process.stdout.write(` D\u1EF1 ki\u1EBFn: ${chalk.cyan(milestone)}
855
- `);
856
- }
857
- process.stdout.write(" Spec \u0111\xE3 c\xF3 trong avatar-cli-implementation_4.html.\n");
858
- process.exit(0);
859
- };
862
+ }
863
+ if (!existsSync(claudeDir)) {
864
+ throw new Error(
865
+ `Kh\xF4ng th\u1EA5y .claude/ trong ${cwd}.
866
+ Ch\u1EA1y 'avatar commit' trong Avatar workspace, kh\xF4ng ph\u1EA3i project b\xECnh th\u01B0\u1EDDng.`
867
+ );
868
+ }
869
+ if (!existsSync(srcGit)) {
870
+ throw new Error(
871
+ `Kh\xF4ng th\u1EA5y src/.git trong ${cwd}.
872
+ Workspace thi\u1EBFu submodule src/. Ch\u1EA1y 'avatar init' l\u1EA1i?`
873
+ );
874
+ }
875
+ }
876
+ function gitExec(cwd, args) {
877
+ const r = spawnSync5("git", ["-C", cwd, ...args], {
878
+ encoding: "utf8",
879
+ stdio: ["ignore", "pipe", "pipe"]
880
+ });
881
+ if (r.status !== 0) {
882
+ const stderr = (r.stderr || "").trim();
883
+ throw new Error(`git ${args.join(" ")} (in ${cwd}) failed:
884
+ ${stderr}`);
885
+ }
886
+ return (r.stdout || "").trim();
887
+ }
888
+ function isDirty(cwd) {
889
+ const status = gitExec(cwd, ["status", "--porcelain"]);
890
+ return status.length > 0;
891
+ }
892
+ async function commitSrc(workspaceRoot, opts) {
893
+ const srcPath = join6(workspaceRoot, "src");
894
+ if (!isDirty(srcPath)) {
895
+ log.dim("src/: nothing to commit (clean)");
896
+ return {};
897
+ }
898
+ log.info("Committing src/ ...");
899
+ gitExec(srcPath, ["add", "."]);
900
+ gitExec(srcPath, ["commit", "-m", opts.message]);
901
+ const sha = gitExec(srcPath, ["rev-parse", "HEAD"]);
902
+ log.success(`src/ committed: ${sha.slice(0, 7)}`);
903
+ let pushed = false;
904
+ if (opts.push) {
905
+ log.info("Pushing src/ ...");
906
+ gitExec(srcPath, ["push"]);
907
+ log.success("src/ pushed");
908
+ pushed = true;
909
+ }
910
+ return { sha, pushed };
911
+ }
912
+ async function commitWorkspace(workspaceRoot, opts) {
913
+ if (!isDirty(workspaceRoot)) {
914
+ log.dim("workspace: nothing to commit (clean)");
915
+ return {};
916
+ }
917
+ log.info("Committing workspace root ...");
918
+ gitExec(workspaceRoot, ["add", "."]);
919
+ gitExec(workspaceRoot, ["commit", "-m", opts.message]);
920
+ const sha = gitExec(workspaceRoot, ["rev-parse", "HEAD"]);
921
+ log.success(`workspace committed: ${sha.slice(0, 7)}`);
922
+ let pushed = false;
923
+ if (opts.push) {
924
+ log.info("Pushing workspace ...");
925
+ gitExec(workspaceRoot, ["push"]);
926
+ log.success("workspace pushed");
927
+ pushed = true;
928
+ }
929
+ return { sha, pushed };
930
+ }
931
+ function warnIfOtherTargetDirty(workspaceRoot, requestedTarget) {
932
+ if (requestedTarget === "all") return;
933
+ const srcDirty = isDirty(join6(workspaceRoot, "src"));
934
+ const workspaceDirty = isDirty(workspaceRoot);
935
+ if (requestedTarget === "src" && workspaceDirty) {
936
+ log.warn(
937
+ "Workspace c\u0169ng c\xF3 changes ch\u01B0a commit. Ch\u1EA1y 'avatar commit workspace' (ho\u1EB7c 'avatar commit all') \u0111\u1EC3 commit lu\xF4n."
938
+ );
939
+ }
940
+ if (requestedTarget === "workspace" && srcDirty) {
941
+ log.warn(
942
+ "src/ c\u0169ng c\xF3 changes ch\u01B0a commit. Ch\u1EA1y 'avatar commit src' (ho\u1EB7c 'avatar commit all') \u0111\u1EC3 commit lu\xF4n."
943
+ );
944
+ }
945
+ }
946
+ async function executeCommitWithTargetSelection(args) {
947
+ assertAvatarWorkspaceRoot(args.workspaceRoot);
948
+ warnIfOtherTargetDirty(args.workspaceRoot, args.target);
949
+ const result = { target: args.target, skipped: [] };
950
+ if (args.target === "src" || args.target === "all") {
951
+ const srcOutcome = await commitSrc(args.workspaceRoot, args.options);
952
+ result.srcCommitSha = srcOutcome.sha;
953
+ result.srcPushed = srcOutcome.pushed;
954
+ if (!srcOutcome.sha) result.skipped?.push("src");
955
+ }
956
+ if (args.target === "workspace" || args.target === "all") {
957
+ const wsOutcome = await commitWorkspace(args.workspaceRoot, args.options);
958
+ result.workspaceCommitSha = wsOutcome.sha;
959
+ result.workspacePushed = wsOutcome.pushed;
960
+ if (!wsOutcome.sha) result.skipped?.push("workspace");
961
+ }
962
+ return result;
860
963
  }
861
964
 
862
965
  // src/commands/commit.ts
863
966
  function registerCommitCommand(program2) {
864
- program2.command("commit").description("Commit code kh\xE1ch (src/) ho\u1EB7c Avatar state ri\xEAng \u2014 ch\u1EC9 client mode (M07)").option("--src", "Commit src/ \u2192 client remote").option("--avatar", "Commit Avatar state \u2192 workspace remote").option("--both", "Commit c\u1EA3 hai (src tr\u01B0\u1EDBc, avatar sau)").option("-m, --message <msg>", "Commit message").option("--push", "T\u1EF1 \u0111\u1ED9ng push sau khi commit").action(notImplementedYet("commit", "Milestone 07"));
967
+ const commit = program2.command("commit").description("Commit code (src/) ho\u1EB7c Avatar state (workspace) \u2014 split-aware (M07)");
968
+ commit.command("src").description("Commit src/ \u2192 push l\xEAn client repo remote").option("-m, --message <msg>", "Commit message").option("--push", "Auto push sau commit (default: ch\u1EC9 commit)").action(async (opts) => {
969
+ await runCommit("src", opts);
970
+ });
971
+ commit.command("workspace").description("Commit workspace root \u2192 push l\xEAn team remote (Avatar state)").option("-m, --message <msg>", "Commit message").option("--push", "Auto push sau commit (default: ch\u1EC9 commit)").action(async (opts) => {
972
+ await runCommit("workspace", opts);
973
+ });
974
+ commit.command("all").description("Commit c\u1EA3 src/ v\xE0 workspace (src tr\u01B0\u1EDBc, gitlink update, workspace sau)").option("-m, --message <msg>", "Commit message").option("--push", "Auto push sau commit (default: ch\u1EC9 commit)").action(async (opts) => {
975
+ await runCommit("all", opts);
976
+ });
977
+ }
978
+ async function runCommit(target, opts) {
979
+ try {
980
+ const message = opts.message ?? await input2({
981
+ message: "Commit message:",
982
+ validate: (v) => v.trim().length > 0 ? true : "Message b\u1EAFt bu\u1ED9c"
983
+ });
984
+ const result = await executeCommitWithTargetSelection({
985
+ workspaceRoot: process.cwd(),
986
+ target,
987
+ options: { message, push: opts.push }
988
+ });
989
+ if (result.srcCommitSha) {
990
+ log.success(`src/: ${result.srcCommitSha.slice(0, 7)}${result.srcPushed ? " (pushed)" : ""}`);
991
+ }
992
+ if (result.workspaceCommitSha) {
993
+ log.success(
994
+ `workspace: ${result.workspaceCommitSha.slice(0, 7)}${result.workspacePushed ? " (pushed)" : ""}`
995
+ );
996
+ }
997
+ if (result.skipped && result.skipped.length > 0) {
998
+ log.dim(`Skipped (nothing to commit): ${result.skipped.join(", ")}`);
999
+ }
1000
+ } catch (err) {
1001
+ log.error(err instanceof Error ? err.message : String(err));
1002
+ process.exit(1);
1003
+ }
865
1004
  }
866
1005
 
867
1006
  // src/commands/doctor.ts
868
- import { spawnSync as spawnSync5 } from "child_process";
1007
+ import { spawnSync as spawnSync6 } from "child_process";
869
1008
  import { promises as fs6 } from "fs";
870
- import { join as join9 } from "path";
1009
+ import { join as join10 } from "path";
871
1010
  import boxen from "boxen";
872
1011
 
873
1012
  // src/lib/git-operations.ts
874
- import { join as join6 } from "path";
1013
+ import { join as join7 } from "path";
875
1014
  import { simpleGit } from "simple-git";
876
1015
  function git(cwd = process.cwd()) {
877
1016
  return simpleGit({ baseDir: cwd, binary: "git" });
878
1017
  }
879
1018
  async function isGitRepo(cwd = process.cwd()) {
880
- return await pathExists(join6(cwd, ".git"));
1019
+ return await pathExists(join7(cwd, ".git"));
881
1020
  }
882
1021
  async function addSubmodule(repoUrl, destPath, cwd = process.cwd()) {
883
1022
  await git(cwd).subModule(["add", repoUrl, destPath]);
884
1023
  }
885
1024
  async function checkoutTagInSubmodule(submodulePath, tag, cwd = process.cwd()) {
886
- const submoduleCwd = join6(cwd, submodulePath);
1025
+ const submoduleCwd = join7(cwd, submodulePath);
887
1026
  await git(submoduleCwd).fetch(["--tags"]);
888
1027
  await git(submoduleCwd).checkout(tag);
889
1028
  }
@@ -902,11 +1041,11 @@ async function currentCommitSha(cwd = process.cwd()) {
902
1041
 
903
1042
  // src/lib/project-tree-scaffolder.ts
904
1043
  import { promises as fs5 } from "fs";
905
- import { join as join8 } from "path";
1044
+ import { join as join9 } from "path";
906
1045
 
907
1046
  // src/lib/template-bundle-loader.ts
908
- import { existsSync } from "fs";
909
- import { dirname as dirname2, join as join7 } from "path";
1047
+ import { existsSync as existsSync2 } from "fs";
1048
+ import { dirname as dirname2, join as join8 } from "path";
910
1049
  import { fileURLToPath } from "url";
911
1050
 
912
1051
  // src/lib/mustache-template-engine.ts
@@ -922,12 +1061,12 @@ function renderTemplate(source, variables) {
922
1061
  // src/lib/template-bundle-loader.ts
923
1062
  var HERE = dirname2(fileURLToPath(import.meta.url));
924
1063
  var PACKAGE_ROOT = findPackageRoot(HERE);
925
- var TEMPLATES_ROOT = join7(PACKAGE_ROOT, "src", "templates");
926
- var HOOKS_ROOT = join7(PACKAGE_ROOT, "src", "hooks");
1064
+ var TEMPLATES_ROOT = join8(PACKAGE_ROOT, "src", "templates");
1065
+ var HOOKS_ROOT = join8(PACKAGE_ROOT, "src", "hooks");
927
1066
  function findPackageRoot(startDir) {
928
1067
  let dir = startDir;
929
1068
  while (true) {
930
- if (existsSync(join7(dir, "package.json"))) return dir;
1069
+ if (existsSync2(join8(dir, "package.json"))) return dir;
931
1070
  const parent = dirname2(dir);
932
1071
  if (parent === dir) {
933
1072
  throw new Error(`Cannot locate package root from ${startDir}`);
@@ -936,14 +1075,14 @@ function findPackageRoot(startDir) {
936
1075
  }
937
1076
  }
938
1077
  async function loadTemplate(name) {
939
- return await readText(join7(TEMPLATES_ROOT, `${name}.tpl`));
1078
+ return await readText(join8(TEMPLATES_ROOT, `${name}.tpl`));
940
1079
  }
941
1080
  async function renderTemplateByName(name, variables) {
942
1081
  const source = await loadTemplate(name);
943
1082
  return renderTemplate(source, variables);
944
1083
  }
945
1084
  async function loadHook(name) {
946
- return await readText(join7(HOOKS_ROOT, `${name}.sh.tpl`));
1085
+ return await readText(join8(HOOKS_ROOT, `${name}.sh.tpl`));
947
1086
  }
948
1087
 
949
1088
  // src/lib/project-tree-scaffolder.ts
@@ -977,12 +1116,12 @@ var PROJECT_KNOWLEDGE_TEMPLATES = [
977
1116
  "project/gotchas.md"
978
1117
  ];
979
1118
  async function createClaudeDirTree(projectRoot) {
980
- const claudeRoot = join8(projectRoot, ".claude");
1119
+ const claudeRoot = join9(projectRoot, ".claude");
981
1120
  await ensureDir(claudeRoot);
982
1121
  for (const sub of CLAUDE_SUBDIRS) {
983
- const dir = join8(claudeRoot, sub);
1122
+ const dir = join9(claudeRoot, sub);
984
1123
  await ensureDir(dir);
985
- await writeTextAtomic(join8(dir, ".gitkeep"), "");
1124
+ await writeTextAtomic(join9(dir, ".gitkeep"), "");
986
1125
  }
987
1126
  }
988
1127
  async function writeProjectKnowledgeFiles(projectRoot, vars) {
@@ -1013,7 +1152,7 @@ async function writeProjectKnowledgeFiles(projectRoot, vars) {
1013
1152
  for (const tpl of PROJECT_KNOWLEDGE_TEMPLATES) {
1014
1153
  const content = await renderTemplateByName(tpl, baseVars);
1015
1154
  const relative4 = tpl.replace(/^project\//, "");
1016
- const outPath = join8(projectRoot, ".claude", "project", relative4);
1155
+ const outPath = join9(projectRoot, ".claude", "project", relative4);
1017
1156
  const backup = await writeWithBackup(outPath, content);
1018
1157
  if (backup) backups.push(backup);
1019
1158
  }
@@ -1021,14 +1160,14 @@ async function writeProjectKnowledgeFiles(projectRoot, vars) {
1021
1160
  }
1022
1161
  async function writeRootClaudeMd(projectRoot, vars) {
1023
1162
  const content = await renderTemplateByName("CLAUDE.md", vars);
1024
- return await writeWithBackup(join8(projectRoot, "CLAUDE.md"), content);
1163
+ return await writeWithBackup(join9(projectRoot, "CLAUDE.md"), content);
1025
1164
  }
1026
1165
  async function writeProjectSettings(projectRoot, vars) {
1027
1166
  const content = await renderTemplateByName("settings.json", vars);
1028
- return await writeWithBackup(join8(projectRoot, ".claude", "settings.json"), content);
1167
+ return await writeWithBackup(join9(projectRoot, ".claude", "settings.json"), content);
1029
1168
  }
1030
1169
  async function appendGitignoreEntries(projectRoot) {
1031
- const path = join8(projectRoot, ".gitignore");
1170
+ const path = join9(projectRoot, ".gitignore");
1032
1171
  const tpl = await renderTemplateByName("gitignore", {});
1033
1172
  const marker = "# Avatar \u2014 git-ignored entries injected on `avatar init`";
1034
1173
  let existing = "";
@@ -1042,9 +1181,9 @@ ${tpl}`);
1042
1181
  }
1043
1182
  async function installGitHook(gitDir, hookName) {
1044
1183
  const content = await loadHook(hookName);
1045
- const hooksDir = join8(gitDir, "hooks");
1184
+ const hooksDir = join9(gitDir, "hooks");
1046
1185
  await ensureDir(hooksDir);
1047
- const dest = join8(hooksDir, hookName);
1186
+ const dest = join9(hooksDir, hookName);
1048
1187
  await writeTextAtomic(dest, content, 493);
1049
1188
  }
1050
1189
 
@@ -1102,7 +1241,7 @@ async function runChecks(cwd) {
1102
1241
  detail: gitRepo ? cwd : "Kh\xF4ng ph\u1EA3i git repo (c\u1EA7n cho 'avatar init')",
1103
1242
  fixable: false
1104
1243
  });
1105
- const packPath = join9(cwd, ".claude", "pack");
1244
+ const packPath = join10(cwd, ".claude", "pack");
1106
1245
  const hasPack = await pathExists(packPath);
1107
1246
  checks.push({
1108
1247
  name: "team-ai-pack submodule",
@@ -1110,7 +1249,7 @@ async function runChecks(cwd) {
1110
1249
  detail: hasPack ? packPath : "Avatar ch\u01B0a init \u2014 ch\u1EA1y 'avatar init'",
1111
1250
  fixable: false
1112
1251
  });
1113
- const claudeMdPath = join9(cwd, "CLAUDE.md");
1252
+ const claudeMdPath = join10(cwd, "CLAUDE.md");
1114
1253
  const hasClaudeMd = await pathExists(claudeMdPath);
1115
1254
  checks.push({
1116
1255
  name: "CLAUDE.md",
@@ -1118,7 +1257,7 @@ async function runChecks(cwd) {
1118
1257
  detail: hasClaudeMd ? "t\u1ED3n t\u1EA1i \u1EDF project root" : "thi\u1EBFu \u2014 ch\u1EA1y 'avatar init'",
1119
1258
  fixable: false
1120
1259
  });
1121
- const hookPath = join9(cwd, ".git", "hooks", "post-merge");
1260
+ const hookPath = join10(cwd, ".git", "hooks", "post-merge");
1122
1261
  const hasHook = await pathExists(hookPath);
1123
1262
  if (gitRepo && hasPack) {
1124
1263
  checks.push({
@@ -1127,11 +1266,11 @@ async function runChecks(cwd) {
1127
1266
  detail: hasHook ? "installed" : "missing \u2014 fixable",
1128
1267
  fixable: !hasHook,
1129
1268
  fix: hasHook ? void 0 : async () => {
1130
- await installGitHook(join9(cwd, ".git"), "post-merge");
1269
+ await installGitHook(join10(cwd, ".git"), "post-merge");
1131
1270
  }
1132
1271
  });
1133
1272
  }
1134
- const gitignorePath = join9(cwd, ".gitignore");
1273
+ const gitignorePath = join10(cwd, ".gitignore");
1135
1274
  if (gitRepo) {
1136
1275
  let gitignoreOk = false;
1137
1276
  if (await pathExists(gitignorePath)) {
@@ -1145,7 +1284,7 @@ async function runChecks(cwd) {
1145
1284
  fixable: false
1146
1285
  });
1147
1286
  }
1148
- const which = spawnSync5("which", ["claude"]);
1287
+ const which = spawnSync6("which", ["claude"]);
1149
1288
  const hasClaudeCli = which.status === 0;
1150
1289
  checks.push({
1151
1290
  name: "Claude Code CLI",
@@ -1193,16 +1332,16 @@ async function applyFixes(checks) {
1193
1332
  }
1194
1333
 
1195
1334
  // src/commands/init.ts
1196
- import { basename, join as join16, relative as relative2, resolve } from "path";
1197
- import { confirm as confirm3, input as input4, select as select8 } from "@inquirer/prompts";
1335
+ import { basename, join as join17, relative as relative2, resolve } from "path";
1336
+ import { confirm as confirm3, input as input5, select as select8 } from "@inquirer/prompts";
1198
1337
  import boxen4 from "boxen";
1199
1338
 
1200
1339
  // src/lib/add-team-pack-submodule-with-retry-on-network-fail.ts
1201
- import { spawnSync as spawnSync7 } from "child_process";
1340
+ import { spawnSync as spawnSync8 } from "child_process";
1202
1341
  import { select as select5 } from "@inquirer/prompts";
1203
1342
 
1204
1343
  // src/lib/prompt-recovery-action-on-failure.ts
1205
- import { input as input2, select as select3 } from "@inquirer/prompts";
1344
+ import { input as input3, select as select3 } from "@inquirer/prompts";
1206
1345
  var UserAbortedRecoveryError = class extends Error {
1207
1346
  constructor(message) {
1208
1347
  super(message);
@@ -1226,10 +1365,10 @@ async function promptRetryOrSkip(args) {
1226
1365
  }
1227
1366
 
1228
1367
  // src/lib/team-pack-submodule-manager.ts
1229
- import { join as join10 } from "path";
1368
+ import { join as join11 } from "path";
1230
1369
 
1231
1370
  // src/lib/check-team-pack-access-with-retry-loop.ts
1232
- import { spawnSync as spawnSync6 } from "child_process";
1371
+ import { spawnSync as spawnSync7 } from "child_process";
1233
1372
  import { confirm as confirm2, select as select4 } from "@inquirer/prompts";
1234
1373
  import boxen2 from "boxen";
1235
1374
  function parseRepoSlugFromGitUrl(url) {
@@ -1238,11 +1377,11 @@ function parseRepoSlugFromGitUrl(url) {
1238
1377
  return null;
1239
1378
  }
1240
1379
  function checkRepoAccess(repoSlug) {
1241
- const r = spawnSync6("gh", ["api", `repos/${repoSlug}`], { stdio: "ignore" });
1380
+ const r = spawnSync7("gh", ["api", `repos/${repoSlug}`], { stdio: "ignore" });
1242
1381
  return r.status === 0;
1243
1382
  }
1244
1383
  function getCurrentGhUser() {
1245
- const r = spawnSync6("gh", ["api", "user", "--jq", ".login"], {
1384
+ const r = spawnSync7("gh", ["api", "user", "--jq", ".login"], {
1246
1385
  encoding: "utf8",
1247
1386
  stdio: ["ignore", "pipe", "pipe"]
1248
1387
  });
@@ -1251,7 +1390,7 @@ function getCurrentGhUser() {
1251
1390
  }
1252
1391
  function triggerGhAuthLoginInteractive() {
1253
1392
  log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
1254
- const r = spawnSync6("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1393
+ const r = spawnSync7("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1255
1394
  if (r.status !== 0) {
1256
1395
  log.warn(`gh auth login exit ${r.status}. C\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
1257
1396
  }
@@ -1391,7 +1530,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
1391
1530
  }
1392
1531
  let target = tag ?? null;
1393
1532
  if (!target) {
1394
- target = await latestTag(join10(projectRoot, TEAM_PACK_RELATIVE_PATH));
1533
+ target = await latestTag(join11(projectRoot, TEAM_PACK_RELATIVE_PATH));
1395
1534
  }
1396
1535
  if (target) {
1397
1536
  await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, target, projectRoot);
@@ -1399,7 +1538,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
1399
1538
  return { pinnedTag: target };
1400
1539
  }
1401
1540
  async function readPinnedPackVersion(projectRoot) {
1402
- const submoduleRoot = join10(projectRoot, TEAM_PACK_RELATIVE_PATH);
1541
+ const submoduleRoot = join11(projectRoot, TEAM_PACK_RELATIVE_PATH);
1403
1542
  const tag = await latestTag(submoduleRoot);
1404
1543
  if (tag) return tag;
1405
1544
  const sha = await currentCommitSha(submoduleRoot);
@@ -1413,14 +1552,14 @@ function isSshPermissionError(message) {
1413
1552
  }
1414
1553
  function triggerGhAuthLoginInteractive2() {
1415
1554
  log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
1416
- const r = spawnSync7("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1555
+ const r = spawnSync8("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1417
1556
  if (r.status !== 0) {
1418
1557
  log.warn(`gh auth login exit ${r.status}. C\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
1419
1558
  }
1420
1559
  }
1421
1560
  function openGithubSshKeysPage() {
1422
1561
  log.info("M\u1EDF trang GitHub Settings \u2192 SSH Keys...");
1423
- const r = spawnSync7("open", ["https://github.com/settings/keys"], { stdio: "ignore" });
1562
+ const r = spawnSync8("open", ["https://github.com/settings/keys"], { stdio: "ignore" });
1424
1563
  if (r.status !== 0) {
1425
1564
  log.info("URL: https://github.com/settings/keys");
1426
1565
  }
@@ -1570,27 +1709,27 @@ ${renderAvatarBanner(opts)}
1570
1709
  }
1571
1710
 
1572
1711
  // src/lib/execute-gh-repo-create.ts
1573
- import { spawnSync as spawnSync8 } from "child_process";
1712
+ import { spawnSync as spawnSync9 } from "child_process";
1574
1713
  var RepoAlreadyExistsError = class extends Error {
1575
1714
  constructor(fullName) {
1576
1715
  super(`Repo "${fullName}" \u0111\xE3 t\u1ED3n t\u1EA1i tr\xEAn GitHub. \u0110\u1ED5i t\xEAn ho\u1EB7c x\xF3a repo c\u0169.`);
1577
1716
  this.name = "RepoAlreadyExistsError";
1578
1717
  }
1579
1718
  };
1580
- function executeGhRepoCreate(input5) {
1581
- const fullName = `${input5.org}/${input5.name}`;
1719
+ function executeGhRepoCreate(input6) {
1720
+ const fullName = `${input6.org}/${input6.name}`;
1582
1721
  const args = [
1583
1722
  "repo",
1584
1723
  "create",
1585
1724
  fullName,
1586
- `--${input5.visibility}`,
1725
+ `--${input6.visibility}`,
1587
1726
  "--source",
1588
- input5.folder,
1727
+ input6.folder,
1589
1728
  "--remote",
1590
1729
  "origin",
1591
1730
  "--push"
1592
1731
  ];
1593
- const r = spawnSync8("gh", args, { stdio: "inherit" });
1732
+ const r = spawnSync9("gh", args, { stdio: "inherit" });
1594
1733
  if (r.status !== 0) {
1595
1734
  if (r.status === 1) {
1596
1735
  throw new RepoAlreadyExistsError(fullName);
@@ -1604,9 +1743,9 @@ function executeGhRepoCreate(input5) {
1604
1743
  }
1605
1744
 
1606
1745
  // src/lib/resolve-github-username-default.ts
1607
- import { spawnSync as spawnSync9 } from "child_process";
1746
+ import { spawnSync as spawnSync10 } from "child_process";
1608
1747
  function resolveGithubUsernameDefault() {
1609
- const r = spawnSync9("gh", ["api", "user", "--jq", ".login"], {
1748
+ const r = spawnSync10("gh", ["api", "user", "--jq", ".login"], {
1610
1749
  encoding: "utf8",
1611
1750
  stdio: ["ignore", "pipe", "pipe"]
1612
1751
  });
@@ -1638,28 +1777,28 @@ function validateRepoVisibility(v) {
1638
1777
  }
1639
1778
 
1640
1779
  // src/lib/create-github-remote-from-folder.ts
1641
- function createGithubRemoteFromFolder(input5) {
1642
- validateRepoName(input5.name);
1643
- validateRepoVisibility(input5.visibility);
1644
- const org = input5.org ?? resolveGithubUsernameDefault();
1645
- log.info(`T\u1EA1o GitHub repo ${org}/${input5.name} (${input5.visibility})...`);
1780
+ function createGithubRemoteFromFolder(input6) {
1781
+ validateRepoName(input6.name);
1782
+ validateRepoVisibility(input6.visibility);
1783
+ const org = input6.org ?? resolveGithubUsernameDefault();
1784
+ log.info(`T\u1EA1o GitHub repo ${org}/${input6.name} (${input6.visibility})...`);
1646
1785
  const urls = executeGhRepoCreate({
1647
- folder: input5.folder,
1786
+ folder: input6.folder,
1648
1787
  org,
1649
- name: input5.name,
1650
- visibility: input5.visibility
1788
+ name: input6.name,
1789
+ visibility: input6.visibility
1651
1790
  });
1652
1791
  log.success(`\u0110\xE3 t\u1EA1o: ${urls.sshUrl}`);
1653
1792
  return urls;
1654
1793
  }
1655
1794
 
1656
1795
  // src/lib/create-workspace-remote-via-gh.ts
1657
- import { spawnSync as spawnSync17 } from "child_process";
1796
+ import { spawnSync as spawnSync18 } from "child_process";
1658
1797
 
1659
1798
  // src/lib/check-gh-cli-auth-status.ts
1660
- import { spawnSync as spawnSync10 } from "child_process";
1799
+ import { spawnSync as spawnSync11 } from "child_process";
1661
1800
  function checkGhCliAuthStatus() {
1662
- const r = spawnSync10("gh", ["auth", "status"], { stdio: "ignore" });
1801
+ const r = spawnSync11("gh", ["auth", "status"], { stdio: "ignore" });
1663
1802
  if (r.error && r.error.code === "ENOENT") {
1664
1803
  return "not-installed";
1665
1804
  }
@@ -1667,12 +1806,12 @@ function checkGhCliAuthStatus() {
1667
1806
  }
1668
1807
 
1669
1808
  // src/lib/detect-package-manager.ts
1670
- import { spawnSync as spawnSync11 } from "child_process";
1809
+ import { spawnSync as spawnSync12 } from "child_process";
1671
1810
  function hasBinary(name) {
1672
1811
  const platform2 = detectHostPlatform();
1673
1812
  const probe = platform2 === "win32" ? "where" : "command";
1674
1813
  const args = platform2 === "win32" ? [name] : ["-v", name];
1675
- const r = spawnSync11(probe, args, {
1814
+ const r = spawnSync12(probe, args, {
1676
1815
  shell: platform2 !== "win32",
1677
1816
  stdio: "ignore"
1678
1817
  });
@@ -1688,11 +1827,11 @@ function detectPackageManager() {
1688
1827
  }
1689
1828
 
1690
1829
  // src/lib/handle-remote-access-failure-with-account-switch.ts
1691
- import { spawnSync as spawnSync13 } from "child_process";
1692
- import { input as input3, select as select6 } from "@inquirer/prompts";
1830
+ import { spawnSync as spawnSync14 } from "child_process";
1831
+ import { input as input4, select as select6 } from "@inquirer/prompts";
1693
1832
 
1694
1833
  // src/lib/verify-git-remote-accessible.ts
1695
- import { spawnSync as spawnSync12 } from "child_process";
1834
+ import { spawnSync as spawnSync13 } from "child_process";
1696
1835
  var TIMEOUT_MS = 5e3;
1697
1836
  function classifyRemoteError(stderr) {
1698
1837
  const text = stderr.toLowerCase();
@@ -1708,7 +1847,7 @@ function classifyRemoteError(stderr) {
1708
1847
  return "unknown";
1709
1848
  }
1710
1849
  function tryVerifyGitRemoteAccessible(url) {
1711
- const r = spawnSync12("git", ["ls-remote", "--exit-code", url, "HEAD"], {
1850
+ const r = spawnSync13("git", ["ls-remote", "--exit-code", url, "HEAD"], {
1712
1851
  encoding: "utf8",
1713
1852
  timeout: TIMEOUT_MS,
1714
1853
  stdio: ["ignore", "pipe", "pipe"]
@@ -1730,7 +1869,7 @@ var RemoteAccessAbortedError = class extends Error {
1730
1869
  }
1731
1870
  };
1732
1871
  function getCurrentGhUser2() {
1733
- const r = spawnSync13("gh", ["api", "user", "--jq", ".login"], {
1872
+ const r = spawnSync14("gh", ["api", "user", "--jq", ".login"], {
1734
1873
  encoding: "utf8",
1735
1874
  stdio: ["ignore", "pipe", "pipe"]
1736
1875
  });
@@ -1739,7 +1878,7 @@ function getCurrentGhUser2() {
1739
1878
  }
1740
1879
  function triggerGhAuthLoginInteractive3() {
1741
1880
  log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
1742
- const r = spawnSync13("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1881
+ const r = spawnSync14("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1743
1882
  if (r.status !== 0) {
1744
1883
  log.warn(`gh auth login exit ${r.status}. B\u1EA1n c\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
1745
1884
  }
@@ -1800,7 +1939,7 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
1800
1939
  );
1801
1940
  }
1802
1941
  if (action === "re-input-url") {
1803
- const newUrl = await input3({
1942
+ const newUrl = await input4({
1804
1943
  message: "URL git remote (https://github.com/owner/repo.git ho\u1EB7c git@github.com:owner/repo.git):",
1805
1944
  default: currentUrl,
1806
1945
  validate: (v) => isValidGitUrl(v) || "URL kh\xF4ng \u0111\xFAng format git remote"
@@ -1822,7 +1961,7 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
1822
1961
  }
1823
1962
 
1824
1963
  // src/lib/install-gh-cli-via-package-manager.ts
1825
- import { spawnSync as spawnSync14 } from "child_process";
1964
+ import { spawnSync as spawnSync15 } from "child_process";
1826
1965
  var INSTALL_COMMANDS = {
1827
1966
  brew: { cmd: "brew", args: ["install", "gh"] },
1828
1967
  apt: { cmd: "sudo", args: ["apt-get", "install", "-y", "gh"] },
@@ -1833,7 +1972,7 @@ var INSTALL_COMMANDS = {
1833
1972
  function installGhCliViaPackageManager(pm) {
1834
1973
  const spec = INSTALL_COMMANDS[pm];
1835
1974
  log.info(`\u0110ang c\xE0i gh CLI qua ${pm}...`);
1836
- const r = spawnSync14(spec.cmd, spec.args, { stdio: "inherit" });
1975
+ const r = spawnSync15(spec.cmd, spec.args, { stdio: "inherit" });
1837
1976
  if (r.status !== 0) {
1838
1977
  throw new Error(`C\xE0i gh CLI th\u1EA5t b\u1EA1i qua ${pm} (exit ${r.status}). C\xE0i tay r\u1ED3i ch\u1EA1y l\u1EA1i.`);
1839
1978
  }
@@ -1841,9 +1980,9 @@ function installGhCliViaPackageManager(pm) {
1841
1980
  }
1842
1981
 
1843
1982
  // src/lib/setup-git-credential-via-gh.ts
1844
- import { spawnSync as spawnSync15 } from "child_process";
1983
+ import { spawnSync as spawnSync16 } from "child_process";
1845
1984
  function setupGitCredentialViaGh() {
1846
- const r = spawnSync15("gh", ["auth", "setup-git"], { stdio: "ignore" });
1985
+ const r = spawnSync16("gh", ["auth", "setup-git"], { stdio: "ignore" });
1847
1986
  if (r.status !== 0) {
1848
1987
  log.warn("gh auth setup-git fail (non-fatal). N\u1EBFu git clone l\u1ED7i 128 \u2192 ch\u1EA1y th\u1EE7 c\xF4ng.");
1849
1988
  return;
@@ -1852,10 +1991,10 @@ function setupGitCredentialViaGh() {
1852
1991
  }
1853
1992
 
1854
1993
  // src/lib/trigger-gh-cli-auth-login.ts
1855
- import { spawnSync as spawnSync16 } from "child_process";
1994
+ import { spawnSync as spawnSync17 } from "child_process";
1856
1995
  function triggerGhCliAuthLogin() {
1857
1996
  log.info("Kh\u1EDFi \u0111\u1ED9ng \u0111\u0103ng nh\u1EADp GitHub qua gh CLI (browser s\u1EBD m\u1EDF)...");
1858
- const r = spawnSync16(
1997
+ const r = spawnSync17(
1859
1998
  "gh",
1860
1999
  ["auth", "login", "--hostname", "github.com", "--web", "--git-protocol", "ssh"],
1861
2000
  { stdio: "inherit" }
@@ -1973,20 +2112,20 @@ function classifyGhCreateError(stderr) {
1973
2112
  return "unknown";
1974
2113
  }
1975
2114
  function repoExistsOnGitHub(fullName) {
1976
- const r = spawnSync17("gh", ["repo", "view", fullName, "--json", "name"], {
2115
+ const r = spawnSync18("gh", ["repo", "view", fullName, "--json", "name"], {
1977
2116
  stdio: "ignore"
1978
2117
  });
1979
2118
  return r.status === 0;
1980
2119
  }
1981
2120
  function canCreateInNamespace(org, ghUser) {
1982
2121
  if (org.toLowerCase() === ghUser.toLowerCase()) return { ok: true };
1983
- const r = spawnSync17("gh", ["api", `orgs/${org}/members/${ghUser}`, "--silent"], {
2122
+ const r = spawnSync18("gh", ["api", `orgs/${org}/members/${ghUser}`, "--silent"], {
1984
2123
  stdio: "ignore"
1985
2124
  });
1986
2125
  if (r.status === 0) return { ok: true };
1987
- const orgCheck = spawnSync17("gh", ["api", `orgs/${org}`, "--silent"], { stdio: "ignore" });
2126
+ const orgCheck = spawnSync18("gh", ["api", `orgs/${org}`, "--silent"], { stdio: "ignore" });
1988
2127
  if (orgCheck.status !== 0) {
1989
- const userCheck = spawnSync17("gh", ["api", `users/${org}`, "--silent"], { stdio: "ignore" });
2128
+ const userCheck = spawnSync18("gh", ["api", `users/${org}`, "--silent"], { stdio: "ignore" });
1990
2129
  if (userCheck.status === 0) {
1991
2130
  return {
1992
2131
  ok: false,
@@ -2003,17 +2142,17 @@ function canCreateInNamespace(org, ghUser) {
2003
2142
  reason: `'${ghUser}' kh\xF4ng ph\u1EA3i member c\u1EE7a org '${org}'. Li\xEAn h\u1EC7 admin org \u0111\u1EC3 \u0111\u01B0\u1EE3c invite, ho\u1EB7c pass --repo-org=${ghUser} t\u1EA1o d\u01B0\u1EDBi personal account.`
2004
2143
  };
2005
2144
  }
2006
- async function createWorkspaceRemoteViaGh(input5) {
2007
- validateRepoName(input5.workspaceName);
2008
- validateRepoVisibility(input5.visibility);
2145
+ async function createWorkspaceRemoteViaGh(input6) {
2146
+ validateRepoName(input6.workspaceName);
2147
+ validateRepoVisibility(input6.visibility);
2009
2148
  await ensureGitHubReady();
2010
2149
  const ghUser = resolveGithubUsernameDefault();
2011
- const org = input5.org ?? ghUser;
2150
+ const org = input6.org ?? ghUser;
2012
2151
  const namespaceCheck = canCreateInNamespace(org, ghUser);
2013
2152
  if (!namespaceCheck.ok) {
2014
2153
  throw new Error(`Kh\xF4ng th\u1EC3 t\u1EA1o repo d\u01B0\u1EDBi '${org}/': ${namespaceCheck.reason}`);
2015
2154
  }
2016
- const fullName = `${org}/${input5.workspaceName}`;
2155
+ const fullName = `${org}/${input6.workspaceName}`;
2017
2156
  if (repoExistsOnGitHub(fullName)) {
2018
2157
  throw new CreateWorkspaceRemoteError(
2019
2158
  "repo-exists",
@@ -2021,16 +2160,16 @@ async function createWorkspaceRemoteViaGh(input5) {
2021
2160
  `Repo '${fullName}' \u0111\xE3 t\u1ED3n t\u1EA1i tr\xEAn GitHub. C\xF3 th\u1EC3 b\u1EA1n \u0111\xE3 init workspace n\xE0y tr\u01B0\u1EDBc \u0111\xF3.`
2022
2161
  );
2023
2162
  }
2024
- log.info(`T\u1EA1o GitHub repo cho workspace: ${fullName} (${input5.visibility})...`);
2025
- const r = spawnSync17(
2163
+ log.info(`T\u1EA1o GitHub repo cho workspace: ${fullName} (${input6.visibility})...`);
2164
+ const r = spawnSync18(
2026
2165
  "gh",
2027
2166
  [
2028
2167
  "repo",
2029
2168
  "create",
2030
2169
  fullName,
2031
- `--${input5.visibility}`,
2170
+ `--${input6.visibility}`,
2032
2171
  "--source",
2033
- input5.workspacePath,
2172
+ input6.workspacePath,
2034
2173
  "--remote",
2035
2174
  "origin",
2036
2175
  "--push"
@@ -2063,7 +2202,7 @@ ${combined}
2063
2202
  function linkExistingRemoteToWorkspace(args) {
2064
2203
  const sshUrl = `git@github.com:${args.fullName}.git`;
2065
2204
  const httpsUrl = `https://github.com/${args.fullName}.git`;
2066
- const addResult = spawnSync17(
2205
+ const addResult = spawnSync18(
2067
2206
  "git",
2068
2207
  ["-C", args.workspacePath, "remote", "add", "origin", sshUrl],
2069
2208
  {
@@ -2072,7 +2211,7 @@ function linkExistingRemoteToWorkspace(args) {
2072
2211
  }
2073
2212
  );
2074
2213
  if (addResult.status !== 0) {
2075
- spawnSync17("git", ["-C", args.workspacePath, "remote", "set-url", "origin", sshUrl], {
2214
+ spawnSync18("git", ["-C", args.workspacePath, "remote", "set-url", "origin", sshUrl], {
2076
2215
  stdio: "ignore"
2077
2216
  });
2078
2217
  }
@@ -2086,11 +2225,11 @@ import { select as select7 } from "@inquirer/prompts";
2086
2225
  import { simpleGit as simpleGit3 } from "simple-git";
2087
2226
 
2088
2227
  // src/lib/check-folder-has-git.ts
2089
- import { existsSync as existsSync2, statSync } from "fs";
2090
- import { join as join11 } from "path";
2228
+ import { existsSync as existsSync3, statSync } from "fs";
2229
+ import { join as join12 } from "path";
2091
2230
  function checkFolderHasGit(folderPath) {
2092
- const gitPath = join11(folderPath, ".git");
2093
- if (!existsSync2(gitPath)) return false;
2231
+ const gitPath = join12(folderPath, ".git");
2232
+ if (!existsSync3(gitPath)) return false;
2094
2233
  const stat = statSync(gitPath);
2095
2234
  return stat.isDirectory() || stat.isFile();
2096
2235
  }
@@ -2120,8 +2259,8 @@ async function createInitialGitCommit(folderPath) {
2120
2259
  }
2121
2260
 
2122
2261
  // src/lib/detect-folder-tech-stack.ts
2123
- import { existsSync as existsSync3 } from "fs";
2124
- import { join as join12 } from "path";
2262
+ import { existsSync as existsSync4 } from "fs";
2263
+ import { join as join13 } from "path";
2125
2264
  var SIGNATURES = {
2126
2265
  node: ["package.json"],
2127
2266
  python: ["pyproject.toml", "requirements.txt", "setup.py", "Pipfile"],
@@ -2133,7 +2272,7 @@ var SIGNATURES = {
2133
2272
  function detectFolderTechStack(folderPath) {
2134
2273
  const matched = [];
2135
2274
  for (const [stack, files] of Object.entries(SIGNATURES)) {
2136
- if (files.some((f) => existsSync3(join12(folderPath, f)))) {
2275
+ if (files.some((f) => existsSync4(join13(folderPath, f)))) {
2137
2276
  matched.push(stack);
2138
2277
  }
2139
2278
  }
@@ -2142,25 +2281,25 @@ function detectFolderTechStack(folderPath) {
2142
2281
 
2143
2282
  // src/lib/gitignore-template-loader.ts
2144
2283
  import { readFileSync as readFileSync2 } from "fs";
2145
- import { dirname as dirname3, join as join13 } from "path";
2284
+ import { dirname as dirname3, join as join14 } from "path";
2146
2285
  import { fileURLToPath as fileURLToPath2 } from "url";
2147
2286
  var __dirname = dirname3(fileURLToPath2(import.meta.url));
2148
2287
  var CANDIDATE_DIRS = [
2149
2288
  // Bundled production: dist/index.js → __dirname = .../dist/, sibling dist/templates
2150
- join13(__dirname, "templates", "gitignore"),
2289
+ join14(__dirname, "templates", "gitignore"),
2151
2290
  // Legacy bundled: nếu file là dist/lib/*.js (sub-bundle), templates ở dist/templates
2152
- join13(__dirname, "..", "templates", "gitignore"),
2291
+ join14(__dirname, "..", "templates", "gitignore"),
2153
2292
  // Dev mode (vitest/tsx run src/ trực tiếp): __dirname = src/lib/
2154
- join13(__dirname, "..", "..", "src", "templates", "gitignore"),
2293
+ join14(__dirname, "..", "..", "src", "templates", "gitignore"),
2155
2294
  // npm-installed alt: __dirname = .../dist/ → package_root/src/templates
2156
- join13(__dirname, "..", "src", "templates", "gitignore")
2295
+ join14(__dirname, "..", "src", "templates", "gitignore")
2157
2296
  ];
2158
2297
  var AVATAR_MARKER_START = "# === avatar ===";
2159
2298
  var AVATAR_MARKER_END = "# === /avatar ===";
2160
2299
  function readTemplate(stack) {
2161
2300
  for (const dir of CANDIDATE_DIRS) {
2162
2301
  try {
2163
- return readFileSync2(join13(dir, `${stack}.txt`), "utf8");
2302
+ return readFileSync2(join14(dir, `${stack}.txt`), "utf8");
2164
2303
  } catch {
2165
2304
  }
2166
2305
  }
@@ -2174,11 +2313,11 @@ ${readTemplate(s).trim()}`);
2174
2313
  }
2175
2314
 
2176
2315
  // src/lib/write-or-merge-gitignore.ts
2177
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync } from "fs";
2178
- import { join as join14 } from "path";
2316
+ import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync } from "fs";
2317
+ import { join as join15 } from "path";
2179
2318
  function writeOrMergeGitignore(folderPath, avatarBlock) {
2180
- const path = join14(folderPath, ".gitignore");
2181
- if (!existsSync4(path)) {
2319
+ const path = join15(folderPath, ".gitignore");
2320
+ if (!existsSync5(path)) {
2182
2321
  writeFileSync(path, avatarBlock, "utf8");
2183
2322
  return;
2184
2323
  }
@@ -2354,7 +2493,7 @@ async function safeBootstrapGitInFolder(folderPath, opts = {}) {
2354
2493
 
2355
2494
  // src/commands/init-conflict-detection-helpers.ts
2356
2495
  import { readdir } from "fs/promises";
2357
- import { join as join15 } from "path";
2496
+ import { join as join16 } from "path";
2358
2497
  async function isEmptyOrMissing(path) {
2359
2498
  if (!await pathExists(path)) return true;
2360
2499
  try {
@@ -2367,7 +2506,7 @@ async function isEmptyOrMissing(path) {
2367
2506
  }
2368
2507
  async function findAlternativeWorkspaceName(parent, desiredName, maxAttempts = 10) {
2369
2508
  for (let i = 2; i < maxAttempts; i++) {
2370
- const candidate = join15(parent, `${desiredName}-${i}`);
2509
+ const candidate = join16(parent, `${desiredName}-${i}`);
2371
2510
  if (await isEmptyOrMissing(candidate)) return candidate;
2372
2511
  }
2373
2512
  return null;
@@ -2678,7 +2817,7 @@ async function promptProjectStatus() {
2678
2817
  });
2679
2818
  }
2680
2819
  async function runInitFromExistingRemote(opts, ownerEmail) {
2681
- const initialRemoteUrl = opts.clientRepo ?? await input4({
2820
+ const initialRemoteUrl = opts.clientRepo ?? await input5({
2682
2821
  message: "URL git c\u1EE7a repo:",
2683
2822
  validate: (v) => v.length > 0 ? true : "URL b\u1EAFt bu\u1ED9c"
2684
2823
  });
@@ -2686,7 +2825,7 @@ async function runInitFromExistingRemote(opts, ownerEmail) {
2686
2825
  const remoteUrl = resolvedRemoteUrl ?? initialRemoteUrl;
2687
2826
  const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
2688
2827
  const inferredName = inferWorkspaceName(remoteUrl);
2689
- const workspaceName = opts.workspaceName ?? await input4({ message: "T\xEAn workspace:", default: inferredName });
2828
+ const workspaceName = opts.workspaceName ?? await input5({ message: "T\xEAn workspace:", default: inferredName });
2690
2829
  const workspaceParent = resolve(opts.workspaceParent ?? ".");
2691
2830
  const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);
2692
2831
  await scaffoldWorkspaceWithSrcSubmodule({
@@ -2709,7 +2848,7 @@ async function runInitFromExistingRemote(opts, ownerEmail) {
2709
2848
  }
2710
2849
  async function runInitFromExistingFolder(opts, ownerEmail) {
2711
2850
  const folderPath = resolve(
2712
- opts.folderPath ?? await input4({
2851
+ opts.folderPath ?? await input5({
2713
2852
  message: "\u0110\u01B0\u1EDDng d\u1EABn folder hi\u1EC7n c\xF3:",
2714
2853
  validate: (v) => v.length > 0 ? true : "Path b\u1EAFt bu\u1ED9c"
2715
2854
  })
@@ -2721,7 +2860,7 @@ async function runInitFromExistingFolder(opts, ownerEmail) {
2721
2860
  const remoteUrl = await getOrCreateOriginRemote(folderPath, opts);
2722
2861
  const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
2723
2862
  const inferredName = opts.workspaceName ?? `${basename(folderPath)}-avatar-workspace`;
2724
- const workspaceName = opts.workspaceName ?? await input4({ message: "T\xEAn workspace:", default: inferredName });
2863
+ const workspaceName = opts.workspaceName ?? await input5({ message: "T\xEAn workspace:", default: inferredName });
2725
2864
  const workspaceParent = resolve(opts.workspaceParent ?? ".");
2726
2865
  const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);
2727
2866
  await scaffoldWorkspaceWithSrcSubmodule({
@@ -2745,7 +2884,7 @@ async function runInitFromExistingFolder(opts, ownerEmail) {
2745
2884
  }
2746
2885
  async function runInitFromScratch(opts, ownerEmail) {
2747
2886
  await ensureGitHubReady();
2748
- const projectName = opts.workspaceName ?? await input4({
2887
+ const projectName = opts.workspaceName ?? await input5({
2749
2888
  message: "T\xEAn d\u1EF1 \xE1n:",
2750
2889
  validate: (v) => v.length > 0 ? true : "T\xEAn b\u1EAFt bu\u1ED9c"
2751
2890
  });
@@ -2759,7 +2898,7 @@ async function runInitFromScratch(opts, ownerEmail) {
2759
2898
  const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
2760
2899
  const workspaceParent = resolve(opts.workspaceParent ?? ".");
2761
2900
  const workspacePath = await resolveWorkspacePath(workspaceParent, projectName, opts.force);
2762
- const srcPath = join16(workspacePath, "src");
2901
+ const srcPath = join17(workspacePath, "src");
2763
2902
  await ensureDir(workspacePath);
2764
2903
  await ensureDir(srcPath);
2765
2904
  await safeBootstrapGitInFolder(srcPath, { autoYes: true });
@@ -2830,7 +2969,7 @@ async function getOrCreateOriginRemote(folderPath, opts) {
2830
2969
  { name: "public", value: "public" }
2831
2970
  ]
2832
2971
  });
2833
- const repoName = await input4({
2972
+ const repoName = await input5({
2834
2973
  message: "T\xEAn repo:",
2835
2974
  default: basename(folderPath)
2836
2975
  });
@@ -2895,10 +3034,10 @@ async function finalizeWorkspaceScaffold(args) {
2895
3034
  await writeRootClaudeMd(args.workspacePath, vars);
2896
3035
  await writeProjectSettings(args.workspacePath, vars);
2897
3036
  await appendGitignoreEntries(args.workspacePath);
2898
- await ensureDir(join16(args.workspacePath, "notes"));
2899
- await ensureDir(join16(args.workspacePath, "scripts"));
2900
- await installGitHook(join16(args.workspacePath, ".git"), "post-merge");
2901
- await installGitHook(join16(args.workspacePath, ".git", "modules", "src"), "pre-push");
3037
+ await ensureDir(join17(args.workspacePath, "notes"));
3038
+ await ensureDir(join17(args.workspacePath, "scripts"));
3039
+ await installGitHook(join17(args.workspacePath, ".git"), "post-merge");
3040
+ await installGitHook(join17(args.workspacePath, ".git", "modules", "src"), "pre-push");
2902
3041
  log.success("C\xE0i post-merge (workspace) + pre-push (src/)");
2903
3042
  await appendAuditEntry("init", `flow=${args.flow},workspace=${args.workspaceName}`);
2904
3043
  await maybeCommitWorkspace(args.workspacePath, args.skipCommit);
@@ -2973,7 +3112,7 @@ async function maybeCreateWorkspaceRemote(args) {
2973
3112
  });
2974
3113
  return;
2975
3114
  }
2976
- const newName = await input4({
3115
+ const newName = await input5({
2977
3116
  message: "T\xEAn workspace m\u1EDBi (s\u1EBD t\u1EA1o repo new):",
2978
3117
  validate: (v) => v.trim().length > 0 ? true : "T\xEAn kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng"
2979
3118
  });
@@ -2998,7 +3137,7 @@ async function maybeCreateWorkspaceRemote(args) {
2998
3137
  }
2999
3138
  }
3000
3139
  async function resolveWorkspacePath(parent, desiredName, force) {
3001
- const desired = join16(parent, desiredName);
3140
+ const desired = join17(parent, desiredName);
3002
3141
  if (await isEmptyOrMissing(desired)) return desired;
3003
3142
  log.warn(`Workspace path "${desired}" \u0111\xE3 c\xF3 n\u1ED9i dung.`);
3004
3143
  while (true) {
@@ -3025,17 +3164,17 @@ async function resolveWorkspacePath(parent, desiredName, force) {
3025
3164
  if (action === "use-alt" && alternative) {
3026
3165
  return alternative;
3027
3166
  }
3028
- const newName = await input4({
3167
+ const newName = await input5({
3029
3168
  message: "T\xEAn workspace m\u1EDBi:",
3030
3169
  validate: (v) => v.trim().length > 0 ? true : "T\xEAn kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng"
3031
3170
  });
3032
- const newPath = join16(parent, newName.trim());
3171
+ const newPath = join17(parent, newName.trim());
3033
3172
  if (await isEmptyOrMissing(newPath)) return newPath;
3034
3173
  log.warn(`"${newPath}" c\u0169ng \u0111\xE3 c\xF3 n\u1ED9i dung. Th\u1EED t\xEAn kh\xE1c.`);
3035
3174
  }
3036
3175
  }
3037
3176
  async function promptTeamOwner(currentUserEmail) {
3038
- return await input4({ message: "Team owner email:", default: currentUserEmail });
3177
+ return await input5({ message: "Team owner email:", default: currentUserEmail });
3039
3178
  }
3040
3179
  async function maybeCommitWorkspace(workspacePath, skipCommit) {
3041
3180
  if (skipCommit) {
@@ -3075,6 +3214,22 @@ function printInitSuccessBox(rootPath, flow, aiResult = null) {
3075
3214
  `);
3076
3215
  }
3077
3216
 
3217
+ // src/lib/not-implemented-stub.ts
3218
+ function notImplementedYet(commandName, milestone) {
3219
+ return () => {
3220
+ process.stdout.write(
3221
+ `${chalk.yellow("\u23F3")} ${chalk.bold(`avatar ${commandName}`)} \u2014 ch\u01B0a implement \u1EDF milestone hi\u1EC7n t\u1EA1i.
3222
+ `
3223
+ );
3224
+ if (milestone) {
3225
+ process.stdout.write(` D\u1EF1 ki\u1EBFn: ${chalk.cyan(milestone)}
3226
+ `);
3227
+ }
3228
+ process.stdout.write(" Spec \u0111\xE3 c\xF3 trong avatar-cli-implementation_4.html.\n");
3229
+ process.exit(0);
3230
+ };
3231
+ }
3232
+
3078
3233
  // src/commands/mcp-run.ts
3079
3234
  function registerMcpRunCommand(program2) {
3080
3235
  program2.command("mcp-run <tool-id>", { hidden: true }).description("[internal] Spawn MCP v\u1EDBi secrets injected (M09)").action(notImplementedYet("mcp-run", "Milestone 09"));
@@ -3107,15 +3262,15 @@ function registerSecretsCommand(program2) {
3107
3262
 
3108
3263
  // src/commands/status.ts
3109
3264
  import { promises as fs8 } from "fs";
3110
- import { join as join18 } from "path";
3265
+ import { join as join19 } from "path";
3111
3266
  import boxen5 from "boxen";
3112
3267
 
3113
3268
  // src/lib/pack-backup-manager.ts
3114
3269
  import { promises as fs7 } from "fs";
3115
- import { join as join17 } from "path";
3270
+ import { join as join18 } from "path";
3116
3271
  var BACKUP_DIR_NAME = "_backup";
3117
3272
  async function listBackups(projectRoot) {
3118
- const dir = join17(projectRoot, ".claude", BACKUP_DIR_NAME);
3273
+ const dir = join18(projectRoot, ".claude", BACKUP_DIR_NAME);
3119
3274
  if (!await pathExists(dir)) return [];
3120
3275
  const entries = await fs7.readdir(dir, { withFileTypes: true });
3121
3276
  return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort().reverse();
@@ -3141,7 +3296,7 @@ function registerStatusCommand(program2) {
3141
3296
  }
3142
3297
  async function gatherStatus(cwd) {
3143
3298
  const projectName = cwd.split("/").filter(Boolean).pop() ?? "unknown";
3144
- const claudeRoot = join18(cwd, ".claude");
3299
+ const claudeRoot = join19(cwd, ".claude");
3145
3300
  const hasAvatar = await pathExists(claudeRoot);
3146
3301
  if (!hasAvatar) {
3147
3302
  return {
@@ -3154,8 +3309,8 @@ async function gatherStatus(cwd) {
3154
3309
  hasAvatar: false
3155
3310
  };
3156
3311
  }
3157
- const packVersion = await isGitRepo(join18(claudeRoot, "pack")) ? await readPinnedPackVersion(cwd).catch(() => null) : null;
3158
- const pendingDir = join18(claudeRoot, "_pending");
3312
+ const packVersion = await isGitRepo(join19(claudeRoot, "pack")) ? await readPinnedPackVersion(cwd).catch(() => null) : null;
3313
+ const pendingDir = join19(claudeRoot, "_pending");
3159
3314
  const pendingCount = await pathExists(pendingDir) ? (await fs8.readdir(pendingDir)).filter((n) => n.endsWith(".diff.md")).length : 0;
3160
3315
  const backupCount = (await listBackups(cwd)).length;
3161
3316
  const techStackSummary = await readTechStackFirstLine(claudeRoot);
@@ -3170,7 +3325,7 @@ async function gatherStatus(cwd) {
3170
3325
  };
3171
3326
  }
3172
3327
  async function readTechStackFirstLine(claudeRoot) {
3173
- const techStackPath = join18(claudeRoot, "project", "tech-stack.md");
3328
+ const techStackPath = join19(claudeRoot, "project", "tech-stack.md");
3174
3329
  if (!await pathExists(techStackPath)) return "(no tech-stack.md)";
3175
3330
  const content = await readText(techStackPath);
3176
3331
  const firstNonHeaderLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#") && !l.startsWith(">"));
@@ -3211,27 +3366,27 @@ import boxen6 from "boxen";
3211
3366
  // src/lib/create-uninstall-backup-snapshot.ts
3212
3367
  import { cp, mkdir, writeFile } from "fs/promises";
3213
3368
  import { homedir as homedir3 } from "os";
3214
- import { basename as basename2, join as join19 } from "path";
3215
- var UNINSTALL_BACKUPS_DIR = join19(homedir3(), ".avatar", "uninstall-backups");
3369
+ import { basename as basename2, join as join20 } from "path";
3370
+ var UNINSTALL_BACKUPS_DIR = join20(homedir3(), ".avatar", "uninstall-backups");
3216
3371
  async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersion) {
3217
3372
  const projectName = basename2(projectRoot);
3218
3373
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3219
- const backupDir = join19(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp}`);
3374
+ const backupDir = join20(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp}`);
3220
3375
  await mkdir(backupDir, { recursive: true, mode: 448 });
3221
3376
  if (artifacts.claudeDir) {
3222
- await cp(artifacts.claudeDir, join19(backupDir, ".claude"), { recursive: true });
3377
+ await cp(artifacts.claudeDir, join20(backupDir, ".claude"), { recursive: true });
3223
3378
  }
3224
3379
  if (artifacts.claudeMd) {
3225
- await cp(artifacts.claudeMd, join19(backupDir, "CLAUDE.md"));
3380
+ await cp(artifacts.claudeMd, join20(backupDir, "CLAUDE.md"));
3226
3381
  }
3227
3382
  if (artifacts.postMergeHook || artifacts.prePushHook) {
3228
- const hooksBackupDir = join19(backupDir, "hooks");
3383
+ const hooksBackupDir = join20(backupDir, "hooks");
3229
3384
  await mkdir(hooksBackupDir, { recursive: true });
3230
3385
  if (artifacts.postMergeHook) {
3231
- await cp(artifacts.postMergeHook, join19(hooksBackupDir, "post-merge"));
3386
+ await cp(artifacts.postMergeHook, join20(hooksBackupDir, "post-merge"));
3232
3387
  }
3233
3388
  if (artifacts.prePushHook) {
3234
- await cp(artifacts.prePushHook, join19(hooksBackupDir, "pre-push"));
3389
+ await cp(artifacts.prePushHook, join20(hooksBackupDir, "pre-push"));
3235
3390
  }
3236
3391
  }
3237
3392
  const manifest = {
@@ -3246,27 +3401,27 @@ async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersi
3246
3401
  prePushHook: !!artifacts.prePushHook
3247
3402
  }
3248
3403
  };
3249
- await writeFile(join19(backupDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
3404
+ await writeFile(join20(backupDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
3250
3405
  return backupDir;
3251
3406
  }
3252
3407
 
3253
3408
  // src/lib/detect-avatar-project-artifacts.ts
3254
- import { existsSync as existsSync5 } from "fs";
3255
- import { join as join20 } from "path";
3409
+ import { existsSync as existsSync6 } from "fs";
3410
+ import { join as join21 } from "path";
3256
3411
  function existsOrNull(path) {
3257
- return existsSync5(path) ? path : null;
3412
+ return existsSync6(path) ? path : null;
3258
3413
  }
3259
3414
  function detectAvatarProjectArtifacts(projectRoot) {
3260
- const claudeDir = existsOrNull(join20(projectRoot, ".claude"));
3261
- const claudeMd = existsOrNull(join20(projectRoot, "CLAUDE.md"));
3262
- const postMergeHook = existsOrNull(join20(projectRoot, ".git", "hooks", "post-merge"));
3415
+ const claudeDir = existsOrNull(join21(projectRoot, ".claude"));
3416
+ const claudeMd = existsOrNull(join21(projectRoot, "CLAUDE.md"));
3417
+ const postMergeHook = existsOrNull(join21(projectRoot, ".git", "hooks", "post-merge"));
3263
3418
  const prePushHook = existsOrNull(
3264
- join20(projectRoot, ".git", "modules", "src", "hooks", "pre-push")
3419
+ join21(projectRoot, ".git", "modules", "src", "hooks", "pre-push")
3265
3420
  );
3266
- const gitignorePath = existsOrNull(join20(projectRoot, ".gitignore"));
3267
- const gitmodulesPath = existsOrNull(join20(projectRoot, ".gitmodules"));
3268
- const notesDir = existsOrNull(join20(projectRoot, "notes"));
3269
- const scriptsDir = existsOrNull(join20(projectRoot, "scripts"));
3421
+ const gitignorePath = existsOrNull(join21(projectRoot, ".gitignore"));
3422
+ const gitmodulesPath = existsOrNull(join21(projectRoot, ".gitmodules"));
3423
+ const notesDir = existsOrNull(join21(projectRoot, "notes"));
3424
+ const scriptsDir = existsOrNull(join21(projectRoot, "scripts"));
3270
3425
  const hasAnyArtifact = !!(claudeDir || claudeMd || postMergeHook || prePushHook);
3271
3426
  return {
3272
3427
  hasAnyArtifact,
@@ -3287,11 +3442,11 @@ async function executeUninstallDeletion(artifacts, flags) {
3287
3442
  if (artifacts.claudeDir) {
3288
3443
  if (flags.keepSubmodule) {
3289
3444
  const { readdir: readdir2 } = await import("fs/promises");
3290
- const { join: join21 } = await import("path");
3445
+ const { join: join22 } = await import("path");
3291
3446
  const entries = await readdir2(artifacts.claudeDir);
3292
3447
  for (const entry of entries) {
3293
3448
  if (entry === "pack") continue;
3294
- await rm(join21(artifacts.claudeDir, entry), { recursive: true, force: true });
3449
+ await rm(join22(artifacts.claudeDir, entry), { recursive: true, force: true });
3295
3450
  }
3296
3451
  } else {
3297
3452
  await rm(artifacts.claudeDir, { recursive: true, force: true });
@@ -3360,7 +3515,7 @@ async function removeSubmoduleEntry(gitmodulesPath, submodulePath) {
3360
3515
  }
3361
3516
 
3362
3517
  // src/commands/uninstall.ts
3363
- var CLI_VERSION = "1.2.11";
3518
+ var CLI_VERSION = "1.3.0";
3364
3519
  function registerUninstallCommand(program2) {
3365
3520
  program2.command("uninstall").description("G\u1EE1 Avatar kh\u1ECFi project \u2014 backup t\u1EF1 \u0111\u1ED9ng (M11)").option("--yes", "Skip confirm prompt").option("--no-backup", "Kh\xF4ng t\u1EA1o backup tr\u01B0\u1EDBc khi x\xF3a (nguy hi\u1EC3m)").option("--keep-submodule", "Gi\u1EEF submodule .claude/pack/").option("--keep-hooks", "Gi\u1EEF git hooks post-merge, pre-push").option("--dry-run", "Hi\u1EC3n th\u1ECB danh s\xE1ch s\u1EBD x\xF3a, kh\xF4ng th\u1EF1c thi").action(async (opts) => {
3366
3521
  try {
@@ -3442,7 +3597,7 @@ function printUninstallSuccessBox(backupPath) {
3442
3597
  }
3443
3598
 
3444
3599
  // src/index.ts
3445
- var CLI_VERSION2 = "1.2.11";
3600
+ var CLI_VERSION2 = "1.3.0";
3446
3601
  var program = new Command();
3447
3602
  program.name("avatar").description("AI harness CLI for NAL Vietnam engineering").version(CLI_VERSION2, "-v, --version", "Hi\u1EC3n th\u1ECB phi\xEAn b\u1EA3n Avatar CLI").addHelpText(
3448
3603
  "beforeAll",