@nalvietnam/avatar-cli 1.2.10 → 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,12 +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 select7 } 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
 
1339
+ // src/lib/add-team-pack-submodule-with-retry-on-network-fail.ts
1340
+ import { spawnSync as spawnSync8 } from "child_process";
1341
+ import { select as select5 } from "@inquirer/prompts";
1342
+
1200
1343
  // src/lib/prompt-recovery-action-on-failure.ts
1201
- import { input as input2, select as select3 } from "@inquirer/prompts";
1344
+ import { input as input3, select as select3 } from "@inquirer/prompts";
1202
1345
  var UserAbortedRecoveryError = class extends Error {
1203
1346
  constructor(message) {
1204
1347
  super(message);
@@ -1222,10 +1365,10 @@ async function promptRetryOrSkip(args) {
1222
1365
  }
1223
1366
 
1224
1367
  // src/lib/team-pack-submodule-manager.ts
1225
- import { join as join10 } from "path";
1368
+ import { join as join11 } from "path";
1226
1369
 
1227
1370
  // src/lib/check-team-pack-access-with-retry-loop.ts
1228
- import { spawnSync as spawnSync6 } from "child_process";
1371
+ import { spawnSync as spawnSync7 } from "child_process";
1229
1372
  import { confirm as confirm2, select as select4 } from "@inquirer/prompts";
1230
1373
  import boxen2 from "boxen";
1231
1374
  function parseRepoSlugFromGitUrl(url) {
@@ -1234,11 +1377,11 @@ function parseRepoSlugFromGitUrl(url) {
1234
1377
  return null;
1235
1378
  }
1236
1379
  function checkRepoAccess(repoSlug) {
1237
- const r = spawnSync6("gh", ["api", `repos/${repoSlug}`], { stdio: "ignore" });
1380
+ const r = spawnSync7("gh", ["api", `repos/${repoSlug}`], { stdio: "ignore" });
1238
1381
  return r.status === 0;
1239
1382
  }
1240
1383
  function getCurrentGhUser() {
1241
- const r = spawnSync6("gh", ["api", "user", "--jq", ".login"], {
1384
+ const r = spawnSync7("gh", ["api", "user", "--jq", ".login"], {
1242
1385
  encoding: "utf8",
1243
1386
  stdio: ["ignore", "pipe", "pipe"]
1244
1387
  });
@@ -1247,7 +1390,7 @@ function getCurrentGhUser() {
1247
1390
  }
1248
1391
  function triggerGhAuthLoginInteractive() {
1249
1392
  log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
1250
- const r = spawnSync6("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1393
+ const r = spawnSync7("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1251
1394
  if (r.status !== 0) {
1252
1395
  log.warn(`gh auth login exit ${r.status}. C\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
1253
1396
  }
@@ -1387,7 +1530,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
1387
1530
  }
1388
1531
  let target = tag ?? null;
1389
1532
  if (!target) {
1390
- target = await latestTag(join10(projectRoot, TEAM_PACK_RELATIVE_PATH));
1533
+ target = await latestTag(join11(projectRoot, TEAM_PACK_RELATIVE_PATH));
1391
1534
  }
1392
1535
  if (target) {
1393
1536
  await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, target, projectRoot);
@@ -1395,7 +1538,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
1395
1538
  return { pinnedTag: target };
1396
1539
  }
1397
1540
  async function readPinnedPackVersion(projectRoot) {
1398
- const submoduleRoot = join10(projectRoot, TEAM_PACK_RELATIVE_PATH);
1541
+ const submoduleRoot = join11(projectRoot, TEAM_PACK_RELATIVE_PATH);
1399
1542
  const tag = await latestTag(submoduleRoot);
1400
1543
  if (tag) return tag;
1401
1544
  const sha = await currentCommitSha(submoduleRoot);
@@ -1403,6 +1546,51 @@ async function readPinnedPackVersion(projectRoot) {
1403
1546
  }
1404
1547
 
1405
1548
  // src/lib/add-team-pack-submodule-with-retry-on-network-fail.ts
1549
+ function isSshPermissionError(message) {
1550
+ const text = message.toLowerCase();
1551
+ return text.includes("permission denied (publickey)") || text.includes("publickey)") || text.includes("ssh: could not resolve") || text.includes("host key verification failed");
1552
+ }
1553
+ function triggerGhAuthLoginInteractive2() {
1554
+ log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
1555
+ const r = spawnSync8("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1556
+ if (r.status !== 0) {
1557
+ log.warn(`gh auth login exit ${r.status}. C\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
1558
+ }
1559
+ }
1560
+ function openGithubSshKeysPage() {
1561
+ log.info("M\u1EDF trang GitHub Settings \u2192 SSH Keys...");
1562
+ const r = spawnSync8("open", ["https://github.com/settings/keys"], { stdio: "ignore" });
1563
+ if (r.status !== 0) {
1564
+ log.info("URL: https://github.com/settings/keys");
1565
+ }
1566
+ }
1567
+ async function handleSshPermissionError() {
1568
+ return await select5({
1569
+ message: "SSH permission denied. C\xE1ch x\u1EED l\xFD?",
1570
+ choices: [
1571
+ {
1572
+ name: "Switch GitHub account (gh auth login \u2014 m\u1EDF browser)",
1573
+ value: "switch"
1574
+ },
1575
+ {
1576
+ name: "D\xF9ng HTTPS thay SSH (override URL b\u1EB1ng env AVATAR_TEAM_PACK_REPO_URL)",
1577
+ value: "https"
1578
+ },
1579
+ {
1580
+ name: "T\xF4i v\u1EEBa add SSH key l\xEAn GitHub \u2014 retry",
1581
+ value: "retry"
1582
+ },
1583
+ {
1584
+ name: "B\u1ECF qua team-ai-pack (d\xF9ng avatar sync sau)",
1585
+ value: "skip"
1586
+ },
1587
+ {
1588
+ name: "T\u1EA1m ng\u01B0ng init \u2014 fix SSH key tay r\u1ED3i ch\u1EA1y l\u1EA1i",
1589
+ value: "abort"
1590
+ }
1591
+ ]
1592
+ });
1593
+ }
1406
1594
  async function addTeamPackSubmoduleWithRetryOnNetworkFail(projectRoot, tag, ssoEmail) {
1407
1595
  while (true) {
1408
1596
  try {
@@ -1410,9 +1598,39 @@ async function addTeamPackSubmoduleWithRetryOnNetworkFail(projectRoot, tag, ssoE
1410
1598
  return { pinnedTag: result.pinnedTag, skipped: false };
1411
1599
  } catch (err) {
1412
1600
  if (err instanceof TeamPackAccessAbortedError) throw err;
1601
+ const message = err instanceof Error ? err.message : String(err);
1602
+ if (isSshPermissionError(message)) {
1603
+ log.warn("Pull team-ai-pack th\u1EA5t b\u1EA1i: SSH permission denied (publickey).");
1604
+ log.dim(
1605
+ " \u2192 M\xE1y n\xE0y ch\u01B0a c\xF3 SSH key \u0111\u01B0\u1EE3c register tr\xEAn GitHub, ho\u1EB7c key thu\u1ED9c account kh\xE1c."
1606
+ );
1607
+ const action2 = await handleSshPermissionError();
1608
+ if (action2 === "abort") {
1609
+ throw new UserAbortedRecoveryError(
1610
+ "User abort t\u1EA1i b\u01B0\u1EDBc pull team-ai-pack. Fix SSH key (https://github.com/settings/keys) r\u1ED3i ch\u1EA1y l\u1EA1i 'avatar init'."
1611
+ );
1612
+ }
1613
+ if (action2 === "skip") {
1614
+ log.warn(
1615
+ "Skip team-ai-pack. Workspace d\xF9ng \u0111\u01B0\u1EE3c nh\u01B0ng kh\xF4ng c\xF3 shared knowledge. Pull sau qua `avatar sync`."
1616
+ );
1617
+ return { pinnedTag: null, skipped: true };
1618
+ }
1619
+ if (action2 === "switch") {
1620
+ triggerGhAuthLoginInteractive2();
1621
+ continue;
1622
+ }
1623
+ if (action2 === "https") {
1624
+ process.env.AVATAR_TEAM_PACK_REPO_URL = "https://github.com/nalvn/team-ai-pack.git";
1625
+ log.info("Override URL sang HTTPS. L\u01B0u \xFD: HTTPS c\xF3 th\u1EC3 fail 403 n\u1EBFu read-only role.");
1626
+ openGithubSshKeysPage();
1627
+ continue;
1628
+ }
1629
+ continue;
1630
+ }
1413
1631
  const action = await promptRetryOrSkip({
1414
1632
  taskName: "Pull team-ai-pack submodule",
1415
- reason: err instanceof Error ? err.message : String(err),
1633
+ reason: message,
1416
1634
  allowSkip: true,
1417
1635
  hint: "Network glitch? Retry th\u01B0\u1EDDng work. N\u1EBFu skip, d\xF9ng `avatar sync` sau \u0111\u1EC3 pull pack."
1418
1636
  });
@@ -1491,27 +1709,27 @@ ${renderAvatarBanner(opts)}
1491
1709
  }
1492
1710
 
1493
1711
  // src/lib/execute-gh-repo-create.ts
1494
- import { spawnSync as spawnSync7 } from "child_process";
1712
+ import { spawnSync as spawnSync9 } from "child_process";
1495
1713
  var RepoAlreadyExistsError = class extends Error {
1496
1714
  constructor(fullName) {
1497
1715
  super(`Repo "${fullName}" \u0111\xE3 t\u1ED3n t\u1EA1i tr\xEAn GitHub. \u0110\u1ED5i t\xEAn ho\u1EB7c x\xF3a repo c\u0169.`);
1498
1716
  this.name = "RepoAlreadyExistsError";
1499
1717
  }
1500
1718
  };
1501
- function executeGhRepoCreate(input5) {
1502
- const fullName = `${input5.org}/${input5.name}`;
1719
+ function executeGhRepoCreate(input6) {
1720
+ const fullName = `${input6.org}/${input6.name}`;
1503
1721
  const args = [
1504
1722
  "repo",
1505
1723
  "create",
1506
1724
  fullName,
1507
- `--${input5.visibility}`,
1725
+ `--${input6.visibility}`,
1508
1726
  "--source",
1509
- input5.folder,
1727
+ input6.folder,
1510
1728
  "--remote",
1511
1729
  "origin",
1512
1730
  "--push"
1513
1731
  ];
1514
- const r = spawnSync7("gh", args, { stdio: "inherit" });
1732
+ const r = spawnSync9("gh", args, { stdio: "inherit" });
1515
1733
  if (r.status !== 0) {
1516
1734
  if (r.status === 1) {
1517
1735
  throw new RepoAlreadyExistsError(fullName);
@@ -1525,9 +1743,9 @@ function executeGhRepoCreate(input5) {
1525
1743
  }
1526
1744
 
1527
1745
  // src/lib/resolve-github-username-default.ts
1528
- import { spawnSync as spawnSync8 } from "child_process";
1746
+ import { spawnSync as spawnSync10 } from "child_process";
1529
1747
  function resolveGithubUsernameDefault() {
1530
- const r = spawnSync8("gh", ["api", "user", "--jq", ".login"], {
1748
+ const r = spawnSync10("gh", ["api", "user", "--jq", ".login"], {
1531
1749
  encoding: "utf8",
1532
1750
  stdio: ["ignore", "pipe", "pipe"]
1533
1751
  });
@@ -1559,28 +1777,28 @@ function validateRepoVisibility(v) {
1559
1777
  }
1560
1778
 
1561
1779
  // src/lib/create-github-remote-from-folder.ts
1562
- function createGithubRemoteFromFolder(input5) {
1563
- validateRepoName(input5.name);
1564
- validateRepoVisibility(input5.visibility);
1565
- const org = input5.org ?? resolveGithubUsernameDefault();
1566
- 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})...`);
1567
1785
  const urls = executeGhRepoCreate({
1568
- folder: input5.folder,
1786
+ folder: input6.folder,
1569
1787
  org,
1570
- name: input5.name,
1571
- visibility: input5.visibility
1788
+ name: input6.name,
1789
+ visibility: input6.visibility
1572
1790
  });
1573
1791
  log.success(`\u0110\xE3 t\u1EA1o: ${urls.sshUrl}`);
1574
1792
  return urls;
1575
1793
  }
1576
1794
 
1577
1795
  // src/lib/create-workspace-remote-via-gh.ts
1578
- import { spawnSync as spawnSync16 } from "child_process";
1796
+ import { spawnSync as spawnSync18 } from "child_process";
1579
1797
 
1580
1798
  // src/lib/check-gh-cli-auth-status.ts
1581
- import { spawnSync as spawnSync9 } from "child_process";
1799
+ import { spawnSync as spawnSync11 } from "child_process";
1582
1800
  function checkGhCliAuthStatus() {
1583
- const r = spawnSync9("gh", ["auth", "status"], { stdio: "ignore" });
1801
+ const r = spawnSync11("gh", ["auth", "status"], { stdio: "ignore" });
1584
1802
  if (r.error && r.error.code === "ENOENT") {
1585
1803
  return "not-installed";
1586
1804
  }
@@ -1588,12 +1806,12 @@ function checkGhCliAuthStatus() {
1588
1806
  }
1589
1807
 
1590
1808
  // src/lib/detect-package-manager.ts
1591
- import { spawnSync as spawnSync10 } from "child_process";
1809
+ import { spawnSync as spawnSync12 } from "child_process";
1592
1810
  function hasBinary(name) {
1593
1811
  const platform2 = detectHostPlatform();
1594
1812
  const probe = platform2 === "win32" ? "where" : "command";
1595
1813
  const args = platform2 === "win32" ? [name] : ["-v", name];
1596
- const r = spawnSync10(probe, args, {
1814
+ const r = spawnSync12(probe, args, {
1597
1815
  shell: platform2 !== "win32",
1598
1816
  stdio: "ignore"
1599
1817
  });
@@ -1609,11 +1827,11 @@ function detectPackageManager() {
1609
1827
  }
1610
1828
 
1611
1829
  // src/lib/handle-remote-access-failure-with-account-switch.ts
1612
- import { spawnSync as spawnSync12 } from "child_process";
1613
- import { input as input3, select as select5 } from "@inquirer/prompts";
1830
+ import { spawnSync as spawnSync14 } from "child_process";
1831
+ import { input as input4, select as select6 } from "@inquirer/prompts";
1614
1832
 
1615
1833
  // src/lib/verify-git-remote-accessible.ts
1616
- import { spawnSync as spawnSync11 } from "child_process";
1834
+ import { spawnSync as spawnSync13 } from "child_process";
1617
1835
  var TIMEOUT_MS = 5e3;
1618
1836
  function classifyRemoteError(stderr) {
1619
1837
  const text = stderr.toLowerCase();
@@ -1629,7 +1847,7 @@ function classifyRemoteError(stderr) {
1629
1847
  return "unknown";
1630
1848
  }
1631
1849
  function tryVerifyGitRemoteAccessible(url) {
1632
- const r = spawnSync11("git", ["ls-remote", "--exit-code", url, "HEAD"], {
1850
+ const r = spawnSync13("git", ["ls-remote", "--exit-code", url, "HEAD"], {
1633
1851
  encoding: "utf8",
1634
1852
  timeout: TIMEOUT_MS,
1635
1853
  stdio: ["ignore", "pipe", "pipe"]
@@ -1651,16 +1869,16 @@ var RemoteAccessAbortedError = class extends Error {
1651
1869
  }
1652
1870
  };
1653
1871
  function getCurrentGhUser2() {
1654
- const r = spawnSync12("gh", ["api", "user", "--jq", ".login"], {
1872
+ const r = spawnSync14("gh", ["api", "user", "--jq", ".login"], {
1655
1873
  encoding: "utf8",
1656
1874
  stdio: ["ignore", "pipe", "pipe"]
1657
1875
  });
1658
1876
  if (r.status !== 0) return null;
1659
1877
  return r.stdout.trim() || null;
1660
1878
  }
1661
- function triggerGhAuthLoginInteractive2() {
1879
+ function triggerGhAuthLoginInteractive3() {
1662
1880
  log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
1663
- const r = spawnSync12("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1881
+ const r = spawnSync14("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1664
1882
  if (r.status !== 0) {
1665
1883
  log.warn(`gh auth login exit ${r.status}. B\u1EA1n c\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
1666
1884
  }
@@ -1694,7 +1912,7 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
1694
1912
  log.dim(` L\xFD do: ${reason}${detail ? ` \u2014 ${detail.slice(0, 150)}` : ""}`);
1695
1913
  log.info(getReasonHint(reason, currentUrl, ghUser));
1696
1914
  if (ghUser) log.dim(` gh CLI hi\u1EC7n \u0111ang login: ${ghUser}`);
1697
- const action = await select5({
1915
+ const action = await select6({
1698
1916
  message: "C\xE1ch x\u1EED l\xFD?",
1699
1917
  choices: [
1700
1918
  {
@@ -1721,7 +1939,7 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
1721
1939
  );
1722
1940
  }
1723
1941
  if (action === "re-input-url") {
1724
- const newUrl = await input3({
1942
+ const newUrl = await input4({
1725
1943
  message: "URL git remote (https://github.com/owner/repo.git ho\u1EB7c git@github.com:owner/repo.git):",
1726
1944
  default: currentUrl,
1727
1945
  validate: (v) => isValidGitUrl(v) || "URL kh\xF4ng \u0111\xFAng format git remote"
@@ -1729,7 +1947,7 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
1729
1947
  currentUrl = newUrl.trim();
1730
1948
  }
1731
1949
  if (action === "switch") {
1732
- triggerGhAuthLoginInteractive2();
1950
+ triggerGhAuthLoginInteractive3();
1733
1951
  }
1734
1952
  log.info(`Verify remote l\u1EA1i: ${currentUrl}...`);
1735
1953
  const result = tryVerifyGitRemoteAccessible(currentUrl);
@@ -1743,7 +1961,7 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
1743
1961
  }
1744
1962
 
1745
1963
  // src/lib/install-gh-cli-via-package-manager.ts
1746
- import { spawnSync as spawnSync13 } from "child_process";
1964
+ import { spawnSync as spawnSync15 } from "child_process";
1747
1965
  var INSTALL_COMMANDS = {
1748
1966
  brew: { cmd: "brew", args: ["install", "gh"] },
1749
1967
  apt: { cmd: "sudo", args: ["apt-get", "install", "-y", "gh"] },
@@ -1754,7 +1972,7 @@ var INSTALL_COMMANDS = {
1754
1972
  function installGhCliViaPackageManager(pm) {
1755
1973
  const spec = INSTALL_COMMANDS[pm];
1756
1974
  log.info(`\u0110ang c\xE0i gh CLI qua ${pm}...`);
1757
- const r = spawnSync13(spec.cmd, spec.args, { stdio: "inherit" });
1975
+ const r = spawnSync15(spec.cmd, spec.args, { stdio: "inherit" });
1758
1976
  if (r.status !== 0) {
1759
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.`);
1760
1978
  }
@@ -1762,9 +1980,9 @@ function installGhCliViaPackageManager(pm) {
1762
1980
  }
1763
1981
 
1764
1982
  // src/lib/setup-git-credential-via-gh.ts
1765
- import { spawnSync as spawnSync14 } from "child_process";
1983
+ import { spawnSync as spawnSync16 } from "child_process";
1766
1984
  function setupGitCredentialViaGh() {
1767
- const r = spawnSync14("gh", ["auth", "setup-git"], { stdio: "ignore" });
1985
+ const r = spawnSync16("gh", ["auth", "setup-git"], { stdio: "ignore" });
1768
1986
  if (r.status !== 0) {
1769
1987
  log.warn("gh auth setup-git fail (non-fatal). N\u1EBFu git clone l\u1ED7i 128 \u2192 ch\u1EA1y th\u1EE7 c\xF4ng.");
1770
1988
  return;
@@ -1773,10 +1991,10 @@ function setupGitCredentialViaGh() {
1773
1991
  }
1774
1992
 
1775
1993
  // src/lib/trigger-gh-cli-auth-login.ts
1776
- import { spawnSync as spawnSync15 } from "child_process";
1994
+ import { spawnSync as spawnSync17 } from "child_process";
1777
1995
  function triggerGhCliAuthLogin() {
1778
1996
  log.info("Kh\u1EDFi \u0111\u1ED9ng \u0111\u0103ng nh\u1EADp GitHub qua gh CLI (browser s\u1EBD m\u1EDF)...");
1779
- const r = spawnSync15(
1997
+ const r = spawnSync17(
1780
1998
  "gh",
1781
1999
  ["auth", "login", "--hostname", "github.com", "--web", "--git-protocol", "ssh"],
1782
2000
  { stdio: "inherit" }
@@ -1894,20 +2112,20 @@ function classifyGhCreateError(stderr) {
1894
2112
  return "unknown";
1895
2113
  }
1896
2114
  function repoExistsOnGitHub(fullName) {
1897
- const r = spawnSync16("gh", ["repo", "view", fullName, "--json", "name"], {
2115
+ const r = spawnSync18("gh", ["repo", "view", fullName, "--json", "name"], {
1898
2116
  stdio: "ignore"
1899
2117
  });
1900
2118
  return r.status === 0;
1901
2119
  }
1902
2120
  function canCreateInNamespace(org, ghUser) {
1903
2121
  if (org.toLowerCase() === ghUser.toLowerCase()) return { ok: true };
1904
- const r = spawnSync16("gh", ["api", `orgs/${org}/members/${ghUser}`, "--silent"], {
2122
+ const r = spawnSync18("gh", ["api", `orgs/${org}/members/${ghUser}`, "--silent"], {
1905
2123
  stdio: "ignore"
1906
2124
  });
1907
2125
  if (r.status === 0) return { ok: true };
1908
- const orgCheck = spawnSync16("gh", ["api", `orgs/${org}`, "--silent"], { stdio: "ignore" });
2126
+ const orgCheck = spawnSync18("gh", ["api", `orgs/${org}`, "--silent"], { stdio: "ignore" });
1909
2127
  if (orgCheck.status !== 0) {
1910
- const userCheck = spawnSync16("gh", ["api", `users/${org}`, "--silent"], { stdio: "ignore" });
2128
+ const userCheck = spawnSync18("gh", ["api", `users/${org}`, "--silent"], { stdio: "ignore" });
1911
2129
  if (userCheck.status === 0) {
1912
2130
  return {
1913
2131
  ok: false,
@@ -1924,17 +2142,17 @@ function canCreateInNamespace(org, ghUser) {
1924
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.`
1925
2143
  };
1926
2144
  }
1927
- async function createWorkspaceRemoteViaGh(input5) {
1928
- validateRepoName(input5.workspaceName);
1929
- validateRepoVisibility(input5.visibility);
2145
+ async function createWorkspaceRemoteViaGh(input6) {
2146
+ validateRepoName(input6.workspaceName);
2147
+ validateRepoVisibility(input6.visibility);
1930
2148
  await ensureGitHubReady();
1931
2149
  const ghUser = resolveGithubUsernameDefault();
1932
- const org = input5.org ?? ghUser;
2150
+ const org = input6.org ?? ghUser;
1933
2151
  const namespaceCheck = canCreateInNamespace(org, ghUser);
1934
2152
  if (!namespaceCheck.ok) {
1935
2153
  throw new Error(`Kh\xF4ng th\u1EC3 t\u1EA1o repo d\u01B0\u1EDBi '${org}/': ${namespaceCheck.reason}`);
1936
2154
  }
1937
- const fullName = `${org}/${input5.workspaceName}`;
2155
+ const fullName = `${org}/${input6.workspaceName}`;
1938
2156
  if (repoExistsOnGitHub(fullName)) {
1939
2157
  throw new CreateWorkspaceRemoteError(
1940
2158
  "repo-exists",
@@ -1942,16 +2160,16 @@ async function createWorkspaceRemoteViaGh(input5) {
1942
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.`
1943
2161
  );
1944
2162
  }
1945
- log.info(`T\u1EA1o GitHub repo cho workspace: ${fullName} (${input5.visibility})...`);
1946
- const r = spawnSync16(
2163
+ log.info(`T\u1EA1o GitHub repo cho workspace: ${fullName} (${input6.visibility})...`);
2164
+ const r = spawnSync18(
1947
2165
  "gh",
1948
2166
  [
1949
2167
  "repo",
1950
2168
  "create",
1951
2169
  fullName,
1952
- `--${input5.visibility}`,
2170
+ `--${input6.visibility}`,
1953
2171
  "--source",
1954
- input5.workspacePath,
2172
+ input6.workspacePath,
1955
2173
  "--remote",
1956
2174
  "origin",
1957
2175
  "--push"
@@ -1984,7 +2202,7 @@ ${combined}
1984
2202
  function linkExistingRemoteToWorkspace(args) {
1985
2203
  const sshUrl = `git@github.com:${args.fullName}.git`;
1986
2204
  const httpsUrl = `https://github.com/${args.fullName}.git`;
1987
- const addResult = spawnSync16(
2205
+ const addResult = spawnSync18(
1988
2206
  "git",
1989
2207
  ["-C", args.workspacePath, "remote", "add", "origin", sshUrl],
1990
2208
  {
@@ -1993,7 +2211,7 @@ function linkExistingRemoteToWorkspace(args) {
1993
2211
  }
1994
2212
  );
1995
2213
  if (addResult.status !== 0) {
1996
- spawnSync16("git", ["-C", args.workspacePath, "remote", "set-url", "origin", sshUrl], {
2214
+ spawnSync18("git", ["-C", args.workspacePath, "remote", "set-url", "origin", sshUrl], {
1997
2215
  stdio: "ignore"
1998
2216
  });
1999
2217
  }
@@ -2003,15 +2221,15 @@ function linkExistingRemoteToWorkspace(args) {
2003
2221
 
2004
2222
  // src/lib/safe-bootstrap-for-dirty-folder.ts
2005
2223
  import { readdirSync } from "fs";
2006
- import { select as select6 } from "@inquirer/prompts";
2224
+ import { select as select7 } from "@inquirer/prompts";
2007
2225
  import { simpleGit as simpleGit3 } from "simple-git";
2008
2226
 
2009
2227
  // src/lib/check-folder-has-git.ts
2010
- import { existsSync as existsSync2, statSync } from "fs";
2011
- import { join as join11 } from "path";
2228
+ import { existsSync as existsSync3, statSync } from "fs";
2229
+ import { join as join12 } from "path";
2012
2230
  function checkFolderHasGit(folderPath) {
2013
- const gitPath = join11(folderPath, ".git");
2014
- if (!existsSync2(gitPath)) return false;
2231
+ const gitPath = join12(folderPath, ".git");
2232
+ if (!existsSync3(gitPath)) return false;
2015
2233
  const stat = statSync(gitPath);
2016
2234
  return stat.isDirectory() || stat.isFile();
2017
2235
  }
@@ -2041,8 +2259,8 @@ async function createInitialGitCommit(folderPath) {
2041
2259
  }
2042
2260
 
2043
2261
  // src/lib/detect-folder-tech-stack.ts
2044
- import { existsSync as existsSync3 } from "fs";
2045
- import { join as join12 } from "path";
2262
+ import { existsSync as existsSync4 } from "fs";
2263
+ import { join as join13 } from "path";
2046
2264
  var SIGNATURES = {
2047
2265
  node: ["package.json"],
2048
2266
  python: ["pyproject.toml", "requirements.txt", "setup.py", "Pipfile"],
@@ -2054,7 +2272,7 @@ var SIGNATURES = {
2054
2272
  function detectFolderTechStack(folderPath) {
2055
2273
  const matched = [];
2056
2274
  for (const [stack, files] of Object.entries(SIGNATURES)) {
2057
- if (files.some((f) => existsSync3(join12(folderPath, f)))) {
2275
+ if (files.some((f) => existsSync4(join13(folderPath, f)))) {
2058
2276
  matched.push(stack);
2059
2277
  }
2060
2278
  }
@@ -2063,25 +2281,25 @@ function detectFolderTechStack(folderPath) {
2063
2281
 
2064
2282
  // src/lib/gitignore-template-loader.ts
2065
2283
  import { readFileSync as readFileSync2 } from "fs";
2066
- import { dirname as dirname3, join as join13 } from "path";
2284
+ import { dirname as dirname3, join as join14 } from "path";
2067
2285
  import { fileURLToPath as fileURLToPath2 } from "url";
2068
2286
  var __dirname = dirname3(fileURLToPath2(import.meta.url));
2069
2287
  var CANDIDATE_DIRS = [
2070
2288
  // Bundled production: dist/index.js → __dirname = .../dist/, sibling dist/templates
2071
- join13(__dirname, "templates", "gitignore"),
2289
+ join14(__dirname, "templates", "gitignore"),
2072
2290
  // Legacy bundled: nếu file là dist/lib/*.js (sub-bundle), templates ở dist/templates
2073
- join13(__dirname, "..", "templates", "gitignore"),
2291
+ join14(__dirname, "..", "templates", "gitignore"),
2074
2292
  // Dev mode (vitest/tsx run src/ trực tiếp): __dirname = src/lib/
2075
- join13(__dirname, "..", "..", "src", "templates", "gitignore"),
2293
+ join14(__dirname, "..", "..", "src", "templates", "gitignore"),
2076
2294
  // npm-installed alt: __dirname = .../dist/ → package_root/src/templates
2077
- join13(__dirname, "..", "src", "templates", "gitignore")
2295
+ join14(__dirname, "..", "src", "templates", "gitignore")
2078
2296
  ];
2079
2297
  var AVATAR_MARKER_START = "# === avatar ===";
2080
2298
  var AVATAR_MARKER_END = "# === /avatar ===";
2081
2299
  function readTemplate(stack) {
2082
2300
  for (const dir of CANDIDATE_DIRS) {
2083
2301
  try {
2084
- return readFileSync2(join13(dir, `${stack}.txt`), "utf8");
2302
+ return readFileSync2(join14(dir, `${stack}.txt`), "utf8");
2085
2303
  } catch {
2086
2304
  }
2087
2305
  }
@@ -2095,11 +2313,11 @@ ${readTemplate(s).trim()}`);
2095
2313
  }
2096
2314
 
2097
2315
  // src/lib/write-or-merge-gitignore.ts
2098
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync } from "fs";
2099
- import { join as join14 } from "path";
2316
+ import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync } from "fs";
2317
+ import { join as join15 } from "path";
2100
2318
  function writeOrMergeGitignore(folderPath, avatarBlock) {
2101
- const path = join14(folderPath, ".gitignore");
2102
- if (!existsSync4(path)) {
2319
+ const path = join15(folderPath, ".gitignore");
2320
+ if (!existsSync5(path)) {
2103
2321
  writeFileSync(path, avatarBlock, "utf8");
2104
2322
  return;
2105
2323
  }
@@ -2140,7 +2358,7 @@ async function promptBootstrapStrategy(state, opts) {
2140
2358
  if (opts.presetStrategy) return opts.presetStrategy;
2141
2359
  if (opts.autoYes) return "stash";
2142
2360
  if (state === "empty" || state === "clean") return "commit-all";
2143
- return await select6({
2361
+ return await select7({
2144
2362
  message: state === "dirty" ? "Folder c\xF3 changes ch\u01B0a commit. C\xE1ch x\u1EED l\xFD:" : "Folder c\xF3 file ch\u01B0a version. C\xE1ch x\u1EED l\xFD:",
2145
2363
  choices: [
2146
2364
  {
@@ -2275,7 +2493,7 @@ async function safeBootstrapGitInFolder(folderPath, opts = {}) {
2275
2493
 
2276
2494
  // src/commands/init-conflict-detection-helpers.ts
2277
2495
  import { readdir } from "fs/promises";
2278
- import { join as join15 } from "path";
2496
+ import { join as join16 } from "path";
2279
2497
  async function isEmptyOrMissing(path) {
2280
2498
  if (!await pathExists(path)) return true;
2281
2499
  try {
@@ -2288,7 +2506,7 @@ async function isEmptyOrMissing(path) {
2288
2506
  }
2289
2507
  async function findAlternativeWorkspaceName(parent, desiredName, maxAttempts = 10) {
2290
2508
  for (let i = 2; i < maxAttempts; i++) {
2291
- const candidate = join15(parent, `${desiredName}-${i}`);
2509
+ const candidate = join16(parent, `${desiredName}-${i}`);
2292
2510
  if (await isEmptyOrMissing(candidate)) return candidate;
2293
2511
  }
2294
2512
  return null;
@@ -2589,7 +2807,7 @@ async function runInit(opts) {
2589
2807
  }
2590
2808
  }
2591
2809
  async function promptProjectStatus() {
2592
- return await select7({
2810
+ return await select8({
2593
2811
  message: "T\xECnh tr\u1EA1ng d\u1EF1 \xE1n c\u1EE7a b\u1EA1n?",
2594
2812
  choices: [
2595
2813
  { name: "1. \u0110\xE3 c\xF3 repo git remote (URL c\xF3 s\u1EB5n)", value: "existing-remote" },
@@ -2599,7 +2817,7 @@ async function promptProjectStatus() {
2599
2817
  });
2600
2818
  }
2601
2819
  async function runInitFromExistingRemote(opts, ownerEmail) {
2602
- const initialRemoteUrl = opts.clientRepo ?? await input4({
2820
+ const initialRemoteUrl = opts.clientRepo ?? await input5({
2603
2821
  message: "URL git c\u1EE7a repo:",
2604
2822
  validate: (v) => v.length > 0 ? true : "URL b\u1EAFt bu\u1ED9c"
2605
2823
  });
@@ -2607,7 +2825,7 @@ async function runInitFromExistingRemote(opts, ownerEmail) {
2607
2825
  const remoteUrl = resolvedRemoteUrl ?? initialRemoteUrl;
2608
2826
  const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
2609
2827
  const inferredName = inferWorkspaceName(remoteUrl);
2610
- 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 });
2611
2829
  const workspaceParent = resolve(opts.workspaceParent ?? ".");
2612
2830
  const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);
2613
2831
  await scaffoldWorkspaceWithSrcSubmodule({
@@ -2630,7 +2848,7 @@ async function runInitFromExistingRemote(opts, ownerEmail) {
2630
2848
  }
2631
2849
  async function runInitFromExistingFolder(opts, ownerEmail) {
2632
2850
  const folderPath = resolve(
2633
- opts.folderPath ?? await input4({
2851
+ opts.folderPath ?? await input5({
2634
2852
  message: "\u0110\u01B0\u1EDDng d\u1EABn folder hi\u1EC7n c\xF3:",
2635
2853
  validate: (v) => v.length > 0 ? true : "Path b\u1EAFt bu\u1ED9c"
2636
2854
  })
@@ -2642,7 +2860,7 @@ async function runInitFromExistingFolder(opts, ownerEmail) {
2642
2860
  const remoteUrl = await getOrCreateOriginRemote(folderPath, opts);
2643
2861
  const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
2644
2862
  const inferredName = opts.workspaceName ?? `${basename(folderPath)}-avatar-workspace`;
2645
- 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 });
2646
2864
  const workspaceParent = resolve(opts.workspaceParent ?? ".");
2647
2865
  const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);
2648
2866
  await scaffoldWorkspaceWithSrcSubmodule({
@@ -2666,11 +2884,11 @@ async function runInitFromExistingFolder(opts, ownerEmail) {
2666
2884
  }
2667
2885
  async function runInitFromScratch(opts, ownerEmail) {
2668
2886
  await ensureGitHubReady();
2669
- const projectName = opts.workspaceName ?? await input4({
2887
+ const projectName = opts.workspaceName ?? await input5({
2670
2888
  message: "T\xEAn d\u1EF1 \xE1n:",
2671
2889
  validate: (v) => v.length > 0 ? true : "T\xEAn b\u1EAFt bu\u1ED9c"
2672
2890
  });
2673
- const visibility = opts.repoVisibility ?? await select7({
2891
+ const visibility = opts.repoVisibility ?? await select8({
2674
2892
  message: "Visibility?",
2675
2893
  choices: [
2676
2894
  { name: "private (m\u1EB7c \u0111\u1ECBnh)", value: "private" },
@@ -2680,7 +2898,7 @@ async function runInitFromScratch(opts, ownerEmail) {
2680
2898
  const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
2681
2899
  const workspaceParent = resolve(opts.workspaceParent ?? ".");
2682
2900
  const workspacePath = await resolveWorkspacePath(workspaceParent, projectName, opts.force);
2683
- const srcPath = join16(workspacePath, "src");
2901
+ const srcPath = join17(workspacePath, "src");
2684
2902
  await ensureDir(workspacePath);
2685
2903
  await ensureDir(srcPath);
2686
2904
  await safeBootstrapGitInFolder(srcPath, { autoYes: true });
@@ -2744,14 +2962,14 @@ async function getOrCreateOriginRemote(folderPath, opts) {
2744
2962
  return void 0;
2745
2963
  }
2746
2964
  await ensureGitHubReady();
2747
- const visibility = opts.repoVisibility ?? await select7({
2965
+ const visibility = opts.repoVisibility ?? await select8({
2748
2966
  message: "Visibility?",
2749
2967
  choices: [
2750
2968
  { name: "private (m\u1EB7c \u0111\u1ECBnh)", value: "private" },
2751
2969
  { name: "public", value: "public" }
2752
2970
  ]
2753
2971
  });
2754
- const repoName = await input4({
2972
+ const repoName = await input5({
2755
2973
  message: "T\xEAn repo:",
2756
2974
  default: basename(folderPath)
2757
2975
  });
@@ -2816,10 +3034,10 @@ async function finalizeWorkspaceScaffold(args) {
2816
3034
  await writeRootClaudeMd(args.workspacePath, vars);
2817
3035
  await writeProjectSettings(args.workspacePath, vars);
2818
3036
  await appendGitignoreEntries(args.workspacePath);
2819
- await ensureDir(join16(args.workspacePath, "notes"));
2820
- await ensureDir(join16(args.workspacePath, "scripts"));
2821
- await installGitHook(join16(args.workspacePath, ".git"), "post-merge");
2822
- 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");
2823
3041
  log.success("C\xE0i post-merge (workspace) + pre-push (src/)");
2824
3042
  await appendAuditEntry("init", `flow=${args.flow},workspace=${args.workspaceName}`);
2825
3043
  await maybeCommitWorkspace(args.workspacePath, args.skipCommit);
@@ -2846,7 +3064,7 @@ async function maybeCreateWorkspaceRemote(args) {
2846
3064
  });
2847
3065
  }
2848
3066
  if (!shouldCreate) return;
2849
- const visibility = args.repoVisibility ?? (args.autoYes ? "private" : await select7({
3067
+ const visibility = args.repoVisibility ?? (args.autoYes ? "private" : await select8({
2850
3068
  message: "Workspace visibility?",
2851
3069
  choices: [
2852
3070
  { name: "private (m\u1EB7c \u0111\u1ECBnh, an to\xE0n)", value: "private" },
@@ -2865,7 +3083,7 @@ async function maybeCreateWorkspaceRemote(args) {
2865
3083
  } catch (err) {
2866
3084
  if (err instanceof CreateWorkspaceRemoteError && err.reason === "repo-exists") {
2867
3085
  const fullName = err.fullName;
2868
- const reuseAction = await select7({
3086
+ const reuseAction = await select8({
2869
3087
  message: `Repo '${fullName}' \u0111\xE3 t\u1ED3n t\u1EA1i tr\xEAn GitHub. C\xE1ch x\u1EED l\xFD?`,
2870
3088
  choices: [
2871
3089
  {
@@ -2894,7 +3112,7 @@ async function maybeCreateWorkspaceRemote(args) {
2894
3112
  });
2895
3113
  return;
2896
3114
  }
2897
- const newName = await input4({
3115
+ const newName = await input5({
2898
3116
  message: "T\xEAn workspace m\u1EDBi (s\u1EBD t\u1EA1o repo new):",
2899
3117
  validate: (v) => v.trim().length > 0 ? true : "T\xEAn kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng"
2900
3118
  });
@@ -2919,7 +3137,7 @@ async function maybeCreateWorkspaceRemote(args) {
2919
3137
  }
2920
3138
  }
2921
3139
  async function resolveWorkspacePath(parent, desiredName, force) {
2922
- const desired = join16(parent, desiredName);
3140
+ const desired = join17(parent, desiredName);
2923
3141
  if (await isEmptyOrMissing(desired)) return desired;
2924
3142
  log.warn(`Workspace path "${desired}" \u0111\xE3 c\xF3 n\u1ED9i dung.`);
2925
3143
  while (true) {
@@ -2934,7 +3152,7 @@ async function resolveWorkspacePath(parent, desiredName, force) {
2934
3152
  }
2935
3153
  choices.push({ name: "Nh\u1EADp t\xEAn workspace kh\xE1c (manual)", value: "manual" });
2936
3154
  choices.push({ name: "T\u1EA1m ng\u01B0ng init", value: "abort" });
2937
- const action = await select7({
3155
+ const action = await select8({
2938
3156
  message: "C\xE1ch x\u1EED l\xFD workspace path conflict?",
2939
3157
  choices
2940
3158
  });
@@ -2946,17 +3164,17 @@ async function resolveWorkspacePath(parent, desiredName, force) {
2946
3164
  if (action === "use-alt" && alternative) {
2947
3165
  return alternative;
2948
3166
  }
2949
- const newName = await input4({
3167
+ const newName = await input5({
2950
3168
  message: "T\xEAn workspace m\u1EDBi:",
2951
3169
  validate: (v) => v.trim().length > 0 ? true : "T\xEAn kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng"
2952
3170
  });
2953
- const newPath = join16(parent, newName.trim());
3171
+ const newPath = join17(parent, newName.trim());
2954
3172
  if (await isEmptyOrMissing(newPath)) return newPath;
2955
3173
  log.warn(`"${newPath}" c\u0169ng \u0111\xE3 c\xF3 n\u1ED9i dung. Th\u1EED t\xEAn kh\xE1c.`);
2956
3174
  }
2957
3175
  }
2958
3176
  async function promptTeamOwner(currentUserEmail) {
2959
- return await input4({ message: "Team owner email:", default: currentUserEmail });
3177
+ return await input5({ message: "Team owner email:", default: currentUserEmail });
2960
3178
  }
2961
3179
  async function maybeCommitWorkspace(workspacePath, skipCommit) {
2962
3180
  if (skipCommit) {
@@ -2996,6 +3214,22 @@ function printInitSuccessBox(rootPath, flow, aiResult = null) {
2996
3214
  `);
2997
3215
  }
2998
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
+
2999
3233
  // src/commands/mcp-run.ts
3000
3234
  function registerMcpRunCommand(program2) {
3001
3235
  program2.command("mcp-run <tool-id>", { hidden: true }).description("[internal] Spawn MCP v\u1EDBi secrets injected (M09)").action(notImplementedYet("mcp-run", "Milestone 09"));
@@ -3028,15 +3262,15 @@ function registerSecretsCommand(program2) {
3028
3262
 
3029
3263
  // src/commands/status.ts
3030
3264
  import { promises as fs8 } from "fs";
3031
- import { join as join18 } from "path";
3265
+ import { join as join19 } from "path";
3032
3266
  import boxen5 from "boxen";
3033
3267
 
3034
3268
  // src/lib/pack-backup-manager.ts
3035
3269
  import { promises as fs7 } from "fs";
3036
- import { join as join17 } from "path";
3270
+ import { join as join18 } from "path";
3037
3271
  var BACKUP_DIR_NAME = "_backup";
3038
3272
  async function listBackups(projectRoot) {
3039
- const dir = join17(projectRoot, ".claude", BACKUP_DIR_NAME);
3273
+ const dir = join18(projectRoot, ".claude", BACKUP_DIR_NAME);
3040
3274
  if (!await pathExists(dir)) return [];
3041
3275
  const entries = await fs7.readdir(dir, { withFileTypes: true });
3042
3276
  return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort().reverse();
@@ -3062,7 +3296,7 @@ function registerStatusCommand(program2) {
3062
3296
  }
3063
3297
  async function gatherStatus(cwd) {
3064
3298
  const projectName = cwd.split("/").filter(Boolean).pop() ?? "unknown";
3065
- const claudeRoot = join18(cwd, ".claude");
3299
+ const claudeRoot = join19(cwd, ".claude");
3066
3300
  const hasAvatar = await pathExists(claudeRoot);
3067
3301
  if (!hasAvatar) {
3068
3302
  return {
@@ -3075,8 +3309,8 @@ async function gatherStatus(cwd) {
3075
3309
  hasAvatar: false
3076
3310
  };
3077
3311
  }
3078
- const packVersion = await isGitRepo(join18(claudeRoot, "pack")) ? await readPinnedPackVersion(cwd).catch(() => null) : null;
3079
- 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");
3080
3314
  const pendingCount = await pathExists(pendingDir) ? (await fs8.readdir(pendingDir)).filter((n) => n.endsWith(".diff.md")).length : 0;
3081
3315
  const backupCount = (await listBackups(cwd)).length;
3082
3316
  const techStackSummary = await readTechStackFirstLine(claudeRoot);
@@ -3091,7 +3325,7 @@ async function gatherStatus(cwd) {
3091
3325
  };
3092
3326
  }
3093
3327
  async function readTechStackFirstLine(claudeRoot) {
3094
- const techStackPath = join18(claudeRoot, "project", "tech-stack.md");
3328
+ const techStackPath = join19(claudeRoot, "project", "tech-stack.md");
3095
3329
  if (!await pathExists(techStackPath)) return "(no tech-stack.md)";
3096
3330
  const content = await readText(techStackPath);
3097
3331
  const firstNonHeaderLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#") && !l.startsWith(">"));
@@ -3132,27 +3366,27 @@ import boxen6 from "boxen";
3132
3366
  // src/lib/create-uninstall-backup-snapshot.ts
3133
3367
  import { cp, mkdir, writeFile } from "fs/promises";
3134
3368
  import { homedir as homedir3 } from "os";
3135
- import { basename as basename2, join as join19 } from "path";
3136
- 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");
3137
3371
  async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersion) {
3138
3372
  const projectName = basename2(projectRoot);
3139
3373
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3140
- const backupDir = join19(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp}`);
3374
+ const backupDir = join20(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp}`);
3141
3375
  await mkdir(backupDir, { recursive: true, mode: 448 });
3142
3376
  if (artifacts.claudeDir) {
3143
- await cp(artifacts.claudeDir, join19(backupDir, ".claude"), { recursive: true });
3377
+ await cp(artifacts.claudeDir, join20(backupDir, ".claude"), { recursive: true });
3144
3378
  }
3145
3379
  if (artifacts.claudeMd) {
3146
- await cp(artifacts.claudeMd, join19(backupDir, "CLAUDE.md"));
3380
+ await cp(artifacts.claudeMd, join20(backupDir, "CLAUDE.md"));
3147
3381
  }
3148
3382
  if (artifacts.postMergeHook || artifacts.prePushHook) {
3149
- const hooksBackupDir = join19(backupDir, "hooks");
3383
+ const hooksBackupDir = join20(backupDir, "hooks");
3150
3384
  await mkdir(hooksBackupDir, { recursive: true });
3151
3385
  if (artifacts.postMergeHook) {
3152
- await cp(artifacts.postMergeHook, join19(hooksBackupDir, "post-merge"));
3386
+ await cp(artifacts.postMergeHook, join20(hooksBackupDir, "post-merge"));
3153
3387
  }
3154
3388
  if (artifacts.prePushHook) {
3155
- await cp(artifacts.prePushHook, join19(hooksBackupDir, "pre-push"));
3389
+ await cp(artifacts.prePushHook, join20(hooksBackupDir, "pre-push"));
3156
3390
  }
3157
3391
  }
3158
3392
  const manifest = {
@@ -3167,27 +3401,27 @@ async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersi
3167
3401
  prePushHook: !!artifacts.prePushHook
3168
3402
  }
3169
3403
  };
3170
- 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");
3171
3405
  return backupDir;
3172
3406
  }
3173
3407
 
3174
3408
  // src/lib/detect-avatar-project-artifacts.ts
3175
- import { existsSync as existsSync5 } from "fs";
3176
- import { join as join20 } from "path";
3409
+ import { existsSync as existsSync6 } from "fs";
3410
+ import { join as join21 } from "path";
3177
3411
  function existsOrNull(path) {
3178
- return existsSync5(path) ? path : null;
3412
+ return existsSync6(path) ? path : null;
3179
3413
  }
3180
3414
  function detectAvatarProjectArtifacts(projectRoot) {
3181
- const claudeDir = existsOrNull(join20(projectRoot, ".claude"));
3182
- const claudeMd = existsOrNull(join20(projectRoot, "CLAUDE.md"));
3183
- 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"));
3184
3418
  const prePushHook = existsOrNull(
3185
- join20(projectRoot, ".git", "modules", "src", "hooks", "pre-push")
3419
+ join21(projectRoot, ".git", "modules", "src", "hooks", "pre-push")
3186
3420
  );
3187
- const gitignorePath = existsOrNull(join20(projectRoot, ".gitignore"));
3188
- const gitmodulesPath = existsOrNull(join20(projectRoot, ".gitmodules"));
3189
- const notesDir = existsOrNull(join20(projectRoot, "notes"));
3190
- 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"));
3191
3425
  const hasAnyArtifact = !!(claudeDir || claudeMd || postMergeHook || prePushHook);
3192
3426
  return {
3193
3427
  hasAnyArtifact,
@@ -3208,11 +3442,11 @@ async function executeUninstallDeletion(artifacts, flags) {
3208
3442
  if (artifacts.claudeDir) {
3209
3443
  if (flags.keepSubmodule) {
3210
3444
  const { readdir: readdir2 } = await import("fs/promises");
3211
- const { join: join21 } = await import("path");
3445
+ const { join: join22 } = await import("path");
3212
3446
  const entries = await readdir2(artifacts.claudeDir);
3213
3447
  for (const entry of entries) {
3214
3448
  if (entry === "pack") continue;
3215
- await rm(join21(artifacts.claudeDir, entry), { recursive: true, force: true });
3449
+ await rm(join22(artifacts.claudeDir, entry), { recursive: true, force: true });
3216
3450
  }
3217
3451
  } else {
3218
3452
  await rm(artifacts.claudeDir, { recursive: true, force: true });
@@ -3281,7 +3515,7 @@ async function removeSubmoduleEntry(gitmodulesPath, submodulePath) {
3281
3515
  }
3282
3516
 
3283
3517
  // src/commands/uninstall.ts
3284
- var CLI_VERSION = "1.2.10";
3518
+ var CLI_VERSION = "1.3.0";
3285
3519
  function registerUninstallCommand(program2) {
3286
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) => {
3287
3521
  try {
@@ -3363,7 +3597,7 @@ function printUninstallSuccessBox(backupPath) {
3363
3597
  }
3364
3598
 
3365
3599
  // src/index.ts
3366
- var CLI_VERSION2 = "1.2.10";
3600
+ var CLI_VERSION2 = "1.3.0";
3367
3601
  var program = new Command();
3368
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(
3369
3603
  "beforeAll",