@nalvietnam/avatar-cli 1.9.0 → 1.11.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
@@ -2311,7 +2311,7 @@ function registerGitnexusCommand(program2) {
2311
2311
  }
2312
2312
 
2313
2313
  // src/commands/init.ts
2314
- import { basename, join as join24, relative as relative3, resolve } from "path";
2314
+ import { basename, join as join25, relative as relative3, resolve as resolve2 } from "path";
2315
2315
  import { confirm as confirm5, input as input5, select as select9 } from "@inquirer/prompts";
2316
2316
  import boxen6 from "boxen";
2317
2317
 
@@ -2482,7 +2482,8 @@ var TeamPackAccessAbortedError = class extends Error {
2482
2482
  this.name = "TeamPackAccessAbortedError";
2483
2483
  }
2484
2484
  };
2485
- async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
2485
+ var DEFAULT_PACK_BRANCH = "main";
2486
+ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail, latest = false) {
2486
2487
  const url = resolveTeamPackRepoUrl();
2487
2488
  const repoSlug = parseRepoSlugFromGitUrl(url);
2488
2489
  if (repoSlug) {
@@ -2508,12 +2509,17 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
2508
2509
  }
2509
2510
  throw err;
2510
2511
  }
2511
- let target = tag ?? null;
2512
- if (!target) {
2513
- const submoduleDir = join16(projectRoot, TEAM_PACK_RELATIVE_PATH);
2514
- const allTags = await listTags(submoduleDir);
2515
- target = pickLatestStableSemVerTag(allTags);
2512
+ if (tag) {
2513
+ await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, tag, projectRoot);
2514
+ return { pinnedTag: tag };
2515
+ }
2516
+ if (latest) {
2517
+ await checkoutBranchHeadInSubmodule(TEAM_PACK_RELATIVE_PATH, DEFAULT_PACK_BRANCH, projectRoot);
2518
+ return { pinnedTag: `${DEFAULT_PACK_BRANCH} (HEAD)` };
2516
2519
  }
2520
+ const submoduleDir = join16(projectRoot, TEAM_PACK_RELATIVE_PATH);
2521
+ const allTags = await listTags(submoduleDir);
2522
+ const target = pickLatestStableSemVerTag(allTags);
2517
2523
  if (target) {
2518
2524
  await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, target, projectRoot);
2519
2525
  }
@@ -2573,10 +2579,10 @@ async function handleSshPermissionError() {
2573
2579
  ]
2574
2580
  });
2575
2581
  }
2576
- async function addTeamPackSubmoduleWithRetryOnNetworkFail(projectRoot, tag, ssoEmail) {
2582
+ async function addTeamPackSubmoduleWithRetryOnNetworkFail(projectRoot, tag, ssoEmail, latest = false) {
2577
2583
  while (true) {
2578
2584
  try {
2579
- const result = await addTeamPackSubmodule(projectRoot, tag, ssoEmail);
2585
+ const result = await addTeamPackSubmodule(projectRoot, tag, ssoEmail, latest);
2580
2586
  return { pinnedTag: result.pinnedTag, skipped: false };
2581
2587
  } catch (err) {
2582
2588
  if (err instanceof TeamPackAccessAbortedError) throw err;
@@ -3741,8 +3747,43 @@ async function findAlternativeWorkspaceName(parent, desiredName, maxAttempts = 1
3741
3747
  return null;
3742
3748
  }
3743
3749
 
3750
+ // src/lib/read-cli-version-from-package-json.ts
3751
+ import { readFileSync as readFileSync5 } from "fs";
3752
+ import { dirname as dirname6, join as join24, resolve } from "path";
3753
+ import { fileURLToPath as fileURLToPath3 } from "url";
3754
+ var cachedVersion = null;
3755
+ function readCliVersion() {
3756
+ if (cachedVersion !== null) return cachedVersion;
3757
+ const here = dirname6(fileURLToPath3(import.meta.url));
3758
+ for (let i = 0; i < 5; i++) {
3759
+ const candidate = resolve(here, ...Array(i).fill(".."), "package.json");
3760
+ try {
3761
+ const raw = readFileSync5(candidate, "utf8");
3762
+ const pkg = JSON.parse(raw);
3763
+ if (pkg.name === "@nalvietnam/avatar-cli" && typeof pkg.version === "string") {
3764
+ cachedVersion = pkg.version;
3765
+ return cachedVersion;
3766
+ }
3767
+ } catch {
3768
+ }
3769
+ if (i === 0) {
3770
+ const sibling = join24(here, "..", "package.json");
3771
+ try {
3772
+ const raw = readFileSync5(sibling, "utf8");
3773
+ const pkg = JSON.parse(raw);
3774
+ if (pkg.name === "@nalvietnam/avatar-cli" && typeof pkg.version === "string") {
3775
+ cachedVersion = pkg.version;
3776
+ return cachedVersion;
3777
+ }
3778
+ } catch {
3779
+ }
3780
+ }
3781
+ }
3782
+ cachedVersion = "unknown";
3783
+ return cachedVersion;
3784
+ }
3785
+
3744
3786
  // src/commands/init-scaffold-variable-builders.ts
3745
- var AVATAR_CLI_VERSION = "1.0.1";
3746
3787
  function inferWorkspaceName(repoUrl) {
3747
3788
  const trimmed = repoUrl.trim().replace(/\/+$/, "");
3748
3789
  const lastSegment = trimmed.split(/[/:]/).pop() ?? "";
@@ -3788,7 +3829,7 @@ function buildScaffoldVariables(args) {
3788
3829
  projectName: args.projectName,
3789
3830
  projectDescription: args.projectDescription,
3790
3831
  teamOwner: args.teamOwner,
3791
- avatarVersion: AVATAR_CLI_VERSION,
3832
+ avatarVersion: readCliVersion(),
3792
3833
  packVersion: args.packVersion,
3793
3834
  lastScan: (/* @__PURE__ */ new Date()).toISOString(),
3794
3835
  mode: args.mode,
@@ -3983,7 +4024,7 @@ async function runLogin(opts) {
3983
4024
  log.success(`L\u01B0u credential v\xE0o ${USER_CONFIG_PATH} (chmod 600)`);
3984
4025
  }
3985
4026
  function sleep(ms) {
3986
- return new Promise((resolve2) => setTimeout(resolve2, ms));
4027
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
3987
4028
  }
3988
4029
 
3989
4030
  // src/commands/init.ts
@@ -3999,7 +4040,7 @@ function parseBootstrapStrategyOpts(opts) {
3999
4040
  );
4000
4041
  }
4001
4042
  function registerInitCommand(program2) {
4002
- program2.command("init").description("Kh\u1EDFi t\u1EA1o Avatar \u2014 3 flow t\u1EF1 nh\u1EADn di\u1EC7n (repo / folder / new)").option("--project-status <val>", "existing-remote | existing-folder | new-project").option("--folder-path <path>", "\u0110\u01B0\u1EDDng d\u1EABn folder hi\u1EC7n c\xF3 (flow existing-folder)").option("--create-remote", "Force t\u1EA1o remote qua gh (flow existing-folder ho\u1EB7c new-project)").option("--repo-visibility <val>", "private (m\u1EB7c \u0111\u1ECBnh) | public").option("--repo-org <name>", "GitHub org/owner cho repo m\u1EDBi").option("--client-repo <url>", "URL git remote (flow existing-remote)").option("--workspace-name <name>", "T\xEAn workspace").option("--workspace-parent <path>", "Th\u01B0 m\u1EE5c cha t\u1EA1o workspace (m\u1EB7c \u0111\u1ECBnh . \u2014 CWD)").option("--pack-version <tag>", "Pin team-ai-pack v\xE0o tag c\u1EE5 th\u1EC3").option("--team-owner <email>", "Email team owner (b\u1ECF qua prompt)").option("--description <text>", "M\xF4 t\u1EA3 1 d\xF2ng c\u1EE7a d\u1EF1 \xE1n").option("--skip-scan", "B\u1ECF qua project-scanner sau scaffold").option("--skip-team-pack", "B\u1ECF qua submodule team-ai-pack (test mode)").option("--force", "B\u1ECF qua prompt khi workspace path \u0111\xE3 t\u1ED3n t\u1EA1i").option("--yes", "Auto-confirm t\u1EA5t c\u1EA3 prompt").option("--no-commit", "Skip commit workspace initial state (m\u1EB7c \u0111\u1ECBnh LU\xD4N commit)").option("--workspace-remote", "T\u1EA1o GitHub remote cho workspace root (default: prompt)").option("--ai-skip", "B\u1ECF qua phase AI setup (CI/test mode \u2014 ch\u1EA1y `avatar ai setup` sau)").option(
4043
+ program2.command("init").description("Kh\u1EDFi t\u1EA1o Avatar \u2014 3 flow t\u1EF1 nh\u1EADn di\u1EC7n (repo / folder / new)").option("--project-status <val>", "existing-remote | existing-folder | new-project").option("--folder-path <path>", "\u0110\u01B0\u1EDDng d\u1EABn folder hi\u1EC7n c\xF3 (flow existing-folder)").option("--create-remote", "Force t\u1EA1o remote qua gh (flow existing-folder ho\u1EB7c new-project)").option("--repo-visibility <val>", "private (m\u1EB7c \u0111\u1ECBnh) | public").option("--repo-org <name>", "GitHub org/owner cho repo m\u1EDBi").option("--client-repo <url>", "URL git remote (flow existing-remote)").option("--workspace-name <name>", "T\xEAn workspace").option("--workspace-parent <path>", "Th\u01B0 m\u1EE5c cha t\u1EA1o workspace (m\u1EB7c \u0111\u1ECBnh . \u2014 CWD)").option("--pack-version <tag>", "Pin team-ai-pack v\xE0o tag c\u1EE5 th\u1EC3").option("--latest", "Pull HEAD c\u1EE7a team-ai-pack main branch (b\u1ECF qua tag SemVer, bleeding-edge)").option("--team-owner <email>", "Email team owner (b\u1ECF qua prompt)").option("--description <text>", "M\xF4 t\u1EA3 1 d\xF2ng c\u1EE7a d\u1EF1 \xE1n").option("--skip-scan", "B\u1ECF qua project-scanner sau scaffold").option("--skip-team-pack", "B\u1ECF qua submodule team-ai-pack (test mode)").option("--force", "B\u1ECF qua prompt khi workspace path \u0111\xE3 t\u1ED3n t\u1EA1i").option("--yes", "Auto-confirm t\u1EA5t c\u1EA3 prompt").option("--no-commit", "Skip commit workspace initial state (m\u1EB7c \u0111\u1ECBnh LU\xD4N commit)").option("--workspace-remote", "T\u1EA1o GitHub remote cho workspace root (default: prompt)").option("--ai-skip", "B\u1ECF qua phase AI setup (CI/test mode \u2014 ch\u1EA1y `avatar ai setup` sau)").option(
4003
4044
  "--gitnexus-skip",
4004
4045
  "B\u1ECF qua phase GitNexus setup (M10 \u2014 ch\u1EA1y `avatar gitnexus install` sau)"
4005
4046
  ).option(
@@ -4091,7 +4132,7 @@ async function runInitFromExistingRemote(opts, ownerEmail) {
4091
4132
  const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
4092
4133
  const inferredName = inferWorkspaceName(remoteUrl);
4093
4134
  const workspaceName = opts.workspaceName ?? await input5({ message: "T\xEAn workspace:", default: inferredName });
4094
- const workspaceParent = resolve(opts.workspaceParent ?? ".");
4135
+ const workspaceParent = resolve2(opts.workspaceParent ?? ".");
4095
4136
  const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);
4096
4137
  await scaffoldWorkspaceWithSrcSubmodule({
4097
4138
  workspacePath,
@@ -4101,6 +4142,7 @@ async function runInitFromExistingRemote(opts, ownerEmail) {
4101
4142
  skipTeamPack: opts.skipTeamPack,
4102
4143
  description: opts.description ?? `Avatar workspace cho ${remoteUrl}`,
4103
4144
  packVersion: opts.packVersion,
4145
+ packLatest: opts.latest,
4104
4146
  autoYes: opts.yes,
4105
4147
  skipCommit: opts.commit === false,
4106
4148
  createWorkspaceRemote: opts.workspaceRemote,
@@ -4113,7 +4155,7 @@ async function runInitFromExistingRemote(opts, ownerEmail) {
4113
4155
  });
4114
4156
  }
4115
4157
  async function runInitFromExistingFolder(opts, ownerEmail) {
4116
- const folderPath = resolve(
4158
+ const folderPath = resolve2(
4117
4159
  opts.folderPath ?? await input5({
4118
4160
  message: "\u0110\u01B0\u1EDDng d\u1EABn folder hi\u1EC7n c\xF3:",
4119
4161
  validate: (v) => v.length > 0 ? true : "Path b\u1EAFt bu\u1ED9c"
@@ -4127,7 +4169,7 @@ async function runInitFromExistingFolder(opts, ownerEmail) {
4127
4169
  const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
4128
4170
  const inferredName = opts.workspaceName ?? `${basename(folderPath)}-avatar-workspace`;
4129
4171
  const workspaceName = opts.workspaceName ?? await input5({ message: "T\xEAn workspace:", default: inferredName });
4130
- const workspaceParent = resolve(opts.workspaceParent ?? ".");
4172
+ const workspaceParent = resolve2(opts.workspaceParent ?? ".");
4131
4173
  const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);
4132
4174
  await scaffoldWorkspaceWithSrcSubmodule({
4133
4175
  workspacePath,
@@ -4138,6 +4180,7 @@ async function runInitFromExistingFolder(opts, ownerEmail) {
4138
4180
  skipTeamPack: opts.skipTeamPack,
4139
4181
  description: opts.description ?? `Avatar workspace cho folder ${folderPath}`,
4140
4182
  packVersion: opts.packVersion,
4183
+ packLatest: opts.latest,
4141
4184
  autoYes: opts.yes,
4142
4185
  skipCommit: opts.commit === false,
4143
4186
  createWorkspaceRemote: opts.workspaceRemote,
@@ -4163,9 +4206,9 @@ async function runInitFromScratch(opts, ownerEmail) {
4163
4206
  ]
4164
4207
  });
4165
4208
  const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
4166
- const workspaceParent = resolve(opts.workspaceParent ?? ".");
4209
+ const workspaceParent = resolve2(opts.workspaceParent ?? ".");
4167
4210
  const workspacePath = await resolveWorkspacePath(workspaceParent, projectName, opts.force);
4168
- const srcPath = join24(workspacePath, "src");
4211
+ const srcPath = join25(workspacePath, "src");
4169
4212
  await ensureDir(workspacePath);
4170
4213
  await ensureDir(srcPath);
4171
4214
  await safeBootstrapGitInFolder(srcPath, { autoYes: true });
@@ -4187,7 +4230,9 @@ async function runInitFromScratch(opts, ownerEmail) {
4187
4230
  const result = await addTeamPackSubmoduleWithRetryOnNetworkFail(
4188
4231
  workspacePath,
4189
4232
  opts.packVersion,
4190
- ownerEmail
4233
+ ownerEmail,
4234
+ opts.latest === true && !opts.packVersion
4235
+ // v1.10.0: latest mode khi flag set + không có explicit tag
4191
4236
  );
4192
4237
  pinnedTag = result.pinnedTag ?? "HEAD";
4193
4238
  sp.succeed(`Pin team-ai-pack v\xE0o ${pinnedTag}`);
@@ -4263,7 +4308,9 @@ async function scaffoldWorkspaceWithSrcSubmodule(args) {
4263
4308
  const result = await addTeamPackSubmoduleWithRetryOnNetworkFail(
4264
4309
  args.workspacePath,
4265
4310
  args.packVersion,
4266
- args.ssoEmail
4311
+ args.ssoEmail,
4312
+ args.packLatest === true && !args.packVersion
4313
+ // v1.10.0
4267
4314
  );
4268
4315
  pinnedTag = result.pinnedTag ?? "HEAD";
4269
4316
  sp.succeed(`Pin team-ai-pack v\xE0o ${pinnedTag}`);
@@ -4303,10 +4350,10 @@ async function finalizeWorkspaceScaffold(args) {
4303
4350
  await writeRootClaudeMd(args.workspacePath, vars);
4304
4351
  await writeProjectSettings(args.workspacePath, vars);
4305
4352
  await appendGitignoreEntries(args.workspacePath);
4306
- await ensureDir(join24(args.workspacePath, "notes"));
4307
- await ensureDir(join24(args.workspacePath, "scripts"));
4308
- await installGitHook(join24(args.workspacePath, ".git"), "post-merge");
4309
- await installGitHook(join24(args.workspacePath, ".git", "modules", "src"), "pre-push");
4353
+ await ensureDir(join25(args.workspacePath, "notes"));
4354
+ await ensureDir(join25(args.workspacePath, "scripts"));
4355
+ await installGitHook(join25(args.workspacePath, ".git"), "post-merge");
4356
+ await installGitHook(join25(args.workspacePath, ".git", "modules", "src"), "pre-push");
4310
4357
  log.success("C\xE0i post-merge (workspace) + pre-push (src/)");
4311
4358
  await autoSyncPackOnInit(args.workspacePath);
4312
4359
  await appendAuditEntry("init", `flow=${args.flow},workspace=${args.workspaceName}`);
@@ -4344,12 +4391,12 @@ async function finalizeWorkspaceScaffold(args) {
4344
4391
  await printInitSuccessBox(args.workspacePath, args.flow, aiResult, gitnexusResult);
4345
4392
  }
4346
4393
  async function autoSyncPackOnInit(workspacePath) {
4347
- const packDir = join24(workspacePath, TEAM_PACK_RELATIVE_PATH);
4394
+ const packDir = join25(workspacePath, TEAM_PACK_RELATIVE_PATH);
4348
4395
  if (!await pathExists(packDir)) {
4349
4396
  log.dim("Pack submodule kh\xF4ng t\u1ED3n t\u1EA1i (skip auto-sync). C\xF3 th\u1EC3 ch\u1EA1y `avatar sync` sau.");
4350
4397
  return;
4351
4398
  }
4352
- const claudeDir = join24(workspacePath, ".claude");
4399
+ const claudeDir = join25(workspacePath, ".claude");
4353
4400
  log.info("Auto-sync pack content v\xE0o .claude/ (symlinks + settings merge)...");
4354
4401
  try {
4355
4402
  const results = await syncAllMountDirs(packDir, claudeDir, false);
@@ -4463,7 +4510,7 @@ async function maybeCreateWorkspaceRemote(args) {
4463
4510
  }
4464
4511
  }
4465
4512
  async function resolveWorkspacePath(parent, desiredName, force) {
4466
- const desired = join24(parent, desiredName);
4513
+ const desired = join25(parent, desiredName);
4467
4514
  if (await isEmptyOrMissing(desired)) return desired;
4468
4515
  log.warn(`Workspace path "${desired}" \u0111\xE3 c\xF3 n\u1ED9i dung.`);
4469
4516
  while (true) {
@@ -4494,7 +4541,7 @@ async function resolveWorkspacePath(parent, desiredName, force) {
4494
4541
  message: "T\xEAn workspace m\u1EDBi:",
4495
4542
  validate: (v) => v.trim().length > 0 ? true : "T\xEAn kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng"
4496
4543
  });
4497
- const newPath = join24(parent, newName.trim());
4544
+ const newPath = join25(parent, newName.trim());
4498
4545
  if (await isEmptyOrMissing(newPath)) return newPath;
4499
4546
  log.warn(`"${newPath}" c\u0169ng \u0111\xE3 c\xF3 n\u1ED9i dung. Th\u1EED t\xEAn kh\xE1c.`);
4500
4547
  }
@@ -4551,7 +4598,7 @@ async function printInitSuccessBox(rootPath, flow, aiResult = null, gitnexusResu
4551
4598
  ];
4552
4599
  process.stdout.write(`${boxen6(lines.join("\n"), { padding: 1, borderStyle: "round" })}
4553
4600
  `);
4554
- const packDir = join24(rootPath, TEAM_PACK_RELATIVE_PATH);
4601
+ const packDir = join25(rootPath, TEAM_PACK_RELATIVE_PATH);
4555
4602
  if (await pathExists(packDir)) {
4556
4603
  process.stdout.write(`
4557
4604
  ${formatPackCommandsCheatsheetBox()}
@@ -4580,6 +4627,91 @@ function registerMcpRunCommand(program2) {
4580
4627
  program2.command("mcp-run <tool-id>", { hidden: true }).description("[internal] Spawn MCP v\u1EDBi secrets injected (M09)").action(notImplementedYet("mcp-run", "Milestone 09"));
4581
4628
  }
4582
4629
 
4630
+ // src/commands/pack-status-and-version-check.ts
4631
+ import { join as join26 } from "path";
4632
+ import boxen7 from "boxen";
4633
+ var PACK_RELATIVE_PATH = ".claude/pack";
4634
+ function registerPackCommand(program2) {
4635
+ const pack = program2.command("pack").description("Qu\u1EA3n l\xFD team-ai-pack submodule (status, ...)");
4636
+ pack.command("status").description("Hi\u1EC3n th\u1ECB tag pack \u0111ang pin + tag m\u1EDBi nh\u1EA5t t\u1EEB remote").option("--json", "Output JSON cho script").option("--no-fetch", "B\u1ECF qua fetch remote (ch\u1EC9 \u0111\u1ECDc local tags)").action(async (opts) => {
4637
+ try {
4638
+ const snap = await gatherPackStatus(process.cwd(), opts.fetch !== false);
4639
+ if (opts.json) {
4640
+ process.stdout.write(`${JSON.stringify(snap, null, 2)}
4641
+ `);
4642
+ } else {
4643
+ renderPackStatus(snap);
4644
+ }
4645
+ } catch (err) {
4646
+ log.error(err instanceof Error ? err.message : String(err));
4647
+ process.exit(1);
4648
+ }
4649
+ });
4650
+ }
4651
+ async function gatherPackStatus(cwd, doFetch) {
4652
+ const packDir = join26(cwd, PACK_RELATIVE_PATH);
4653
+ if (!await pathExists(packDir) || !await isGitRepo(packDir)) {
4654
+ return {
4655
+ installed: false,
4656
+ currentTag: null,
4657
+ latestTag: null,
4658
+ upToDate: null,
4659
+ fetched: false
4660
+ };
4661
+ }
4662
+ const currentTag = await readPinnedPackVersion(cwd).catch(() => null);
4663
+ let fetched = false;
4664
+ if (doFetch) {
4665
+ try {
4666
+ await git(packDir).fetch(["--tags", "origin"]);
4667
+ fetched = true;
4668
+ } catch {
4669
+ fetched = false;
4670
+ }
4671
+ }
4672
+ const allTags = await listTags(packDir).catch(() => []);
4673
+ const latestTag = pickLatestStableSemVerTag(allTags);
4674
+ const upToDate = currentTag && latestTag ? currentTag === latestTag : null;
4675
+ return {
4676
+ installed: true,
4677
+ currentTag,
4678
+ latestTag,
4679
+ upToDate,
4680
+ fetched
4681
+ };
4682
+ }
4683
+ function renderPackStatus(s) {
4684
+ if (!s.installed) {
4685
+ const lines2 = [
4686
+ `${chalk.bold("team-ai-pack")} \xB7 ${chalk.yellow("ch\u01B0a c\xE0i")}`,
4687
+ "\u2500".repeat(48),
4688
+ chalk.dim("Ch\u1EA1y `avatar init` ho\u1EB7c `avatar sync` \u0111\u1EC3 c\xE0i pack.")
4689
+ ];
4690
+ process.stdout.write(`${boxen7(lines2.join("\n"), { padding: 1, borderStyle: "round" })}
4691
+ `);
4692
+ return;
4693
+ }
4694
+ const current = s.currentTag ?? chalk.yellow("(unknown)");
4695
+ const latest = s.latestTag ?? chalk.dim(s.fetched ? "(no tags)" : "(kh\xF4ng fetch)");
4696
+ let verdict;
4697
+ if (s.upToDate === true) {
4698
+ verdict = chalk.green("\u2713 \u0110ang d\xF9ng tag m\u1EDBi nh\u1EA5t");
4699
+ } else if (s.upToDate === false) {
4700
+ verdict = `${chalk.yellow("\u26A0 C\xF3 version m\u1EDBi")} ${chalk.dim("\u2192 avatar sync")}`;
4701
+ } else {
4702
+ verdict = chalk.dim("(kh\xF4ng so s\xE1nh \u0111\u01B0\u1EE3c)");
4703
+ }
4704
+ const lines = [
4705
+ `${chalk.bold("team-ai-pack status")}`,
4706
+ "\u2500".repeat(48),
4707
+ `${chalk.dim("Tag hi\u1EC7n t\u1EA1i:")} ${current}`,
4708
+ `${chalk.dim("Tag m\u1EDBi nh\u1EA5t:")} ${latest}`,
4709
+ `${chalk.dim("Tr\u1EA1ng th\xE1i:")} ${verdict}`
4710
+ ];
4711
+ process.stdout.write(`${boxen7(lines.join("\n"), { padding: 1, borderStyle: "round" })}
4712
+ `);
4713
+ }
4714
+
4583
4715
  // src/commands/restore.ts
4584
4716
  function registerRestoreCommand(program2) {
4585
4717
  program2.command("restore").description("Kh\xF4i ph\u1EE5c .claude/pack/ t\u1EEB backup (M08)").option("--backup <name>", "T\xEAn backup folder trong .claude/_backup/").option("--list", "Li\u1EC7t k\xEA c\xE1c backup hi\u1EC7n c\xF3").action(notImplementedYet("restore", "Milestone 08"));
@@ -4607,22 +4739,21 @@ function registerSecretsCommand(program2) {
4607
4739
 
4608
4740
  // src/commands/status.ts
4609
4741
  import { promises as fs13 } from "fs";
4610
- import { join as join26 } from "path";
4611
- import boxen7 from "boxen";
4742
+ import { join as join28 } from "path";
4743
+ import boxen8 from "boxen";
4612
4744
 
4613
4745
  // src/lib/pack-backup-manager.ts
4614
4746
  import { promises as fs12 } from "fs";
4615
- import { join as join25 } from "path";
4747
+ import { join as join27 } from "path";
4616
4748
  var BACKUP_DIR_NAME = "_backup";
4617
4749
  async function listBackups(projectRoot) {
4618
- const dir = join25(projectRoot, ".claude", BACKUP_DIR_NAME);
4750
+ const dir = join27(projectRoot, ".claude", BACKUP_DIR_NAME);
4619
4751
  if (!await pathExists(dir)) return [];
4620
4752
  const entries = await fs12.readdir(dir, { withFileTypes: true });
4621
4753
  return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort().reverse();
4622
4754
  }
4623
4755
 
4624
4756
  // src/commands/status.ts
4625
- var AVATAR_CLI_VERSION2 = "1.0.1";
4626
4757
  function registerStatusCommand(program2) {
4627
4758
  program2.command("status").description("Snapshot t\u1EE9c th\xEC: project, pack version, pending, backup").option("--json", "Output JSON cho script").action(async (opts) => {
4628
4759
  try {
@@ -4641,12 +4772,12 @@ function registerStatusCommand(program2) {
4641
4772
  }
4642
4773
  async function gatherStatus(cwd) {
4643
4774
  const projectName = cwd.split("/").filter(Boolean).pop() ?? "unknown";
4644
- const claudeRoot = join26(cwd, ".claude");
4775
+ const claudeRoot = join28(cwd, ".claude");
4645
4776
  const hasAvatar = await pathExists(claudeRoot);
4646
4777
  if (!hasAvatar) {
4647
4778
  return {
4648
4779
  projectName,
4649
- cliVersion: AVATAR_CLI_VERSION2,
4780
+ cliVersion: readCliVersion(),
4650
4781
  packVersion: null,
4651
4782
  pendingCount: 0,
4652
4783
  backupCount: 0,
@@ -4654,14 +4785,14 @@ async function gatherStatus(cwd) {
4654
4785
  hasAvatar: false
4655
4786
  };
4656
4787
  }
4657
- const packVersion = await isGitRepo(join26(claudeRoot, "pack")) ? await readPinnedPackVersion(cwd).catch(() => null) : null;
4658
- const pendingDir = join26(claudeRoot, "_pending");
4788
+ const packVersion = await isGitRepo(join28(claudeRoot, "pack")) ? await readPinnedPackVersion(cwd).catch(() => null) : null;
4789
+ const pendingDir = join28(claudeRoot, "_pending");
4659
4790
  const pendingCount = await pathExists(pendingDir) ? (await fs13.readdir(pendingDir)).filter((n) => n.endsWith(".diff.md")).length : 0;
4660
4791
  const backupCount = (await listBackups(cwd)).length;
4661
4792
  const techStackSummary = await readTechStackFirstLine(claudeRoot);
4662
4793
  return {
4663
4794
  projectName,
4664
- cliVersion: AVATAR_CLI_VERSION2,
4795
+ cliVersion: AVATAR_CLI_VERSION,
4665
4796
  packVersion,
4666
4797
  pendingCount,
4667
4798
  backupCount,
@@ -4670,7 +4801,7 @@ async function gatherStatus(cwd) {
4670
4801
  };
4671
4802
  }
4672
4803
  async function readTechStackFirstLine(claudeRoot) {
4673
- const techStackPath = join26(claudeRoot, "project", "tech-stack.md");
4804
+ const techStackPath = join28(claudeRoot, "project", "tech-stack.md");
4674
4805
  if (!await pathExists(techStackPath)) return "(no tech-stack.md)";
4675
4806
  const content = await readText(techStackPath);
4676
4807
  const firstNonHeaderLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#") && !l.startsWith(">"));
@@ -4686,18 +4817,18 @@ function renderStatusBox(s) {
4686
4817
  `${chalk.dim("Backups:")} ${s.backupCount}`,
4687
4818
  `${chalk.dim("Tech stack:")} ${s.techStackSummary}`
4688
4819
  ];
4689
- process.stdout.write(`${boxen7(lines.join("\n"), { padding: 1, borderStyle: "round" })}
4820
+ process.stdout.write(`${boxen8(lines.join("\n"), { padding: 1, borderStyle: "round" })}
4690
4821
  `);
4691
4822
  }
4692
4823
 
4693
4824
  // src/commands/sync.ts
4694
- import { join as join28 } from "path";
4825
+ import { join as join30 } from "path";
4695
4826
 
4696
4827
  // src/lib/preview-team-pack-sync-changes-for-dry-run.ts
4697
- import { join as join27 } from "path";
4828
+ import { join as join29 } from "path";
4698
4829
  async function inspectMountDir(packDir, claudeDir, dir) {
4699
- const source = join27(packDir, dir);
4700
- const dest = join27(claudeDir, dir);
4830
+ const source = join29(packDir, dir);
4831
+ const dest = join29(claudeDir, dir);
4701
4832
  if (!await pathExists(source)) return "source-missing";
4702
4833
  if (!await pathExists(dest)) return "needs-creation";
4703
4834
  const { promises: fs14 } = await import("fs");
@@ -4736,11 +4867,11 @@ async function buildSyncPreview(packDir, claudeDir, targetVersion) {
4736
4867
  }
4737
4868
 
4738
4869
  // src/commands/sync.ts
4739
- var DEFAULT_PACK_BRANCH = "main";
4870
+ var DEFAULT_PACK_BRANCH2 = "main";
4740
4871
  async function syncAction(opts) {
4741
4872
  const projectRoot = process.cwd();
4742
- const claudeDir = join28(projectRoot, ".claude");
4743
- const packDir = join28(projectRoot, TEAM_PACK_RELATIVE_PATH);
4873
+ const claudeDir = join30(projectRoot, ".claude");
4874
+ const packDir = join30(projectRoot, TEAM_PACK_RELATIVE_PATH);
4744
4875
  if (!await pathExists(packDir)) {
4745
4876
  log.error(
4746
4877
  `team-ai-pack submodule ch\u01B0a \u0111\u01B0\u1EE3c kh\u1EDFi t\u1EA1o \u1EDF ${TEAM_PACK_RELATIVE_PATH}/.
@@ -4760,14 +4891,14 @@ async function syncAction(opts) {
4760
4891
  const allTags = await listTags(packDir);
4761
4892
  let targetVersion;
4762
4893
  if (useLatestMode) {
4763
- targetVersion = `${DEFAULT_PACK_BRANCH} (HEAD)`;
4894
+ targetVersion = `${DEFAULT_PACK_BRANCH2} (HEAD)`;
4764
4895
  } else {
4765
4896
  const picked = opts.version ?? pickLatestStableSemVerTag(allTags);
4766
4897
  if (!picked) {
4767
4898
  log.error(
4768
4899
  `Kh\xF4ng t\xECm th\u1EA5y stable SemVer tag (vMAJOR.MINOR.PATCH) trong team-ai-pack submodule.
4769
4900
  Tags hi\u1EC7n c\xF3: ${allTags.length > 0 ? allTags.join(", ") : "(none)"}
4770
- Pass --version <tag> r\xF5 r\xE0ng, ho\u1EB7c d\xF9ng --latest \u0111\u1EC3 pull HEAD branch ${DEFAULT_PACK_BRANCH}.`
4901
+ Pass --version <tag> r\xF5 r\xE0ng, ho\u1EB7c d\xF9ng --latest \u0111\u1EC3 pull HEAD branch ${DEFAULT_PACK_BRANCH2}.`
4771
4902
  );
4772
4903
  process.exit(1);
4773
4904
  return;
@@ -4797,8 +4928,8 @@ async function syncAction(opts) {
4797
4928
  return;
4798
4929
  }
4799
4930
  if (useLatestMode) {
4800
- log.info(`Pulling HEAD c\u1EE7a branch ${DEFAULT_PACK_BRANCH} (bleeding-edge mode)...`);
4801
- await checkoutBranchHeadInSubmodule(TEAM_PACK_RELATIVE_PATH, DEFAULT_PACK_BRANCH, projectRoot);
4931
+ log.info(`Pulling HEAD c\u1EE7a branch ${DEFAULT_PACK_BRANCH2} (bleeding-edge mode)...`);
4932
+ await checkoutBranchHeadInSubmodule(TEAM_PACK_RELATIVE_PATH, DEFAULT_PACK_BRANCH2, projectRoot);
4802
4933
  const sha = await currentCommitSha(packDir);
4803
4934
  log.dim(` HEAD = ${sha.slice(0, 7)}`);
4804
4935
  log.warn(
@@ -4878,32 +5009,32 @@ function registerToolsCommand(program2) {
4878
5009
  // src/commands/uninstall.ts
4879
5010
  import { relative as relative4 } from "path";
4880
5011
  import { confirm as confirm6 } from "@inquirer/prompts";
4881
- import boxen8 from "boxen";
5012
+ import boxen9 from "boxen";
4882
5013
 
4883
5014
  // src/lib/create-uninstall-backup-snapshot.ts
4884
5015
  import { cp, mkdir, writeFile } from "fs/promises";
4885
5016
  import { homedir as homedir4 } from "os";
4886
- import { basename as basename2, join as join29 } from "path";
4887
- var UNINSTALL_BACKUPS_DIR = join29(homedir4(), ".avatar", "uninstall-backups");
5017
+ import { basename as basename2, join as join31 } from "path";
5018
+ var UNINSTALL_BACKUPS_DIR = join31(homedir4(), ".avatar", "uninstall-backups");
4888
5019
  async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersion) {
4889
5020
  const projectName = basename2(projectRoot);
4890
5021
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4891
- const backupDir = join29(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp2}`);
5022
+ const backupDir = join31(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp2}`);
4892
5023
  await mkdir(backupDir, { recursive: true, mode: 448 });
4893
5024
  if (artifacts.claudeDir) {
4894
- await cp(artifacts.claudeDir, join29(backupDir, ".claude"), { recursive: true });
5025
+ await cp(artifacts.claudeDir, join31(backupDir, ".claude"), { recursive: true });
4895
5026
  }
4896
5027
  if (artifacts.claudeMd) {
4897
- await cp(artifacts.claudeMd, join29(backupDir, "CLAUDE.md"));
5028
+ await cp(artifacts.claudeMd, join31(backupDir, "CLAUDE.md"));
4898
5029
  }
4899
5030
  if (artifacts.postMergeHook || artifacts.prePushHook) {
4900
- const hooksBackupDir = join29(backupDir, "hooks");
5031
+ const hooksBackupDir = join31(backupDir, "hooks");
4901
5032
  await mkdir(hooksBackupDir, { recursive: true });
4902
5033
  if (artifacts.postMergeHook) {
4903
- await cp(artifacts.postMergeHook, join29(hooksBackupDir, "post-merge"));
5034
+ await cp(artifacts.postMergeHook, join31(hooksBackupDir, "post-merge"));
4904
5035
  }
4905
5036
  if (artifacts.prePushHook) {
4906
- await cp(artifacts.prePushHook, join29(hooksBackupDir, "pre-push"));
5037
+ await cp(artifacts.prePushHook, join31(hooksBackupDir, "pre-push"));
4907
5038
  }
4908
5039
  }
4909
5040
  const manifest = {
@@ -4918,27 +5049,27 @@ async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersi
4918
5049
  prePushHook: !!artifacts.prePushHook
4919
5050
  }
4920
5051
  };
4921
- await writeFile(join29(backupDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
5052
+ await writeFile(join31(backupDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
4922
5053
  return backupDir;
4923
5054
  }
4924
5055
 
4925
5056
  // src/lib/detect-avatar-project-artifacts.ts
4926
5057
  import { existsSync as existsSync9 } from "fs";
4927
- import { join as join30 } from "path";
5058
+ import { join as join32 } from "path";
4928
5059
  function existsOrNull(path) {
4929
5060
  return existsSync9(path) ? path : null;
4930
5061
  }
4931
5062
  function detectAvatarProjectArtifacts(projectRoot) {
4932
- const claudeDir = existsOrNull(join30(projectRoot, ".claude"));
4933
- const claudeMd = existsOrNull(join30(projectRoot, "CLAUDE.md"));
4934
- const postMergeHook = existsOrNull(join30(projectRoot, ".git", "hooks", "post-merge"));
5063
+ const claudeDir = existsOrNull(join32(projectRoot, ".claude"));
5064
+ const claudeMd = existsOrNull(join32(projectRoot, "CLAUDE.md"));
5065
+ const postMergeHook = existsOrNull(join32(projectRoot, ".git", "hooks", "post-merge"));
4935
5066
  const prePushHook = existsOrNull(
4936
- join30(projectRoot, ".git", "modules", "src", "hooks", "pre-push")
5067
+ join32(projectRoot, ".git", "modules", "src", "hooks", "pre-push")
4937
5068
  );
4938
- const gitignorePath = existsOrNull(join30(projectRoot, ".gitignore"));
4939
- const gitmodulesPath = existsOrNull(join30(projectRoot, ".gitmodules"));
4940
- const notesDir = existsOrNull(join30(projectRoot, "notes"));
4941
- const scriptsDir = existsOrNull(join30(projectRoot, "scripts"));
5069
+ const gitignorePath = existsOrNull(join32(projectRoot, ".gitignore"));
5070
+ const gitmodulesPath = existsOrNull(join32(projectRoot, ".gitmodules"));
5071
+ const notesDir = existsOrNull(join32(projectRoot, "notes"));
5072
+ const scriptsDir = existsOrNull(join32(projectRoot, "scripts"));
4942
5073
  const hasAnyArtifact = !!(claudeDir || claudeMd || postMergeHook || prePushHook);
4943
5074
  return {
4944
5075
  hasAnyArtifact,
@@ -4959,11 +5090,11 @@ async function executeUninstallDeletion(artifacts, flags) {
4959
5090
  if (artifacts.claudeDir) {
4960
5091
  if (flags.keepSubmodule) {
4961
5092
  const { readdir: readdir2 } = await import("fs/promises");
4962
- const { join: join31 } = await import("path");
5093
+ const { join: join33 } = await import("path");
4963
5094
  const entries = await readdir2(artifacts.claudeDir);
4964
5095
  for (const entry of entries) {
4965
5096
  if (entry === "pack") continue;
4966
- await rm(join31(artifacts.claudeDir, entry), { recursive: true, force: true });
5097
+ await rm(join33(artifacts.claudeDir, entry), { recursive: true, force: true });
4967
5098
  }
4968
5099
  } else {
4969
5100
  await rm(artifacts.claudeDir, { recursive: true, force: true });
@@ -5032,7 +5163,7 @@ async function removeSubmoduleEntry(gitmodulesPath, submodulePath) {
5032
5163
  }
5033
5164
 
5034
5165
  // src/commands/uninstall.ts
5035
- var CLI_VERSION = "1.9.0";
5166
+ var CLI_VERSION = "1.10.0";
5036
5167
  function registerUninstallCommand(program2) {
5037
5168
  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) => {
5038
5169
  try {
@@ -5109,12 +5240,12 @@ function printUninstallSuccessBox(backupPath) {
5109
5240
  lines.push(` ${chalk.dim("Backup:")} ${backupPath}`);
5110
5241
  lines.push(` ${chalk.dim("Restore:")} ${chalk.cyan(`cp -r "${backupPath}"/* .`)}`);
5111
5242
  }
5112
- process.stdout.write(`${boxen8(lines.join("\n"), { padding: 1, borderStyle: "round" })}
5243
+ process.stdout.write(`${boxen9(lines.join("\n"), { padding: 1, borderStyle: "round" })}
5113
5244
  `);
5114
5245
  }
5115
5246
 
5116
5247
  // src/index.ts
5117
- var CLI_VERSION2 = "1.9.0";
5248
+ var CLI_VERSION2 = readCliVersion();
5118
5249
  var program = new Command();
5119
5250
  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(
5120
5251
  "beforeAll",
@@ -5142,6 +5273,7 @@ registerSecretsCommand(program);
5142
5273
  registerMcpRunCommand(program);
5143
5274
  registerAiCommand(program);
5144
5275
  registerGitnexusCommand(program);
5276
+ registerPackCommand(program);
5145
5277
  registerUninstallCommand(program);
5146
5278
  program.parseAsync(process.argv).catch((err) => {
5147
5279
  const msg = err instanceof Error ? err.message : String(err);