@nalvietnam/avatar-cli 1.2.11 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { Command } from "commander";
5
5
 
6
6
  // src/commands/ai.ts
7
7
  import { promises as fs4 } from "fs";
8
- import { join as join5 } from "path";
8
+ import { join as join6 } from "path";
9
9
  import { confirm } from "@inquirer/prompts";
10
10
 
11
11
  // src/lib/filesystem-helpers.ts
@@ -42,12 +42,44 @@ async function writeJsonAtomic(path, data, mode) {
42
42
  `, mode);
43
43
  }
44
44
 
45
+ // src/lib/resolve-avatar-workspace-root-from-cwd.ts
46
+ import { existsSync, readFileSync } from "fs";
47
+ import { dirname as dirname2, join as join2 } from "path";
48
+ var MAX_WALKUP_LEVELS = 5;
49
+ function isAvatarWorkspace(dir) {
50
+ const hasClaudeDir = existsSync(join2(dir, ".claude"));
51
+ const hasClaudeMd = existsSync(join2(dir, "CLAUDE.md"));
52
+ if (!hasClaudeDir || !hasClaudeMd) return false;
53
+ const hasSrcDir = existsSync(join2(dir, "src"));
54
+ const gitmodulesPath = join2(dir, ".gitmodules");
55
+ if (hasSrcDir) return true;
56
+ if (existsSync(gitmodulesPath)) {
57
+ try {
58
+ const content = readFileSync(gitmodulesPath, "utf8");
59
+ return content.includes("path = src") || content.includes("path = ./src");
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+ return false;
65
+ }
66
+ function resolveAvatarWorkspaceRootFromCwd(startDir) {
67
+ let current = startDir;
68
+ for (let i = 0; i < MAX_WALKUP_LEVELS; i++) {
69
+ if (isAvatarWorkspace(current)) return current;
70
+ const parent = dirname2(current);
71
+ if (parent === current) return null;
72
+ current = parent;
73
+ }
74
+ return null;
75
+ }
76
+
45
77
  // src/lib/audit-log-appender.ts
46
78
  import { promises as fs2 } from "fs";
47
79
 
48
80
  // src/lib/user-config-store.ts
49
81
  import { homedir } from "os";
50
- import { join as join2 } from "path";
82
+ import { join as join3 } from "path";
51
83
 
52
84
  // src/types/config-schema.ts
53
85
  import { z } from "zod";
@@ -80,11 +112,11 @@ var projectSettingsSchema = z.object({
80
112
  var initModeSchema = z.enum(["internal", "client", "library"]);
81
113
 
82
114
  // src/lib/user-config-store.ts
83
- var AVATAR_HOME = join2(homedir(), ".avatar");
84
- var USER_CONFIG_PATH = join2(AVATAR_HOME, "config.json");
85
- var USER_STATE_PATH = join2(AVATAR_HOME, "state.json");
86
- var AUDIT_LOG_PATH = join2(AVATAR_HOME, "audit.log");
87
- var BACKUPS_DIR = join2(AVATAR_HOME, "backups");
115
+ var AVATAR_HOME = join3(homedir(), ".avatar");
116
+ var USER_CONFIG_PATH = join3(AVATAR_HOME, "config.json");
117
+ var USER_STATE_PATH = join3(AVATAR_HOME, "state.json");
118
+ var AUDIT_LOG_PATH = join3(AVATAR_HOME, "audit.log");
119
+ var BACKUPS_DIR = join3(AVATAR_HOME, "backups");
88
120
  var SECRET_FILE_MODE = 384;
89
121
  async function ensureAvatarHome() {
90
122
  await ensureDir(AVATAR_HOME);
@@ -348,18 +380,18 @@ function installClaudeCodeViaNpm() {
348
380
  }
349
381
 
350
382
  // src/lib/prompt-ai-provider-choice.ts
351
- import { readFileSync } from "fs";
383
+ import { readFileSync as readFileSync2 } from "fs";
352
384
  import { homedir as homedir2 } from "os";
353
- import { join as join3 } from "path";
385
+ import { join as join4 } from "path";
354
386
  import { select } from "@inquirer/prompts";
355
387
  function getGlobalSettingsPath() {
356
- return join3(homedir2(), ".claude", "settings.json");
388
+ return join4(homedir2(), ".claude", "settings.json");
357
389
  }
358
390
  function detectGlobalClaudeSettings() {
359
391
  const path = getGlobalSettingsPath();
360
392
  let raw;
361
393
  try {
362
- raw = readFileSync(path, "utf8");
394
+ raw = readFileSync2(path, "utf8");
363
395
  } catch {
364
396
  return { exists: false, hasBaseUrl: false, hasToken: false };
365
397
  }
@@ -497,10 +529,10 @@ async function setupLLMLiteApiKeyAndModel() {
497
529
 
498
530
  // src/lib/write-claude-settings-json-per-project.ts
499
531
  import { promises as fs3 } from "fs";
500
- import { join as join4 } from "path";
532
+ import { join as join5 } from "path";
501
533
  var SECRET_FILE_MODE2 = 384;
502
534
  function getClaudeSettingsPath(workspacePath) {
503
- return join4(workspacePath, ".claude", "settings.json");
535
+ return join5(workspacePath, ".claude", "settings.json");
504
536
  }
505
537
  async function readExistingSettings(path) {
506
538
  if (!await pathExists(path)) return {};
@@ -546,19 +578,19 @@ function applyUseGlobal(existing, source) {
546
578
  ...sourceModel ? { model: sourceModel } : {}
547
579
  };
548
580
  }
549
- async function writeClaudeSettings(workspacePath, input5) {
581
+ async function writeClaudeSettings(workspacePath, input6) {
550
582
  const path = getClaudeSettingsPath(workspacePath);
551
583
  const existing = await readExistingSettings(path);
552
584
  let merged;
553
- switch (input5.provider) {
585
+ switch (input6.provider) {
554
586
  case "subscription":
555
- merged = applySubscription(existing, input5.model);
587
+ merged = applySubscription(existing, input6.model);
556
588
  break;
557
589
  case "llmlite":
558
- merged = applyLLMLite(existing, input5.apiKey, input5.baseUrl, input5.model);
590
+ merged = applyLLMLite(existing, input6.apiKey, input6.baseUrl, input6.model);
559
591
  break;
560
592
  case "use-global":
561
- merged = applyUseGlobal(existing, input5.sourceSettings);
593
+ merged = applyUseGlobal(existing, input6.sourceSettings);
562
594
  break;
563
595
  }
564
596
  await writeJsonAtomic(path, merged, SECRET_FILE_MODE2);
@@ -752,15 +784,23 @@ async function testAiProviderByDetectedMode(settings) {
752
784
  // src/commands/ai.ts
753
785
  async function ensureWorkspaceCwd() {
754
786
  const cwd = process.cwd();
755
- const claudeDir = join5(cwd, ".claude");
756
- if (!await pathExists(claudeDir)) {
757
- log.error("Kh\xF4ng th\u1EA5y .claude/ trong th\u01B0 m\u1EE5c hi\u1EC7n t\u1EA1i. Ch\u1EA1y l\u1EC7nh n\xE0y trong workspace Avatar.");
787
+ const workspaceRoot = resolveAvatarWorkspaceRootFromCwd(cwd);
788
+ if (!workspaceRoot) {
789
+ log.error(
790
+ `Kh\xF4ng t\xECm th\u1EA5y Avatar workspace t\u1EEB th\u01B0 m\u1EE5c hi\u1EC7n t\u1EA1i.
791
+ Avatar workspace c\u1EA7n c\xF3: .claude/ + CLAUDE.md + src/ (ho\u1EB7c .gitmodules).
792
+ B\u1EA1n \u0111ang \u1EDF: ${cwd}
793
+ Cd v\xE0o workspace dir (vd /path/to/<project>-workspace) r\u1ED3i ch\u1EA1y l\u1EA1i.`
794
+ );
758
795
  process.exit(1);
759
796
  }
760
- return cwd;
797
+ if (workspaceRoot !== cwd) {
798
+ log.dim(`Detected workspace root: ${workspaceRoot}`);
799
+ }
800
+ return workspaceRoot;
761
801
  }
762
802
  async function readWorkspaceSettings(workspacePath) {
763
- const settingsPath = join5(workspacePath, ".claude", "settings.json");
803
+ const settingsPath = join6(workspacePath, ".claude", "settings.json");
764
804
  if (!await pathExists(settingsPath)) return {};
765
805
  try {
766
806
  return await readJson(settingsPath);
@@ -798,7 +838,7 @@ async function runAiTest() {
798
838
  }
799
839
  async function runAiReset(opts) {
800
840
  const workspacePath = await ensureWorkspaceCwd();
801
- const settingsPath = join5(workspacePath, ".claude", "settings.json");
841
+ const settingsPath = join6(workspacePath, ".claude", "settings.json");
802
842
  const settings = await readWorkspaceSettings(workspacePath);
803
843
  if (!opts.yes) {
804
844
  const ok = await confirm({
@@ -843,47 +883,186 @@ function registerAiCommand(program2) {
843
883
  });
844
884
  }
845
885
 
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
- `
886
+ // src/commands/commit.ts
887
+ import { input as input2 } from "@inquirer/prompts";
888
+
889
+ // src/lib/execute-commit-with-target-selection.ts
890
+ import { spawnSync as spawnSync5 } from "child_process";
891
+ import { existsSync as existsSync2 } from "fs";
892
+ import { join as join7 } from "path";
893
+ function assertAvatarWorkspaceRoot(cwd) {
894
+ const srcGit = join7(cwd, "src", ".git");
895
+ const workspaceGit = join7(cwd, ".git");
896
+ const claudeDir = join7(cwd, ".claude");
897
+ if (!existsSync2(workspaceGit)) {
898
+ throw new Error(
899
+ `Kh\xF4ng ph\u1EA3i workspace root: ${cwd}
900
+ Ch\u1EA1y 'avatar commit' trong workspace dir (c\xF3 .git v\xE0 .claude/).`
852
901
  );
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
- };
902
+ }
903
+ if (!existsSync2(claudeDir)) {
904
+ throw new Error(
905
+ `Kh\xF4ng th\u1EA5y .claude/ trong ${cwd}.
906
+ Ch\u1EA1y 'avatar commit' trong Avatar workspace, kh\xF4ng ph\u1EA3i project b\xECnh th\u01B0\u1EDDng.`
907
+ );
908
+ }
909
+ if (!existsSync2(srcGit)) {
910
+ throw new Error(
911
+ `Kh\xF4ng th\u1EA5y src/.git trong ${cwd}.
912
+ Workspace thi\u1EBFu submodule src/. Ch\u1EA1y 'avatar init' l\u1EA1i?`
913
+ );
914
+ }
915
+ }
916
+ function gitExec(cwd, args) {
917
+ const r = spawnSync5("git", ["-C", cwd, ...args], {
918
+ encoding: "utf8",
919
+ stdio: ["ignore", "pipe", "pipe"]
920
+ });
921
+ if (r.status !== 0) {
922
+ const stderr = (r.stderr || "").trim();
923
+ throw new Error(`git ${args.join(" ")} (in ${cwd}) failed:
924
+ ${stderr}`);
925
+ }
926
+ return (r.stdout || "").trim();
927
+ }
928
+ function isDirty(cwd) {
929
+ const status = gitExec(cwd, ["status", "--porcelain"]);
930
+ return status.length > 0;
931
+ }
932
+ async function commitSrc(workspaceRoot, opts) {
933
+ const srcPath = join7(workspaceRoot, "src");
934
+ if (!isDirty(srcPath)) {
935
+ log.dim("src/: nothing to commit (clean)");
936
+ return {};
937
+ }
938
+ log.info("Committing src/ ...");
939
+ gitExec(srcPath, ["add", "."]);
940
+ gitExec(srcPath, ["commit", "-m", opts.message]);
941
+ const sha = gitExec(srcPath, ["rev-parse", "HEAD"]);
942
+ log.success(`src/ committed: ${sha.slice(0, 7)}`);
943
+ let pushed = false;
944
+ if (opts.push) {
945
+ log.info("Pushing src/ ...");
946
+ gitExec(srcPath, ["push"]);
947
+ log.success("src/ pushed");
948
+ pushed = true;
949
+ }
950
+ return { sha, pushed };
951
+ }
952
+ async function commitWorkspace(workspaceRoot, opts) {
953
+ if (!isDirty(workspaceRoot)) {
954
+ log.dim("workspace: nothing to commit (clean)");
955
+ return {};
956
+ }
957
+ log.info("Committing workspace root ...");
958
+ gitExec(workspaceRoot, ["add", "."]);
959
+ gitExec(workspaceRoot, ["commit", "-m", opts.message]);
960
+ const sha = gitExec(workspaceRoot, ["rev-parse", "HEAD"]);
961
+ log.success(`workspace committed: ${sha.slice(0, 7)}`);
962
+ let pushed = false;
963
+ if (opts.push) {
964
+ log.info("Pushing workspace ...");
965
+ gitExec(workspaceRoot, ["push"]);
966
+ log.success("workspace pushed");
967
+ pushed = true;
968
+ }
969
+ return { sha, pushed };
970
+ }
971
+ function warnIfOtherTargetDirty(workspaceRoot, requestedTarget) {
972
+ if (requestedTarget === "all") return;
973
+ const srcDirty = isDirty(join7(workspaceRoot, "src"));
974
+ const workspaceDirty = isDirty(workspaceRoot);
975
+ if (requestedTarget === "src" && workspaceDirty) {
976
+ log.warn(
977
+ "Workspace c\u0169ng c\xF3 changes ch\u01B0a commit. Ch\u1EA1y 'avatar commit workspace' (ho\u1EB7c 'avatar commit all') \u0111\u1EC3 commit lu\xF4n."
978
+ );
979
+ }
980
+ if (requestedTarget === "workspace" && srcDirty) {
981
+ log.warn(
982
+ "src/ c\u0169ng c\xF3 changes ch\u01B0a commit. Ch\u1EA1y 'avatar commit src' (ho\u1EB7c 'avatar commit all') \u0111\u1EC3 commit lu\xF4n."
983
+ );
984
+ }
985
+ }
986
+ async function executeCommitWithTargetSelection(args) {
987
+ assertAvatarWorkspaceRoot(args.workspaceRoot);
988
+ warnIfOtherTargetDirty(args.workspaceRoot, args.target);
989
+ const result = { target: args.target, skipped: [] };
990
+ if (args.target === "src" || args.target === "all") {
991
+ const srcOutcome = await commitSrc(args.workspaceRoot, args.options);
992
+ result.srcCommitSha = srcOutcome.sha;
993
+ result.srcPushed = srcOutcome.pushed;
994
+ if (!srcOutcome.sha) result.skipped?.push("src");
995
+ }
996
+ if (args.target === "workspace" || args.target === "all") {
997
+ const wsOutcome = await commitWorkspace(args.workspaceRoot, args.options);
998
+ result.workspaceCommitSha = wsOutcome.sha;
999
+ result.workspacePushed = wsOutcome.pushed;
1000
+ if (!wsOutcome.sha) result.skipped?.push("workspace");
1001
+ }
1002
+ return result;
860
1003
  }
861
1004
 
862
1005
  // src/commands/commit.ts
863
1006
  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"));
1007
+ const commit = program2.command("commit").description("Commit code (src/) ho\u1EB7c Avatar state (workspace) \u2014 split-aware (M07)");
1008
+ 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) => {
1009
+ await runCommit("src", opts);
1010
+ });
1011
+ 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) => {
1012
+ await runCommit("workspace", opts);
1013
+ });
1014
+ 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) => {
1015
+ await runCommit("all", opts);
1016
+ });
1017
+ }
1018
+ async function runCommit(target, opts) {
1019
+ try {
1020
+ const message = opts.message ?? await input2({
1021
+ message: "Commit message:",
1022
+ validate: (v) => v.trim().length > 0 ? true : "Message b\u1EAFt bu\u1ED9c"
1023
+ });
1024
+ const result = await executeCommitWithTargetSelection({
1025
+ workspaceRoot: process.cwd(),
1026
+ target,
1027
+ options: { message, push: opts.push }
1028
+ });
1029
+ if (result.srcCommitSha) {
1030
+ log.success(`src/: ${result.srcCommitSha.slice(0, 7)}${result.srcPushed ? " (pushed)" : ""}`);
1031
+ }
1032
+ if (result.workspaceCommitSha) {
1033
+ log.success(
1034
+ `workspace: ${result.workspaceCommitSha.slice(0, 7)}${result.workspacePushed ? " (pushed)" : ""}`
1035
+ );
1036
+ }
1037
+ if (result.skipped && result.skipped.length > 0) {
1038
+ log.dim(`Skipped (nothing to commit): ${result.skipped.join(", ")}`);
1039
+ }
1040
+ } catch (err) {
1041
+ log.error(err instanceof Error ? err.message : String(err));
1042
+ process.exit(1);
1043
+ }
865
1044
  }
866
1045
 
867
1046
  // src/commands/doctor.ts
868
- import { spawnSync as spawnSync5 } from "child_process";
1047
+ import { spawnSync as spawnSync6 } from "child_process";
869
1048
  import { promises as fs6 } from "fs";
870
- import { join as join9 } from "path";
1049
+ import { join as join11 } from "path";
871
1050
  import boxen from "boxen";
872
1051
 
873
1052
  // src/lib/git-operations.ts
874
- import { join as join6 } from "path";
1053
+ import { join as join8 } from "path";
875
1054
  import { simpleGit } from "simple-git";
876
1055
  function git(cwd = process.cwd()) {
877
1056
  return simpleGit({ baseDir: cwd, binary: "git" });
878
1057
  }
879
1058
  async function isGitRepo(cwd = process.cwd()) {
880
- return await pathExists(join6(cwd, ".git"));
1059
+ return await pathExists(join8(cwd, ".git"));
881
1060
  }
882
1061
  async function addSubmodule(repoUrl, destPath, cwd = process.cwd()) {
883
1062
  await git(cwd).subModule(["add", repoUrl, destPath]);
884
1063
  }
885
1064
  async function checkoutTagInSubmodule(submodulePath, tag, cwd = process.cwd()) {
886
- const submoduleCwd = join6(cwd, submodulePath);
1065
+ const submoduleCwd = join8(cwd, submodulePath);
887
1066
  await git(submoduleCwd).fetch(["--tags"]);
888
1067
  await git(submoduleCwd).checkout(tag);
889
1068
  }
@@ -902,11 +1081,11 @@ async function currentCommitSha(cwd = process.cwd()) {
902
1081
 
903
1082
  // src/lib/project-tree-scaffolder.ts
904
1083
  import { promises as fs5 } from "fs";
905
- import { join as join8 } from "path";
1084
+ import { join as join10 } from "path";
906
1085
 
907
1086
  // src/lib/template-bundle-loader.ts
908
- import { existsSync } from "fs";
909
- import { dirname as dirname2, join as join7 } from "path";
1087
+ import { existsSync as existsSync3 } from "fs";
1088
+ import { dirname as dirname3, join as join9 } from "path";
910
1089
  import { fileURLToPath } from "url";
911
1090
 
912
1091
  // src/lib/mustache-template-engine.ts
@@ -920,15 +1099,15 @@ function renderTemplate(source, variables) {
920
1099
  }
921
1100
 
922
1101
  // src/lib/template-bundle-loader.ts
923
- var HERE = dirname2(fileURLToPath(import.meta.url));
1102
+ var HERE = dirname3(fileURLToPath(import.meta.url));
924
1103
  var PACKAGE_ROOT = findPackageRoot(HERE);
925
- var TEMPLATES_ROOT = join7(PACKAGE_ROOT, "src", "templates");
926
- var HOOKS_ROOT = join7(PACKAGE_ROOT, "src", "hooks");
1104
+ var TEMPLATES_ROOT = join9(PACKAGE_ROOT, "src", "templates");
1105
+ var HOOKS_ROOT = join9(PACKAGE_ROOT, "src", "hooks");
927
1106
  function findPackageRoot(startDir) {
928
1107
  let dir = startDir;
929
1108
  while (true) {
930
- if (existsSync(join7(dir, "package.json"))) return dir;
931
- const parent = dirname2(dir);
1109
+ if (existsSync3(join9(dir, "package.json"))) return dir;
1110
+ const parent = dirname3(dir);
932
1111
  if (parent === dir) {
933
1112
  throw new Error(`Cannot locate package root from ${startDir}`);
934
1113
  }
@@ -936,14 +1115,14 @@ function findPackageRoot(startDir) {
936
1115
  }
937
1116
  }
938
1117
  async function loadTemplate(name) {
939
- return await readText(join7(TEMPLATES_ROOT, `${name}.tpl`));
1118
+ return await readText(join9(TEMPLATES_ROOT, `${name}.tpl`));
940
1119
  }
941
1120
  async function renderTemplateByName(name, variables) {
942
1121
  const source = await loadTemplate(name);
943
1122
  return renderTemplate(source, variables);
944
1123
  }
945
1124
  async function loadHook(name) {
946
- return await readText(join7(HOOKS_ROOT, `${name}.sh.tpl`));
1125
+ return await readText(join9(HOOKS_ROOT, `${name}.sh.tpl`));
947
1126
  }
948
1127
 
949
1128
  // src/lib/project-tree-scaffolder.ts
@@ -977,12 +1156,12 @@ var PROJECT_KNOWLEDGE_TEMPLATES = [
977
1156
  "project/gotchas.md"
978
1157
  ];
979
1158
  async function createClaudeDirTree(projectRoot) {
980
- const claudeRoot = join8(projectRoot, ".claude");
1159
+ const claudeRoot = join10(projectRoot, ".claude");
981
1160
  await ensureDir(claudeRoot);
982
1161
  for (const sub of CLAUDE_SUBDIRS) {
983
- const dir = join8(claudeRoot, sub);
1162
+ const dir = join10(claudeRoot, sub);
984
1163
  await ensureDir(dir);
985
- await writeTextAtomic(join8(dir, ".gitkeep"), "");
1164
+ await writeTextAtomic(join10(dir, ".gitkeep"), "");
986
1165
  }
987
1166
  }
988
1167
  async function writeProjectKnowledgeFiles(projectRoot, vars) {
@@ -1013,7 +1192,7 @@ async function writeProjectKnowledgeFiles(projectRoot, vars) {
1013
1192
  for (const tpl of PROJECT_KNOWLEDGE_TEMPLATES) {
1014
1193
  const content = await renderTemplateByName(tpl, baseVars);
1015
1194
  const relative4 = tpl.replace(/^project\//, "");
1016
- const outPath = join8(projectRoot, ".claude", "project", relative4);
1195
+ const outPath = join10(projectRoot, ".claude", "project", relative4);
1017
1196
  const backup = await writeWithBackup(outPath, content);
1018
1197
  if (backup) backups.push(backup);
1019
1198
  }
@@ -1021,14 +1200,14 @@ async function writeProjectKnowledgeFiles(projectRoot, vars) {
1021
1200
  }
1022
1201
  async function writeRootClaudeMd(projectRoot, vars) {
1023
1202
  const content = await renderTemplateByName("CLAUDE.md", vars);
1024
- return await writeWithBackup(join8(projectRoot, "CLAUDE.md"), content);
1203
+ return await writeWithBackup(join10(projectRoot, "CLAUDE.md"), content);
1025
1204
  }
1026
1205
  async function writeProjectSettings(projectRoot, vars) {
1027
1206
  const content = await renderTemplateByName("settings.json", vars);
1028
- return await writeWithBackup(join8(projectRoot, ".claude", "settings.json"), content);
1207
+ return await writeWithBackup(join10(projectRoot, ".claude", "settings.json"), content);
1029
1208
  }
1030
1209
  async function appendGitignoreEntries(projectRoot) {
1031
- const path = join8(projectRoot, ".gitignore");
1210
+ const path = join10(projectRoot, ".gitignore");
1032
1211
  const tpl = await renderTemplateByName("gitignore", {});
1033
1212
  const marker = "# Avatar \u2014 git-ignored entries injected on `avatar init`";
1034
1213
  let existing = "";
@@ -1042,9 +1221,9 @@ ${tpl}`);
1042
1221
  }
1043
1222
  async function installGitHook(gitDir, hookName) {
1044
1223
  const content = await loadHook(hookName);
1045
- const hooksDir = join8(gitDir, "hooks");
1224
+ const hooksDir = join10(gitDir, "hooks");
1046
1225
  await ensureDir(hooksDir);
1047
- const dest = join8(hooksDir, hookName);
1226
+ const dest = join10(hooksDir, hookName);
1048
1227
  await writeTextAtomic(dest, content, 493);
1049
1228
  }
1050
1229
 
@@ -1102,7 +1281,7 @@ async function runChecks(cwd) {
1102
1281
  detail: gitRepo ? cwd : "Kh\xF4ng ph\u1EA3i git repo (c\u1EA7n cho 'avatar init')",
1103
1282
  fixable: false
1104
1283
  });
1105
- const packPath = join9(cwd, ".claude", "pack");
1284
+ const packPath = join11(cwd, ".claude", "pack");
1106
1285
  const hasPack = await pathExists(packPath);
1107
1286
  checks.push({
1108
1287
  name: "team-ai-pack submodule",
@@ -1110,7 +1289,7 @@ async function runChecks(cwd) {
1110
1289
  detail: hasPack ? packPath : "Avatar ch\u01B0a init \u2014 ch\u1EA1y 'avatar init'",
1111
1290
  fixable: false
1112
1291
  });
1113
- const claudeMdPath = join9(cwd, "CLAUDE.md");
1292
+ const claudeMdPath = join11(cwd, "CLAUDE.md");
1114
1293
  const hasClaudeMd = await pathExists(claudeMdPath);
1115
1294
  checks.push({
1116
1295
  name: "CLAUDE.md",
@@ -1118,7 +1297,7 @@ async function runChecks(cwd) {
1118
1297
  detail: hasClaudeMd ? "t\u1ED3n t\u1EA1i \u1EDF project root" : "thi\u1EBFu \u2014 ch\u1EA1y 'avatar init'",
1119
1298
  fixable: false
1120
1299
  });
1121
- const hookPath = join9(cwd, ".git", "hooks", "post-merge");
1300
+ const hookPath = join11(cwd, ".git", "hooks", "post-merge");
1122
1301
  const hasHook = await pathExists(hookPath);
1123
1302
  if (gitRepo && hasPack) {
1124
1303
  checks.push({
@@ -1127,11 +1306,11 @@ async function runChecks(cwd) {
1127
1306
  detail: hasHook ? "installed" : "missing \u2014 fixable",
1128
1307
  fixable: !hasHook,
1129
1308
  fix: hasHook ? void 0 : async () => {
1130
- await installGitHook(join9(cwd, ".git"), "post-merge");
1309
+ await installGitHook(join11(cwd, ".git"), "post-merge");
1131
1310
  }
1132
1311
  });
1133
1312
  }
1134
- const gitignorePath = join9(cwd, ".gitignore");
1313
+ const gitignorePath = join11(cwd, ".gitignore");
1135
1314
  if (gitRepo) {
1136
1315
  let gitignoreOk = false;
1137
1316
  if (await pathExists(gitignorePath)) {
@@ -1145,7 +1324,7 @@ async function runChecks(cwd) {
1145
1324
  fixable: false
1146
1325
  });
1147
1326
  }
1148
- const which = spawnSync5("which", ["claude"]);
1327
+ const which = spawnSync6("which", ["claude"]);
1149
1328
  const hasClaudeCli = which.status === 0;
1150
1329
  checks.push({
1151
1330
  name: "Claude Code CLI",
@@ -1193,16 +1372,16 @@ async function applyFixes(checks) {
1193
1372
  }
1194
1373
 
1195
1374
  // 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";
1375
+ import { basename, join as join18, relative as relative2, resolve } from "path";
1376
+ import { confirm as confirm3, input as input5, select as select8 } from "@inquirer/prompts";
1198
1377
  import boxen4 from "boxen";
1199
1378
 
1200
1379
  // src/lib/add-team-pack-submodule-with-retry-on-network-fail.ts
1201
- import { spawnSync as spawnSync7 } from "child_process";
1380
+ import { spawnSync as spawnSync8 } from "child_process";
1202
1381
  import { select as select5 } from "@inquirer/prompts";
1203
1382
 
1204
1383
  // src/lib/prompt-recovery-action-on-failure.ts
1205
- import { input as input2, select as select3 } from "@inquirer/prompts";
1384
+ import { input as input3, select as select3 } from "@inquirer/prompts";
1206
1385
  var UserAbortedRecoveryError = class extends Error {
1207
1386
  constructor(message) {
1208
1387
  super(message);
@@ -1226,10 +1405,10 @@ async function promptRetryOrSkip(args) {
1226
1405
  }
1227
1406
 
1228
1407
  // src/lib/team-pack-submodule-manager.ts
1229
- import { join as join10 } from "path";
1408
+ import { join as join12 } from "path";
1230
1409
 
1231
1410
  // src/lib/check-team-pack-access-with-retry-loop.ts
1232
- import { spawnSync as spawnSync6 } from "child_process";
1411
+ import { spawnSync as spawnSync7 } from "child_process";
1233
1412
  import { confirm as confirm2, select as select4 } from "@inquirer/prompts";
1234
1413
  import boxen2 from "boxen";
1235
1414
  function parseRepoSlugFromGitUrl(url) {
@@ -1238,11 +1417,11 @@ function parseRepoSlugFromGitUrl(url) {
1238
1417
  return null;
1239
1418
  }
1240
1419
  function checkRepoAccess(repoSlug) {
1241
- const r = spawnSync6("gh", ["api", `repos/${repoSlug}`], { stdio: "ignore" });
1420
+ const r = spawnSync7("gh", ["api", `repos/${repoSlug}`], { stdio: "ignore" });
1242
1421
  return r.status === 0;
1243
1422
  }
1244
1423
  function getCurrentGhUser() {
1245
- const r = spawnSync6("gh", ["api", "user", "--jq", ".login"], {
1424
+ const r = spawnSync7("gh", ["api", "user", "--jq", ".login"], {
1246
1425
  encoding: "utf8",
1247
1426
  stdio: ["ignore", "pipe", "pipe"]
1248
1427
  });
@@ -1251,7 +1430,7 @@ function getCurrentGhUser() {
1251
1430
  }
1252
1431
  function triggerGhAuthLoginInteractive() {
1253
1432
  log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
1254
- const r = spawnSync6("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1433
+ const r = spawnSync7("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1255
1434
  if (r.status !== 0) {
1256
1435
  log.warn(`gh auth login exit ${r.status}. C\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
1257
1436
  }
@@ -1391,7 +1570,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
1391
1570
  }
1392
1571
  let target = tag ?? null;
1393
1572
  if (!target) {
1394
- target = await latestTag(join10(projectRoot, TEAM_PACK_RELATIVE_PATH));
1573
+ target = await latestTag(join12(projectRoot, TEAM_PACK_RELATIVE_PATH));
1395
1574
  }
1396
1575
  if (target) {
1397
1576
  await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, target, projectRoot);
@@ -1399,7 +1578,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
1399
1578
  return { pinnedTag: target };
1400
1579
  }
1401
1580
  async function readPinnedPackVersion(projectRoot) {
1402
- const submoduleRoot = join10(projectRoot, TEAM_PACK_RELATIVE_PATH);
1581
+ const submoduleRoot = join12(projectRoot, TEAM_PACK_RELATIVE_PATH);
1403
1582
  const tag = await latestTag(submoduleRoot);
1404
1583
  if (tag) return tag;
1405
1584
  const sha = await currentCommitSha(submoduleRoot);
@@ -1413,14 +1592,14 @@ function isSshPermissionError(message) {
1413
1592
  }
1414
1593
  function triggerGhAuthLoginInteractive2() {
1415
1594
  log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
1416
- const r = spawnSync7("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1595
+ const r = spawnSync8("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1417
1596
  if (r.status !== 0) {
1418
1597
  log.warn(`gh auth login exit ${r.status}. C\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
1419
1598
  }
1420
1599
  }
1421
1600
  function openGithubSshKeysPage() {
1422
1601
  log.info("M\u1EDF trang GitHub Settings \u2192 SSH Keys...");
1423
- const r = spawnSync7("open", ["https://github.com/settings/keys"], { stdio: "ignore" });
1602
+ const r = spawnSync8("open", ["https://github.com/settings/keys"], { stdio: "ignore" });
1424
1603
  if (r.status !== 0) {
1425
1604
  log.info("URL: https://github.com/settings/keys");
1426
1605
  }
@@ -1570,27 +1749,27 @@ ${renderAvatarBanner(opts)}
1570
1749
  }
1571
1750
 
1572
1751
  // src/lib/execute-gh-repo-create.ts
1573
- import { spawnSync as spawnSync8 } from "child_process";
1752
+ import { spawnSync as spawnSync9 } from "child_process";
1574
1753
  var RepoAlreadyExistsError = class extends Error {
1575
1754
  constructor(fullName) {
1576
1755
  super(`Repo "${fullName}" \u0111\xE3 t\u1ED3n t\u1EA1i tr\xEAn GitHub. \u0110\u1ED5i t\xEAn ho\u1EB7c x\xF3a repo c\u0169.`);
1577
1756
  this.name = "RepoAlreadyExistsError";
1578
1757
  }
1579
1758
  };
1580
- function executeGhRepoCreate(input5) {
1581
- const fullName = `${input5.org}/${input5.name}`;
1759
+ function executeGhRepoCreate(input6) {
1760
+ const fullName = `${input6.org}/${input6.name}`;
1582
1761
  const args = [
1583
1762
  "repo",
1584
1763
  "create",
1585
1764
  fullName,
1586
- `--${input5.visibility}`,
1765
+ `--${input6.visibility}`,
1587
1766
  "--source",
1588
- input5.folder,
1767
+ input6.folder,
1589
1768
  "--remote",
1590
1769
  "origin",
1591
1770
  "--push"
1592
1771
  ];
1593
- const r = spawnSync8("gh", args, { stdio: "inherit" });
1772
+ const r = spawnSync9("gh", args, { stdio: "inherit" });
1594
1773
  if (r.status !== 0) {
1595
1774
  if (r.status === 1) {
1596
1775
  throw new RepoAlreadyExistsError(fullName);
@@ -1604,9 +1783,9 @@ function executeGhRepoCreate(input5) {
1604
1783
  }
1605
1784
 
1606
1785
  // src/lib/resolve-github-username-default.ts
1607
- import { spawnSync as spawnSync9 } from "child_process";
1786
+ import { spawnSync as spawnSync10 } from "child_process";
1608
1787
  function resolveGithubUsernameDefault() {
1609
- const r = spawnSync9("gh", ["api", "user", "--jq", ".login"], {
1788
+ const r = spawnSync10("gh", ["api", "user", "--jq", ".login"], {
1610
1789
  encoding: "utf8",
1611
1790
  stdio: ["ignore", "pipe", "pipe"]
1612
1791
  });
@@ -1638,28 +1817,28 @@ function validateRepoVisibility(v) {
1638
1817
  }
1639
1818
 
1640
1819
  // 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})...`);
1820
+ function createGithubRemoteFromFolder(input6) {
1821
+ validateRepoName(input6.name);
1822
+ validateRepoVisibility(input6.visibility);
1823
+ const org = input6.org ?? resolveGithubUsernameDefault();
1824
+ log.info(`T\u1EA1o GitHub repo ${org}/${input6.name} (${input6.visibility})...`);
1646
1825
  const urls = executeGhRepoCreate({
1647
- folder: input5.folder,
1826
+ folder: input6.folder,
1648
1827
  org,
1649
- name: input5.name,
1650
- visibility: input5.visibility
1828
+ name: input6.name,
1829
+ visibility: input6.visibility
1651
1830
  });
1652
1831
  log.success(`\u0110\xE3 t\u1EA1o: ${urls.sshUrl}`);
1653
1832
  return urls;
1654
1833
  }
1655
1834
 
1656
1835
  // src/lib/create-workspace-remote-via-gh.ts
1657
- import { spawnSync as spawnSync17 } from "child_process";
1836
+ import { spawnSync as spawnSync18 } from "child_process";
1658
1837
 
1659
1838
  // src/lib/check-gh-cli-auth-status.ts
1660
- import { spawnSync as spawnSync10 } from "child_process";
1839
+ import { spawnSync as spawnSync11 } from "child_process";
1661
1840
  function checkGhCliAuthStatus() {
1662
- const r = spawnSync10("gh", ["auth", "status"], { stdio: "ignore" });
1841
+ const r = spawnSync11("gh", ["auth", "status"], { stdio: "ignore" });
1663
1842
  if (r.error && r.error.code === "ENOENT") {
1664
1843
  return "not-installed";
1665
1844
  }
@@ -1667,12 +1846,12 @@ function checkGhCliAuthStatus() {
1667
1846
  }
1668
1847
 
1669
1848
  // src/lib/detect-package-manager.ts
1670
- import { spawnSync as spawnSync11 } from "child_process";
1849
+ import { spawnSync as spawnSync12 } from "child_process";
1671
1850
  function hasBinary(name) {
1672
1851
  const platform2 = detectHostPlatform();
1673
1852
  const probe = platform2 === "win32" ? "where" : "command";
1674
1853
  const args = platform2 === "win32" ? [name] : ["-v", name];
1675
- const r = spawnSync11(probe, args, {
1854
+ const r = spawnSync12(probe, args, {
1676
1855
  shell: platform2 !== "win32",
1677
1856
  stdio: "ignore"
1678
1857
  });
@@ -1688,11 +1867,11 @@ function detectPackageManager() {
1688
1867
  }
1689
1868
 
1690
1869
  // 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";
1870
+ import { spawnSync as spawnSync14 } from "child_process";
1871
+ import { input as input4, select as select6 } from "@inquirer/prompts";
1693
1872
 
1694
1873
  // src/lib/verify-git-remote-accessible.ts
1695
- import { spawnSync as spawnSync12 } from "child_process";
1874
+ import { spawnSync as spawnSync13 } from "child_process";
1696
1875
  var TIMEOUT_MS = 5e3;
1697
1876
  function classifyRemoteError(stderr) {
1698
1877
  const text = stderr.toLowerCase();
@@ -1708,7 +1887,7 @@ function classifyRemoteError(stderr) {
1708
1887
  return "unknown";
1709
1888
  }
1710
1889
  function tryVerifyGitRemoteAccessible(url) {
1711
- const r = spawnSync12("git", ["ls-remote", "--exit-code", url, "HEAD"], {
1890
+ const r = spawnSync13("git", ["ls-remote", "--exit-code", url, "HEAD"], {
1712
1891
  encoding: "utf8",
1713
1892
  timeout: TIMEOUT_MS,
1714
1893
  stdio: ["ignore", "pipe", "pipe"]
@@ -1730,7 +1909,7 @@ var RemoteAccessAbortedError = class extends Error {
1730
1909
  }
1731
1910
  };
1732
1911
  function getCurrentGhUser2() {
1733
- const r = spawnSync13("gh", ["api", "user", "--jq", ".login"], {
1912
+ const r = spawnSync14("gh", ["api", "user", "--jq", ".login"], {
1734
1913
  encoding: "utf8",
1735
1914
  stdio: ["ignore", "pipe", "pipe"]
1736
1915
  });
@@ -1739,7 +1918,7 @@ function getCurrentGhUser2() {
1739
1918
  }
1740
1919
  function triggerGhAuthLoginInteractive3() {
1741
1920
  log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
1742
- const r = spawnSync13("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1921
+ const r = spawnSync14("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1743
1922
  if (r.status !== 0) {
1744
1923
  log.warn(`gh auth login exit ${r.status}. B\u1EA1n c\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
1745
1924
  }
@@ -1800,7 +1979,7 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
1800
1979
  );
1801
1980
  }
1802
1981
  if (action === "re-input-url") {
1803
- const newUrl = await input3({
1982
+ const newUrl = await input4({
1804
1983
  message: "URL git remote (https://github.com/owner/repo.git ho\u1EB7c git@github.com:owner/repo.git):",
1805
1984
  default: currentUrl,
1806
1985
  validate: (v) => isValidGitUrl(v) || "URL kh\xF4ng \u0111\xFAng format git remote"
@@ -1822,7 +2001,7 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
1822
2001
  }
1823
2002
 
1824
2003
  // src/lib/install-gh-cli-via-package-manager.ts
1825
- import { spawnSync as spawnSync14 } from "child_process";
2004
+ import { spawnSync as spawnSync15 } from "child_process";
1826
2005
  var INSTALL_COMMANDS = {
1827
2006
  brew: { cmd: "brew", args: ["install", "gh"] },
1828
2007
  apt: { cmd: "sudo", args: ["apt-get", "install", "-y", "gh"] },
@@ -1833,7 +2012,7 @@ var INSTALL_COMMANDS = {
1833
2012
  function installGhCliViaPackageManager(pm) {
1834
2013
  const spec = INSTALL_COMMANDS[pm];
1835
2014
  log.info(`\u0110ang c\xE0i gh CLI qua ${pm}...`);
1836
- const r = spawnSync14(spec.cmd, spec.args, { stdio: "inherit" });
2015
+ const r = spawnSync15(spec.cmd, spec.args, { stdio: "inherit" });
1837
2016
  if (r.status !== 0) {
1838
2017
  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
2018
  }
@@ -1841,9 +2020,9 @@ function installGhCliViaPackageManager(pm) {
1841
2020
  }
1842
2021
 
1843
2022
  // src/lib/setup-git-credential-via-gh.ts
1844
- import { spawnSync as spawnSync15 } from "child_process";
2023
+ import { spawnSync as spawnSync16 } from "child_process";
1845
2024
  function setupGitCredentialViaGh() {
1846
- const r = spawnSync15("gh", ["auth", "setup-git"], { stdio: "ignore" });
2025
+ const r = spawnSync16("gh", ["auth", "setup-git"], { stdio: "ignore" });
1847
2026
  if (r.status !== 0) {
1848
2027
  log.warn("gh auth setup-git fail (non-fatal). N\u1EBFu git clone l\u1ED7i 128 \u2192 ch\u1EA1y th\u1EE7 c\xF4ng.");
1849
2028
  return;
@@ -1852,10 +2031,10 @@ function setupGitCredentialViaGh() {
1852
2031
  }
1853
2032
 
1854
2033
  // src/lib/trigger-gh-cli-auth-login.ts
1855
- import { spawnSync as spawnSync16 } from "child_process";
2034
+ import { spawnSync as spawnSync17 } from "child_process";
1856
2035
  function triggerGhCliAuthLogin() {
1857
2036
  log.info("Kh\u1EDFi \u0111\u1ED9ng \u0111\u0103ng nh\u1EADp GitHub qua gh CLI (browser s\u1EBD m\u1EDF)...");
1858
- const r = spawnSync16(
2037
+ const r = spawnSync17(
1859
2038
  "gh",
1860
2039
  ["auth", "login", "--hostname", "github.com", "--web", "--git-protocol", "ssh"],
1861
2040
  { stdio: "inherit" }
@@ -1973,20 +2152,20 @@ function classifyGhCreateError(stderr) {
1973
2152
  return "unknown";
1974
2153
  }
1975
2154
  function repoExistsOnGitHub(fullName) {
1976
- const r = spawnSync17("gh", ["repo", "view", fullName, "--json", "name"], {
2155
+ const r = spawnSync18("gh", ["repo", "view", fullName, "--json", "name"], {
1977
2156
  stdio: "ignore"
1978
2157
  });
1979
2158
  return r.status === 0;
1980
2159
  }
1981
2160
  function canCreateInNamespace(org, ghUser) {
1982
2161
  if (org.toLowerCase() === ghUser.toLowerCase()) return { ok: true };
1983
- const r = spawnSync17("gh", ["api", `orgs/${org}/members/${ghUser}`, "--silent"], {
2162
+ const r = spawnSync18("gh", ["api", `orgs/${org}/members/${ghUser}`, "--silent"], {
1984
2163
  stdio: "ignore"
1985
2164
  });
1986
2165
  if (r.status === 0) return { ok: true };
1987
- const orgCheck = spawnSync17("gh", ["api", `orgs/${org}`, "--silent"], { stdio: "ignore" });
2166
+ const orgCheck = spawnSync18("gh", ["api", `orgs/${org}`, "--silent"], { stdio: "ignore" });
1988
2167
  if (orgCheck.status !== 0) {
1989
- const userCheck = spawnSync17("gh", ["api", `users/${org}`, "--silent"], { stdio: "ignore" });
2168
+ const userCheck = spawnSync18("gh", ["api", `users/${org}`, "--silent"], { stdio: "ignore" });
1990
2169
  if (userCheck.status === 0) {
1991
2170
  return {
1992
2171
  ok: false,
@@ -2003,17 +2182,17 @@ function canCreateInNamespace(org, ghUser) {
2003
2182
  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
2183
  };
2005
2184
  }
2006
- async function createWorkspaceRemoteViaGh(input5) {
2007
- validateRepoName(input5.workspaceName);
2008
- validateRepoVisibility(input5.visibility);
2185
+ async function createWorkspaceRemoteViaGh(input6) {
2186
+ validateRepoName(input6.workspaceName);
2187
+ validateRepoVisibility(input6.visibility);
2009
2188
  await ensureGitHubReady();
2010
2189
  const ghUser = resolveGithubUsernameDefault();
2011
- const org = input5.org ?? ghUser;
2190
+ const org = input6.org ?? ghUser;
2012
2191
  const namespaceCheck = canCreateInNamespace(org, ghUser);
2013
2192
  if (!namespaceCheck.ok) {
2014
2193
  throw new Error(`Kh\xF4ng th\u1EC3 t\u1EA1o repo d\u01B0\u1EDBi '${org}/': ${namespaceCheck.reason}`);
2015
2194
  }
2016
- const fullName = `${org}/${input5.workspaceName}`;
2195
+ const fullName = `${org}/${input6.workspaceName}`;
2017
2196
  if (repoExistsOnGitHub(fullName)) {
2018
2197
  throw new CreateWorkspaceRemoteError(
2019
2198
  "repo-exists",
@@ -2021,16 +2200,16 @@ async function createWorkspaceRemoteViaGh(input5) {
2021
2200
  `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
2201
  );
2023
2202
  }
2024
- log.info(`T\u1EA1o GitHub repo cho workspace: ${fullName} (${input5.visibility})...`);
2025
- const r = spawnSync17(
2203
+ log.info(`T\u1EA1o GitHub repo cho workspace: ${fullName} (${input6.visibility})...`);
2204
+ const r = spawnSync18(
2026
2205
  "gh",
2027
2206
  [
2028
2207
  "repo",
2029
2208
  "create",
2030
2209
  fullName,
2031
- `--${input5.visibility}`,
2210
+ `--${input6.visibility}`,
2032
2211
  "--source",
2033
- input5.workspacePath,
2212
+ input6.workspacePath,
2034
2213
  "--remote",
2035
2214
  "origin",
2036
2215
  "--push"
@@ -2063,7 +2242,7 @@ ${combined}
2063
2242
  function linkExistingRemoteToWorkspace(args) {
2064
2243
  const sshUrl = `git@github.com:${args.fullName}.git`;
2065
2244
  const httpsUrl = `https://github.com/${args.fullName}.git`;
2066
- const addResult = spawnSync17(
2245
+ const addResult = spawnSync18(
2067
2246
  "git",
2068
2247
  ["-C", args.workspacePath, "remote", "add", "origin", sshUrl],
2069
2248
  {
@@ -2072,7 +2251,7 @@ function linkExistingRemoteToWorkspace(args) {
2072
2251
  }
2073
2252
  );
2074
2253
  if (addResult.status !== 0) {
2075
- spawnSync17("git", ["-C", args.workspacePath, "remote", "set-url", "origin", sshUrl], {
2254
+ spawnSync18("git", ["-C", args.workspacePath, "remote", "set-url", "origin", sshUrl], {
2076
2255
  stdio: "ignore"
2077
2256
  });
2078
2257
  }
@@ -2086,11 +2265,11 @@ import { select as select7 } from "@inquirer/prompts";
2086
2265
  import { simpleGit as simpleGit3 } from "simple-git";
2087
2266
 
2088
2267
  // src/lib/check-folder-has-git.ts
2089
- import { existsSync as existsSync2, statSync } from "fs";
2090
- import { join as join11 } from "path";
2268
+ import { existsSync as existsSync4, statSync } from "fs";
2269
+ import { join as join13 } from "path";
2091
2270
  function checkFolderHasGit(folderPath) {
2092
- const gitPath = join11(folderPath, ".git");
2093
- if (!existsSync2(gitPath)) return false;
2271
+ const gitPath = join13(folderPath, ".git");
2272
+ if (!existsSync4(gitPath)) return false;
2094
2273
  const stat = statSync(gitPath);
2095
2274
  return stat.isDirectory() || stat.isFile();
2096
2275
  }
@@ -2120,8 +2299,8 @@ async function createInitialGitCommit(folderPath) {
2120
2299
  }
2121
2300
 
2122
2301
  // src/lib/detect-folder-tech-stack.ts
2123
- import { existsSync as existsSync3 } from "fs";
2124
- import { join as join12 } from "path";
2302
+ import { existsSync as existsSync5 } from "fs";
2303
+ import { join as join14 } from "path";
2125
2304
  var SIGNATURES = {
2126
2305
  node: ["package.json"],
2127
2306
  python: ["pyproject.toml", "requirements.txt", "setup.py", "Pipfile"],
@@ -2133,7 +2312,7 @@ var SIGNATURES = {
2133
2312
  function detectFolderTechStack(folderPath) {
2134
2313
  const matched = [];
2135
2314
  for (const [stack, files] of Object.entries(SIGNATURES)) {
2136
- if (files.some((f) => existsSync3(join12(folderPath, f)))) {
2315
+ if (files.some((f) => existsSync5(join14(folderPath, f)))) {
2137
2316
  matched.push(stack);
2138
2317
  }
2139
2318
  }
@@ -2141,26 +2320,26 @@ function detectFolderTechStack(folderPath) {
2141
2320
  }
2142
2321
 
2143
2322
  // src/lib/gitignore-template-loader.ts
2144
- import { readFileSync as readFileSync2 } from "fs";
2145
- import { dirname as dirname3, join as join13 } from "path";
2323
+ import { readFileSync as readFileSync3 } from "fs";
2324
+ import { dirname as dirname4, join as join15 } from "path";
2146
2325
  import { fileURLToPath as fileURLToPath2 } from "url";
2147
- var __dirname = dirname3(fileURLToPath2(import.meta.url));
2326
+ var __dirname = dirname4(fileURLToPath2(import.meta.url));
2148
2327
  var CANDIDATE_DIRS = [
2149
2328
  // Bundled production: dist/index.js → __dirname = .../dist/, sibling dist/templates
2150
- join13(__dirname, "templates", "gitignore"),
2329
+ join15(__dirname, "templates", "gitignore"),
2151
2330
  // Legacy bundled: nếu file là dist/lib/*.js (sub-bundle), templates ở dist/templates
2152
- join13(__dirname, "..", "templates", "gitignore"),
2331
+ join15(__dirname, "..", "templates", "gitignore"),
2153
2332
  // Dev mode (vitest/tsx run src/ trực tiếp): __dirname = src/lib/
2154
- join13(__dirname, "..", "..", "src", "templates", "gitignore"),
2333
+ join15(__dirname, "..", "..", "src", "templates", "gitignore"),
2155
2334
  // npm-installed alt: __dirname = .../dist/ → package_root/src/templates
2156
- join13(__dirname, "..", "src", "templates", "gitignore")
2335
+ join15(__dirname, "..", "src", "templates", "gitignore")
2157
2336
  ];
2158
2337
  var AVATAR_MARKER_START = "# === avatar ===";
2159
2338
  var AVATAR_MARKER_END = "# === /avatar ===";
2160
2339
  function readTemplate(stack) {
2161
2340
  for (const dir of CANDIDATE_DIRS) {
2162
2341
  try {
2163
- return readFileSync2(join13(dir, `${stack}.txt`), "utf8");
2342
+ return readFileSync3(join15(dir, `${stack}.txt`), "utf8");
2164
2343
  } catch {
2165
2344
  }
2166
2345
  }
@@ -2174,15 +2353,15 @@ ${readTemplate(s).trim()}`);
2174
2353
  }
2175
2354
 
2176
2355
  // 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";
2356
+ import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync } from "fs";
2357
+ import { join as join16 } from "path";
2179
2358
  function writeOrMergeGitignore(folderPath, avatarBlock) {
2180
- const path = join14(folderPath, ".gitignore");
2181
- if (!existsSync4(path)) {
2359
+ const path = join16(folderPath, ".gitignore");
2360
+ if (!existsSync6(path)) {
2182
2361
  writeFileSync(path, avatarBlock, "utf8");
2183
2362
  return;
2184
2363
  }
2185
- const existing = readFileSync3(path, "utf8");
2364
+ const existing = readFileSync4(path, "utf8");
2186
2365
  const startIdx = existing.indexOf(AVATAR_MARKER_START);
2187
2366
  const endIdx = existing.indexOf(AVATAR_MARKER_END);
2188
2367
  if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
@@ -2354,7 +2533,7 @@ async function safeBootstrapGitInFolder(folderPath, opts = {}) {
2354
2533
 
2355
2534
  // src/commands/init-conflict-detection-helpers.ts
2356
2535
  import { readdir } from "fs/promises";
2357
- import { join as join15 } from "path";
2536
+ import { join as join17 } from "path";
2358
2537
  async function isEmptyOrMissing(path) {
2359
2538
  if (!await pathExists(path)) return true;
2360
2539
  try {
@@ -2367,7 +2546,7 @@ async function isEmptyOrMissing(path) {
2367
2546
  }
2368
2547
  async function findAlternativeWorkspaceName(parent, desiredName, maxAttempts = 10) {
2369
2548
  for (let i = 2; i < maxAttempts; i++) {
2370
- const candidate = join15(parent, `${desiredName}-${i}`);
2549
+ const candidate = join17(parent, `${desiredName}-${i}`);
2371
2550
  if (await isEmptyOrMissing(candidate)) return candidate;
2372
2551
  }
2373
2552
  return null;
@@ -2678,7 +2857,7 @@ async function promptProjectStatus() {
2678
2857
  });
2679
2858
  }
2680
2859
  async function runInitFromExistingRemote(opts, ownerEmail) {
2681
- const initialRemoteUrl = opts.clientRepo ?? await input4({
2860
+ const initialRemoteUrl = opts.clientRepo ?? await input5({
2682
2861
  message: "URL git c\u1EE7a repo:",
2683
2862
  validate: (v) => v.length > 0 ? true : "URL b\u1EAFt bu\u1ED9c"
2684
2863
  });
@@ -2686,7 +2865,7 @@ async function runInitFromExistingRemote(opts, ownerEmail) {
2686
2865
  const remoteUrl = resolvedRemoteUrl ?? initialRemoteUrl;
2687
2866
  const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
2688
2867
  const inferredName = inferWorkspaceName(remoteUrl);
2689
- const workspaceName = opts.workspaceName ?? await input4({ message: "T\xEAn workspace:", default: inferredName });
2868
+ const workspaceName = opts.workspaceName ?? await input5({ message: "T\xEAn workspace:", default: inferredName });
2690
2869
  const workspaceParent = resolve(opts.workspaceParent ?? ".");
2691
2870
  const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);
2692
2871
  await scaffoldWorkspaceWithSrcSubmodule({
@@ -2709,7 +2888,7 @@ async function runInitFromExistingRemote(opts, ownerEmail) {
2709
2888
  }
2710
2889
  async function runInitFromExistingFolder(opts, ownerEmail) {
2711
2890
  const folderPath = resolve(
2712
- opts.folderPath ?? await input4({
2891
+ opts.folderPath ?? await input5({
2713
2892
  message: "\u0110\u01B0\u1EDDng d\u1EABn folder hi\u1EC7n c\xF3:",
2714
2893
  validate: (v) => v.length > 0 ? true : "Path b\u1EAFt bu\u1ED9c"
2715
2894
  })
@@ -2721,7 +2900,7 @@ async function runInitFromExistingFolder(opts, ownerEmail) {
2721
2900
  const remoteUrl = await getOrCreateOriginRemote(folderPath, opts);
2722
2901
  const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
2723
2902
  const inferredName = opts.workspaceName ?? `${basename(folderPath)}-avatar-workspace`;
2724
- const workspaceName = opts.workspaceName ?? await input4({ message: "T\xEAn workspace:", default: inferredName });
2903
+ const workspaceName = opts.workspaceName ?? await input5({ message: "T\xEAn workspace:", default: inferredName });
2725
2904
  const workspaceParent = resolve(opts.workspaceParent ?? ".");
2726
2905
  const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);
2727
2906
  await scaffoldWorkspaceWithSrcSubmodule({
@@ -2745,7 +2924,7 @@ async function runInitFromExistingFolder(opts, ownerEmail) {
2745
2924
  }
2746
2925
  async function runInitFromScratch(opts, ownerEmail) {
2747
2926
  await ensureGitHubReady();
2748
- const projectName = opts.workspaceName ?? await input4({
2927
+ const projectName = opts.workspaceName ?? await input5({
2749
2928
  message: "T\xEAn d\u1EF1 \xE1n:",
2750
2929
  validate: (v) => v.length > 0 ? true : "T\xEAn b\u1EAFt bu\u1ED9c"
2751
2930
  });
@@ -2759,7 +2938,7 @@ async function runInitFromScratch(opts, ownerEmail) {
2759
2938
  const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
2760
2939
  const workspaceParent = resolve(opts.workspaceParent ?? ".");
2761
2940
  const workspacePath = await resolveWorkspacePath(workspaceParent, projectName, opts.force);
2762
- const srcPath = join16(workspacePath, "src");
2941
+ const srcPath = join18(workspacePath, "src");
2763
2942
  await ensureDir(workspacePath);
2764
2943
  await ensureDir(srcPath);
2765
2944
  await safeBootstrapGitInFolder(srcPath, { autoYes: true });
@@ -2830,7 +3009,7 @@ async function getOrCreateOriginRemote(folderPath, opts) {
2830
3009
  { name: "public", value: "public" }
2831
3010
  ]
2832
3011
  });
2833
- const repoName = await input4({
3012
+ const repoName = await input5({
2834
3013
  message: "T\xEAn repo:",
2835
3014
  default: basename(folderPath)
2836
3015
  });
@@ -2895,10 +3074,10 @@ async function finalizeWorkspaceScaffold(args) {
2895
3074
  await writeRootClaudeMd(args.workspacePath, vars);
2896
3075
  await writeProjectSettings(args.workspacePath, vars);
2897
3076
  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");
3077
+ await ensureDir(join18(args.workspacePath, "notes"));
3078
+ await ensureDir(join18(args.workspacePath, "scripts"));
3079
+ await installGitHook(join18(args.workspacePath, ".git"), "post-merge");
3080
+ await installGitHook(join18(args.workspacePath, ".git", "modules", "src"), "pre-push");
2902
3081
  log.success("C\xE0i post-merge (workspace) + pre-push (src/)");
2903
3082
  await appendAuditEntry("init", `flow=${args.flow},workspace=${args.workspaceName}`);
2904
3083
  await maybeCommitWorkspace(args.workspacePath, args.skipCommit);
@@ -2973,7 +3152,7 @@ async function maybeCreateWorkspaceRemote(args) {
2973
3152
  });
2974
3153
  return;
2975
3154
  }
2976
- const newName = await input4({
3155
+ const newName = await input5({
2977
3156
  message: "T\xEAn workspace m\u1EDBi (s\u1EBD t\u1EA1o repo new):",
2978
3157
  validate: (v) => v.trim().length > 0 ? true : "T\xEAn kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng"
2979
3158
  });
@@ -2998,7 +3177,7 @@ async function maybeCreateWorkspaceRemote(args) {
2998
3177
  }
2999
3178
  }
3000
3179
  async function resolveWorkspacePath(parent, desiredName, force) {
3001
- const desired = join16(parent, desiredName);
3180
+ const desired = join18(parent, desiredName);
3002
3181
  if (await isEmptyOrMissing(desired)) return desired;
3003
3182
  log.warn(`Workspace path "${desired}" \u0111\xE3 c\xF3 n\u1ED9i dung.`);
3004
3183
  while (true) {
@@ -3025,17 +3204,17 @@ async function resolveWorkspacePath(parent, desiredName, force) {
3025
3204
  if (action === "use-alt" && alternative) {
3026
3205
  return alternative;
3027
3206
  }
3028
- const newName = await input4({
3207
+ const newName = await input5({
3029
3208
  message: "T\xEAn workspace m\u1EDBi:",
3030
3209
  validate: (v) => v.trim().length > 0 ? true : "T\xEAn kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng"
3031
3210
  });
3032
- const newPath = join16(parent, newName.trim());
3211
+ const newPath = join18(parent, newName.trim());
3033
3212
  if (await isEmptyOrMissing(newPath)) return newPath;
3034
3213
  log.warn(`"${newPath}" c\u0169ng \u0111\xE3 c\xF3 n\u1ED9i dung. Th\u1EED t\xEAn kh\xE1c.`);
3035
3214
  }
3036
3215
  }
3037
3216
  async function promptTeamOwner(currentUserEmail) {
3038
- return await input4({ message: "Team owner email:", default: currentUserEmail });
3217
+ return await input5({ message: "Team owner email:", default: currentUserEmail });
3039
3218
  }
3040
3219
  async function maybeCommitWorkspace(workspacePath, skipCommit) {
3041
3220
  if (skipCommit) {
@@ -3075,6 +3254,22 @@ function printInitSuccessBox(rootPath, flow, aiResult = null) {
3075
3254
  `);
3076
3255
  }
3077
3256
 
3257
+ // src/lib/not-implemented-stub.ts
3258
+ function notImplementedYet(commandName, milestone) {
3259
+ return () => {
3260
+ process.stdout.write(
3261
+ `${chalk.yellow("\u23F3")} ${chalk.bold(`avatar ${commandName}`)} \u2014 ch\u01B0a implement \u1EDF milestone hi\u1EC7n t\u1EA1i.
3262
+ `
3263
+ );
3264
+ if (milestone) {
3265
+ process.stdout.write(` D\u1EF1 ki\u1EBFn: ${chalk.cyan(milestone)}
3266
+ `);
3267
+ }
3268
+ process.stdout.write(" Spec \u0111\xE3 c\xF3 trong avatar-cli-implementation_4.html.\n");
3269
+ process.exit(0);
3270
+ };
3271
+ }
3272
+
3078
3273
  // src/commands/mcp-run.ts
3079
3274
  function registerMcpRunCommand(program2) {
3080
3275
  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 +3302,15 @@ function registerSecretsCommand(program2) {
3107
3302
 
3108
3303
  // src/commands/status.ts
3109
3304
  import { promises as fs8 } from "fs";
3110
- import { join as join18 } from "path";
3305
+ import { join as join20 } from "path";
3111
3306
  import boxen5 from "boxen";
3112
3307
 
3113
3308
  // src/lib/pack-backup-manager.ts
3114
3309
  import { promises as fs7 } from "fs";
3115
- import { join as join17 } from "path";
3310
+ import { join as join19 } from "path";
3116
3311
  var BACKUP_DIR_NAME = "_backup";
3117
3312
  async function listBackups(projectRoot) {
3118
- const dir = join17(projectRoot, ".claude", BACKUP_DIR_NAME);
3313
+ const dir = join19(projectRoot, ".claude", BACKUP_DIR_NAME);
3119
3314
  if (!await pathExists(dir)) return [];
3120
3315
  const entries = await fs7.readdir(dir, { withFileTypes: true });
3121
3316
  return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort().reverse();
@@ -3141,7 +3336,7 @@ function registerStatusCommand(program2) {
3141
3336
  }
3142
3337
  async function gatherStatus(cwd) {
3143
3338
  const projectName = cwd.split("/").filter(Boolean).pop() ?? "unknown";
3144
- const claudeRoot = join18(cwd, ".claude");
3339
+ const claudeRoot = join20(cwd, ".claude");
3145
3340
  const hasAvatar = await pathExists(claudeRoot);
3146
3341
  if (!hasAvatar) {
3147
3342
  return {
@@ -3154,8 +3349,8 @@ async function gatherStatus(cwd) {
3154
3349
  hasAvatar: false
3155
3350
  };
3156
3351
  }
3157
- const packVersion = await isGitRepo(join18(claudeRoot, "pack")) ? await readPinnedPackVersion(cwd).catch(() => null) : null;
3158
- const pendingDir = join18(claudeRoot, "_pending");
3352
+ const packVersion = await isGitRepo(join20(claudeRoot, "pack")) ? await readPinnedPackVersion(cwd).catch(() => null) : null;
3353
+ const pendingDir = join20(claudeRoot, "_pending");
3159
3354
  const pendingCount = await pathExists(pendingDir) ? (await fs8.readdir(pendingDir)).filter((n) => n.endsWith(".diff.md")).length : 0;
3160
3355
  const backupCount = (await listBackups(cwd)).length;
3161
3356
  const techStackSummary = await readTechStackFirstLine(claudeRoot);
@@ -3170,7 +3365,7 @@ async function gatherStatus(cwd) {
3170
3365
  };
3171
3366
  }
3172
3367
  async function readTechStackFirstLine(claudeRoot) {
3173
- const techStackPath = join18(claudeRoot, "project", "tech-stack.md");
3368
+ const techStackPath = join20(claudeRoot, "project", "tech-stack.md");
3174
3369
  if (!await pathExists(techStackPath)) return "(no tech-stack.md)";
3175
3370
  const content = await readText(techStackPath);
3176
3371
  const firstNonHeaderLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#") && !l.startsWith(">"));
@@ -3211,27 +3406,27 @@ import boxen6 from "boxen";
3211
3406
  // src/lib/create-uninstall-backup-snapshot.ts
3212
3407
  import { cp, mkdir, writeFile } from "fs/promises";
3213
3408
  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");
3409
+ import { basename as basename2, join as join21 } from "path";
3410
+ var UNINSTALL_BACKUPS_DIR = join21(homedir3(), ".avatar", "uninstall-backups");
3216
3411
  async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersion) {
3217
3412
  const projectName = basename2(projectRoot);
3218
3413
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3219
- const backupDir = join19(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp}`);
3414
+ const backupDir = join21(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp}`);
3220
3415
  await mkdir(backupDir, { recursive: true, mode: 448 });
3221
3416
  if (artifacts.claudeDir) {
3222
- await cp(artifacts.claudeDir, join19(backupDir, ".claude"), { recursive: true });
3417
+ await cp(artifacts.claudeDir, join21(backupDir, ".claude"), { recursive: true });
3223
3418
  }
3224
3419
  if (artifacts.claudeMd) {
3225
- await cp(artifacts.claudeMd, join19(backupDir, "CLAUDE.md"));
3420
+ await cp(artifacts.claudeMd, join21(backupDir, "CLAUDE.md"));
3226
3421
  }
3227
3422
  if (artifacts.postMergeHook || artifacts.prePushHook) {
3228
- const hooksBackupDir = join19(backupDir, "hooks");
3423
+ const hooksBackupDir = join21(backupDir, "hooks");
3229
3424
  await mkdir(hooksBackupDir, { recursive: true });
3230
3425
  if (artifacts.postMergeHook) {
3231
- await cp(artifacts.postMergeHook, join19(hooksBackupDir, "post-merge"));
3426
+ await cp(artifacts.postMergeHook, join21(hooksBackupDir, "post-merge"));
3232
3427
  }
3233
3428
  if (artifacts.prePushHook) {
3234
- await cp(artifacts.prePushHook, join19(hooksBackupDir, "pre-push"));
3429
+ await cp(artifacts.prePushHook, join21(hooksBackupDir, "pre-push"));
3235
3430
  }
3236
3431
  }
3237
3432
  const manifest = {
@@ -3246,27 +3441,27 @@ async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersi
3246
3441
  prePushHook: !!artifacts.prePushHook
3247
3442
  }
3248
3443
  };
3249
- await writeFile(join19(backupDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
3444
+ await writeFile(join21(backupDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
3250
3445
  return backupDir;
3251
3446
  }
3252
3447
 
3253
3448
  // src/lib/detect-avatar-project-artifacts.ts
3254
- import { existsSync as existsSync5 } from "fs";
3255
- import { join as join20 } from "path";
3449
+ import { existsSync as existsSync7 } from "fs";
3450
+ import { join as join22 } from "path";
3256
3451
  function existsOrNull(path) {
3257
- return existsSync5(path) ? path : null;
3452
+ return existsSync7(path) ? path : null;
3258
3453
  }
3259
3454
  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"));
3455
+ const claudeDir = existsOrNull(join22(projectRoot, ".claude"));
3456
+ const claudeMd = existsOrNull(join22(projectRoot, "CLAUDE.md"));
3457
+ const postMergeHook = existsOrNull(join22(projectRoot, ".git", "hooks", "post-merge"));
3263
3458
  const prePushHook = existsOrNull(
3264
- join20(projectRoot, ".git", "modules", "src", "hooks", "pre-push")
3459
+ join22(projectRoot, ".git", "modules", "src", "hooks", "pre-push")
3265
3460
  );
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"));
3461
+ const gitignorePath = existsOrNull(join22(projectRoot, ".gitignore"));
3462
+ const gitmodulesPath = existsOrNull(join22(projectRoot, ".gitmodules"));
3463
+ const notesDir = existsOrNull(join22(projectRoot, "notes"));
3464
+ const scriptsDir = existsOrNull(join22(projectRoot, "scripts"));
3270
3465
  const hasAnyArtifact = !!(claudeDir || claudeMd || postMergeHook || prePushHook);
3271
3466
  return {
3272
3467
  hasAnyArtifact,
@@ -3287,11 +3482,11 @@ async function executeUninstallDeletion(artifacts, flags) {
3287
3482
  if (artifacts.claudeDir) {
3288
3483
  if (flags.keepSubmodule) {
3289
3484
  const { readdir: readdir2 } = await import("fs/promises");
3290
- const { join: join21 } = await import("path");
3485
+ const { join: join23 } = await import("path");
3291
3486
  const entries = await readdir2(artifacts.claudeDir);
3292
3487
  for (const entry of entries) {
3293
3488
  if (entry === "pack") continue;
3294
- await rm(join21(artifacts.claudeDir, entry), { recursive: true, force: true });
3489
+ await rm(join23(artifacts.claudeDir, entry), { recursive: true, force: true });
3295
3490
  }
3296
3491
  } else {
3297
3492
  await rm(artifacts.claudeDir, { recursive: true, force: true });
@@ -3360,7 +3555,7 @@ async function removeSubmoduleEntry(gitmodulesPath, submodulePath) {
3360
3555
  }
3361
3556
 
3362
3557
  // src/commands/uninstall.ts
3363
- var CLI_VERSION = "1.2.11";
3558
+ var CLI_VERSION = "1.3.1";
3364
3559
  function registerUninstallCommand(program2) {
3365
3560
  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
3561
  try {
@@ -3442,7 +3637,7 @@ function printUninstallSuccessBox(backupPath) {
3442
3637
  }
3443
3638
 
3444
3639
  // src/index.ts
3445
- var CLI_VERSION2 = "1.2.11";
3640
+ var CLI_VERSION2 = "1.3.1";
3446
3641
  var program = new Command();
3447
3642
  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
3643
  "beforeAll",