@nalvietnam/avatar-cli 1.4.2 → 1.5.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
@@ -103,11 +103,22 @@ var userStateSchema = z.object({
103
103
  tool_inputs: z.record(z.string(), z.unknown()).default({})
104
104
  });
105
105
  var projectSettingsSchema = z.object({
106
- allowedTools: z.array(z.string()),
107
- hooks: z.object({
108
- PostToolUse: z.array(z.unknown()).optional()
106
+ $schema: z.string().optional(),
107
+ includeCoAuthoredBy: z.boolean().optional(),
108
+ env: z.record(z.string(), z.string()).default({}),
109
+ permissions: z.object({
110
+ allow: z.array(z.string()).default([]),
111
+ deny: z.array(z.string()).default([])
109
112
  }).partial().optional(),
110
- env: z.record(z.string(), z.string()).default({})
113
+ // Hooks shape per Claude Code spec: each event key maps to array of matcher entries.
114
+ // We accept unknown body since pack/template control concrete schema; Zod just guards
115
+ // top-level structure to avoid corrupting user file on merge.
116
+ hooks: z.record(z.string(), z.array(z.unknown())).optional(),
117
+ statusLine: z.object({
118
+ type: z.string(),
119
+ command: z.string(),
120
+ padding: z.number().optional()
121
+ }).optional()
111
122
  });
112
123
  var initModeSchema = z.enum(["internal", "client", "library"]);
113
124
 
@@ -134,8 +145,8 @@ async function writeUserConfig(config) {
134
145
  }
135
146
  async function clearUserConfig() {
136
147
  if (await pathExists(USER_CONFIG_PATH)) {
137
- const { promises: fs11 } = await import("fs");
138
- await fs11.unlink(USER_CONFIG_PATH);
148
+ const { promises: fs14 } = await import("fs");
149
+ await fs14.unlink(USER_CONFIG_PATH);
139
150
  }
140
151
  }
141
152
  function isTokenExpired(config) {
@@ -4014,8 +4025,350 @@ function renderStatusBox(s) {
4014
4025
  }
4015
4026
 
4016
4027
  // src/commands/sync.ts
4028
+ import { join as join28 } from "path";
4029
+
4030
+ // src/lib/merge-pack-settings-into-project-settings.ts
4031
+ import { promises as fs11 } from "fs";
4032
+ import { join as join25 } from "path";
4033
+ function backupFilename(originalPath) {
4034
+ const d = /* @__PURE__ */ new Date();
4035
+ const stamp = `${d.getFullYear().toString().slice(-2) + String(d.getMonth() + 1).padStart(2, "0") + String(d.getDate()).padStart(2, "0")}-${String(d.getHours()).padStart(2, "0")}${String(d.getMinutes()).padStart(2, "0")}`;
4036
+ return `${originalPath}.backup-${stamp}`;
4037
+ }
4038
+ function unionDedupe(a, b) {
4039
+ const seen = /* @__PURE__ */ new Set();
4040
+ const out = [];
4041
+ for (const item of [...a, ...b]) {
4042
+ const key = typeof item === "string" ? item : JSON.stringify(item);
4043
+ if (!seen.has(key)) {
4044
+ seen.add(key);
4045
+ out.push(item);
4046
+ }
4047
+ }
4048
+ return out;
4049
+ }
4050
+ function mergeHooksPerEvent(packHooks, userHooks) {
4051
+ const touched = [];
4052
+ const merged = { ...userHooks };
4053
+ for (const [event, packEntries] of Object.entries(packHooks)) {
4054
+ const userEntries = userHooks[event] || [];
4055
+ const union = unionDedupe(userEntries, packEntries);
4056
+ if (union.length !== userEntries.length) {
4057
+ touched.push(event);
4058
+ }
4059
+ merged[event] = union;
4060
+ }
4061
+ return { merged, touchedEvents: touched };
4062
+ }
4063
+ async function mergePackSettingsIntoProjectSettings(workspacePath) {
4064
+ const packTemplatePath = join25(workspacePath, ".claude", "pack", "templates", "settings.json.tpl");
4065
+ const projectSettingsPath = join25(workspacePath, ".claude", "settings.json");
4066
+ if (!await pathExists(packTemplatePath)) {
4067
+ return { action: "no-pack-template", changes: [] };
4068
+ }
4069
+ let packTemplate;
4070
+ try {
4071
+ const raw = await readText(packTemplatePath);
4072
+ packTemplate = JSON.parse(raw);
4073
+ } catch (err) {
4074
+ throw new Error(
4075
+ `Pack settings template kh\xF4ng parse \u0111\u01B0\u1EE3c JSON: ${err.message}. Path: ${packTemplatePath}`
4076
+ );
4077
+ }
4078
+ let userSettings = {};
4079
+ let projectHasSettings = false;
4080
+ if (await pathExists(projectSettingsPath)) {
4081
+ projectHasSettings = true;
4082
+ try {
4083
+ userSettings = await readJson(projectSettingsPath);
4084
+ } catch (err) {
4085
+ throw new Error(
4086
+ `Project settings.json kh\xF4ng parse \u0111\u01B0\u1EE3c: ${err.message}. Manual fix tr\u01B0\u1EDBc khi sync.`
4087
+ );
4088
+ }
4089
+ }
4090
+ const changes = [];
4091
+ const merged = { ...userSettings };
4092
+ if (packTemplate.statusLine && !userSettings.statusLine) {
4093
+ merged.statusLine = packTemplate.statusLine;
4094
+ changes.push("statusLine added");
4095
+ }
4096
+ if (typeof packTemplate.includeCoAuthoredBy === "boolean" && typeof userSettings.includeCoAuthoredBy !== "boolean") {
4097
+ merged.includeCoAuthoredBy = packTemplate.includeCoAuthoredBy;
4098
+ changes.push("includeCoAuthoredBy added");
4099
+ }
4100
+ if (packTemplate.model && !userSettings.model) {
4101
+ merged.model = packTemplate.model;
4102
+ changes.push("model added");
4103
+ }
4104
+ if (packTemplate.env) {
4105
+ const mergedEnv = { ...userSettings.env || {} };
4106
+ let envChanged = false;
4107
+ for (const [k, v] of Object.entries(packTemplate.env)) {
4108
+ if (!(k in mergedEnv)) {
4109
+ mergedEnv[k] = v;
4110
+ envChanged = true;
4111
+ }
4112
+ }
4113
+ if (envChanged) {
4114
+ merged.env = mergedEnv;
4115
+ changes.push("env vars added from pack");
4116
+ }
4117
+ }
4118
+ if (packTemplate.permissions) {
4119
+ const userAllow = userSettings.permissions?.allow || [];
4120
+ const userDeny = userSettings.permissions?.deny || [];
4121
+ const packAllow = packTemplate.permissions.allow || [];
4122
+ const packDeny = packTemplate.permissions.deny || [];
4123
+ const mergedAllow = unionDedupe(userAllow, packAllow);
4124
+ const mergedDeny = unionDedupe(userDeny, packDeny);
4125
+ if (mergedAllow.length !== userAllow.length || mergedDeny.length !== userDeny.length) {
4126
+ merged.permissions = { allow: mergedAllow, deny: mergedDeny };
4127
+ changes.push(
4128
+ `permissions union (+${mergedAllow.length - userAllow.length} allow, +${mergedDeny.length - userDeny.length} deny)`
4129
+ );
4130
+ }
4131
+ }
4132
+ if (packTemplate.hooks) {
4133
+ const userHooks = userSettings.hooks || {};
4134
+ const { merged: mergedHooks, touchedEvents } = mergeHooksPerEvent(
4135
+ packTemplate.hooks,
4136
+ userHooks
4137
+ );
4138
+ if (touchedEvents.length > 0) {
4139
+ merged.hooks = mergedHooks;
4140
+ changes.push(`hooks added for events: ${touchedEvents.join(", ")}`);
4141
+ }
4142
+ }
4143
+ if (changes.length === 0) {
4144
+ return { action: "no-change", changes: [] };
4145
+ }
4146
+ let backupPath;
4147
+ if (projectHasSettings) {
4148
+ backupPath = backupFilename(projectSettingsPath);
4149
+ await fs11.copyFile(projectSettingsPath, backupPath);
4150
+ }
4151
+ await writeJsonAtomic(projectSettingsPath, merged);
4152
+ return { action: "merged", backupPath, changes };
4153
+ }
4154
+
4155
+ // src/lib/preview-team-pack-sync-changes-for-dry-run.ts
4156
+ import { join as join27 } from "path";
4157
+
4158
+ // src/lib/symlink-farm-for-team-pack-mount-dirs.ts
4159
+ import { promises as fs13 } from "fs";
4160
+ import { dirname as dirname5, join as join26, relative as relative3 } from "path";
4161
+
4162
+ // src/lib/backup-existing-dir-before-symlink-override.ts
4163
+ import { promises as fs12 } from "fs";
4164
+ function timestamp() {
4165
+ const d = /* @__PURE__ */ new Date();
4166
+ const pad = (n) => n.toString().padStart(2, "0");
4167
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}`;
4168
+ }
4169
+ async function backupDirBeforeReplace(targetPath) {
4170
+ const backupPath = `${targetPath}.backup-${timestamp()}`;
4171
+ await fs12.rename(targetPath, backupPath);
4172
+ return backupPath;
4173
+ }
4174
+
4175
+ // src/lib/symlink-farm-for-team-pack-mount-dirs.ts
4176
+ var TEAM_PACK_MOUNT_DIRS = [
4177
+ "skills",
4178
+ "agents",
4179
+ "commands",
4180
+ "hooks",
4181
+ "workflows",
4182
+ "scripts",
4183
+ "knowledge"
4184
+ ];
4185
+ async function isSymbolicLink(path) {
4186
+ try {
4187
+ const st = await fs13.lstat(path);
4188
+ return st.isSymbolicLink();
4189
+ } catch {
4190
+ return false;
4191
+ }
4192
+ }
4193
+ async function syncMountedDir(source, dest, force) {
4194
+ const dir = relative3(dirname5(dest), dest) || dest;
4195
+ if (!await pathExists(source)) {
4196
+ return { dir, action: "source-missing" };
4197
+ }
4198
+ if (await pathExists(dest)) {
4199
+ if (await isSymbolicLink(dest)) {
4200
+ await fs13.unlink(dest);
4201
+ } else if (force) {
4202
+ const backupPath = await backupDirBeforeReplace(dest);
4203
+ const relativeSource2 = relative3(dirname5(dest), source);
4204
+ await fs13.symlink(relativeSource2, dest);
4205
+ return { dir, action: "backed-up-and-linked", backupPath };
4206
+ } else {
4207
+ return { dir, action: "skipped-conflict" };
4208
+ }
4209
+ }
4210
+ const relativeSource = relative3(dirname5(dest), source);
4211
+ await fs13.symlink(relativeSource, dest);
4212
+ return { dir, action: "created" };
4213
+ }
4214
+ async function syncAllMountDirs(packDir, claudeDir, force) {
4215
+ const results = [];
4216
+ for (const dir of TEAM_PACK_MOUNT_DIRS) {
4217
+ const source = join26(packDir, dir);
4218
+ const dest = join26(claudeDir, dir);
4219
+ results.push(await syncMountedDir(source, dest, force));
4220
+ }
4221
+ return results;
4222
+ }
4223
+
4224
+ // src/lib/preview-team-pack-sync-changes-for-dry-run.ts
4225
+ async function inspectMountDir(packDir, claudeDir, dir) {
4226
+ const source = join27(packDir, dir);
4227
+ const dest = join27(claudeDir, dir);
4228
+ if (!await pathExists(source)) return "source-missing";
4229
+ if (!await pathExists(dest)) return "needs-creation";
4230
+ const { promises: fs14 } = await import("fs");
4231
+ const st = await fs14.lstat(dest);
4232
+ if (st.isSymbolicLink()) return "already-linked";
4233
+ return "conflict-real-dir";
4234
+ }
4235
+ async function listCommitsBetween(packDir, fromSha, toRef) {
4236
+ try {
4237
+ const result = await git(packDir).raw(["log", "--oneline", `${fromSha}..${toRef}`]);
4238
+ return result.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
4239
+ } catch {
4240
+ return [];
4241
+ }
4242
+ }
4243
+ async function buildSyncPreview(packDir, claudeDir, targetVersion) {
4244
+ const currentSha = await currentCommitSha(packDir);
4245
+ const currentVersion = currentSha.slice(0, 7);
4246
+ const target = targetVersion ?? await latestTag(packDir) ?? "HEAD";
4247
+ const commits = await listCommitsBetween(packDir, currentSha, target);
4248
+ const mountStatuses = [];
4249
+ for (const dir of TEAM_PACK_MOUNT_DIRS) {
4250
+ mountStatuses.push({
4251
+ dir,
4252
+ status: await inspectMountDir(packDir, claudeDir, dir)
4253
+ });
4254
+ }
4255
+ return {
4256
+ currentVersion,
4257
+ targetVersion: target,
4258
+ commitsBehind: commits,
4259
+ mountDirStatuses: mountStatuses
4260
+ };
4261
+ }
4262
+
4263
+ // src/commands/sync.ts
4264
+ async function syncAction(opts) {
4265
+ const projectRoot = process.cwd();
4266
+ const claudeDir = join28(projectRoot, ".claude");
4267
+ const packDir = join28(projectRoot, TEAM_PACK_RELATIVE_PATH);
4268
+ if (!await pathExists(packDir)) {
4269
+ log.error(
4270
+ `team-ai-pack submodule ch\u01B0a \u0111\u01B0\u1EE3c kh\u1EDFi t\u1EA1o \u1EDF ${TEAM_PACK_RELATIVE_PATH}/.
4271
+ Ch\u1EA1y 'avatar init' \u0111\u1EC3 add submodule, ho\u1EB7c 'git submodule update --init' n\u1EBFu \u0111\xE3 clone repo.`
4272
+ );
4273
+ process.exit(1);
4274
+ return;
4275
+ }
4276
+ try {
4277
+ await git(packDir).fetch(["--tags", "origin"]);
4278
+ } catch (err) {
4279
+ log.warn(
4280
+ `Kh\xF4ng fetch \u0111\u01B0\u1EE3c tags t\u1EEB origin (${err instanceof Error ? err.message : err}). S\u1EBD d\xF9ng tag local hi\u1EC7n c\xF3.`
4281
+ );
4282
+ }
4283
+ const targetVersion = opts.version ?? await latestTag(packDir);
4284
+ if (!targetVersion) {
4285
+ log.error(
4286
+ "Kh\xF4ng t\xECm th\u1EA5y tag n\xE0o trong team-ai-pack submodule. Pass --version <tag> r\xF5 r\xE0ng, ho\u1EB7c ki\u1EC3m tra repo c\xF3 tag \u0111\u01B0\u1EE3c kh\xF4ng."
4287
+ );
4288
+ process.exit(1);
4289
+ return;
4290
+ }
4291
+ if (opts.dryRun) {
4292
+ const preview = await buildSyncPreview(packDir, claudeDir, targetVersion);
4293
+ log.info(`Pack version hi\u1EC7n t\u1EA1i: ${preview.currentVersion}`);
4294
+ log.info(`Target version: ${preview.targetVersion}`);
4295
+ if (preview.commitsBehind.length === 0) {
4296
+ log.info("\u0110\xE3 \u1EDF target version, kh\xF4ng c\xF3 commit m\u1EDBi.");
4297
+ } else {
4298
+ log.info(`Commits c\u1EA7n pull (${preview.commitsBehind.length}):`);
4299
+ for (const c of preview.commitsBehind.slice(0, 20)) {
4300
+ console.log(` ${c}`);
4301
+ }
4302
+ if (preview.commitsBehind.length > 20) {
4303
+ console.log(` ... v\xE0 ${preview.commitsBehind.length - 20} commits kh\xE1c`);
4304
+ }
4305
+ }
4306
+ log.info("\nMount dir statuses:");
4307
+ for (const m of preview.mountDirStatuses) {
4308
+ console.log(` ${m.dir.padEnd(12)} ${m.status}`);
4309
+ }
4310
+ log.info("\nDry-run done. Kh\xF4ng apply thay \u0111\u1ED5i. B\u1ECF --dry-run \u0111\u1EC3 th\u1EF1c thi.");
4311
+ return;
4312
+ }
4313
+ log.info(`Checking out ${targetVersion} trong submodule...`);
4314
+ await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, targetVersion, projectRoot);
4315
+ log.info("Creating symlink farm...");
4316
+ const results = await syncAllMountDirs(packDir, claudeDir, opts.force === true);
4317
+ reportResults(results, opts.force === true);
4318
+ log.info("Merging pack settings.json template into project settings.json...");
4319
+ try {
4320
+ const mergeResult = await mergePackSettingsIntoProjectSettings(projectRoot);
4321
+ switch (mergeResult.action) {
4322
+ case "merged":
4323
+ log.success(
4324
+ ` \u2713 settings.json merged (${mergeResult.changes.join("; ")}). Backup: ${mergeResult.backupPath ?? "n/a"}`
4325
+ );
4326
+ break;
4327
+ case "no-change":
4328
+ log.info(" - settings.json \u0111\xE3 sync v\u1EDBi pack, kh\xF4ng c\xF3 thay \u0111\u1ED5i.");
4329
+ break;
4330
+ case "no-pack-template":
4331
+ log.dim(" - Pack kh\xF4ng c\xF3 templates/settings.json.tpl, skip merge.");
4332
+ break;
4333
+ }
4334
+ } catch (err) {
4335
+ log.warn(
4336
+ ` ! Merge settings.json fail: ${err instanceof Error ? err.message : err}. Symlinks \u0111\xE3 t\u1EA1o OK, hooks c\xF3 th\u1EC3 ch\u01B0a active. Manual merge n\u1EBFu c\u1EA7n.`
4337
+ );
4338
+ }
4339
+ log.success(`Synced team-ai-pack to ${targetVersion}.`);
4340
+ }
4341
+ function reportResults(results, force) {
4342
+ for (const r of results) {
4343
+ switch (r.action) {
4344
+ case "created":
4345
+ log.info(` \u2713 ${r.dir} \u2192 symlinked (new)`);
4346
+ break;
4347
+ case "updated":
4348
+ log.info(` \u2713 ${r.dir} \u2192 symlink refreshed`);
4349
+ break;
4350
+ case "backed-up-and-linked":
4351
+ log.info(` \u2713 ${r.dir} \u2192 symlinked (backup: ${r.backupPath})`);
4352
+ break;
4353
+ case "source-missing":
4354
+ log.warn(` - ${r.dir} \u2192 pack kh\xF4ng c\xF3 dir n\xE0y, skip`);
4355
+ break;
4356
+ case "skipped-conflict":
4357
+ log.warn(
4358
+ ` ! ${r.dir} \u2192 CONFLICT: existing real dir. D\xF9ng --force \u0111\u1EC3 backup + override (s\u1EBD gi\u1EEF data \u1EDF ${r.dir}.backup-<timestamp>/).`
4359
+ );
4360
+ break;
4361
+ }
4362
+ }
4363
+ const conflicts = results.filter((r) => r.action === "skipped-conflict").length;
4364
+ if (conflicts > 0 && !force) {
4365
+ log.warn(
4366
+ `${conflicts} mount dir(s) b\u1ECB skip do conflict. Ch\u1EA1y l\u1EA1i v\u1EDBi --force n\u1EBFu mu\u1ED1n override (backup t\u1EF1 \u0111\u1ED9ng).`
4367
+ );
4368
+ }
4369
+ }
4017
4370
  function registerSyncCommand(program2) {
4018
- program2.command("sync").description("Pull team-ai-pack m\u1EDBi nh\u1EA5t (M08)").option("--force", "Override .claude/pack/, backup tr\u01B0\u1EDBc").option("--version <tag>", "Pin v\xE0o version c\u1EE5 th\u1EC3").option("--dry-run", "Hi\u1EC3n th\u1ECB changes, kh\xF4ng apply").action(notImplementedYet("sync", "Milestone 08"));
4371
+ program2.command("sync").description("Pull team-ai-pack m\u1EDBi nh\u1EA5t + t\u1EA1o symlink farm v\xE0o .claude/").option("--force", "Override .claude/<dir>/ n\u1EBFu l\xE0 real dir (backup tr\u01B0\u1EDBc)").option("--version <tag>", "Pin v\xE0o version c\u1EE5 th\u1EC3 (vd: v0.2.0)").option("--dry-run", "Hi\u1EC3n th\u1ECB preview, kh\xF4ng apply thay \u0111\u1ED5i").action(syncAction);
4019
4372
  }
4020
4373
 
4021
4374
  // src/commands/tools.ts
@@ -4027,40 +4380,40 @@ function registerToolsCommand(program2) {
4027
4380
  }
4028
4381
 
4029
4382
  // src/commands/uninstall.ts
4030
- import { relative as relative3 } from "path";
4383
+ import { relative as relative4 } from "path";
4031
4384
  import { confirm as confirm6 } from "@inquirer/prompts";
4032
4385
  import boxen7 from "boxen";
4033
4386
 
4034
4387
  // src/lib/create-uninstall-backup-snapshot.ts
4035
4388
  import { cp, mkdir, writeFile } from "fs/promises";
4036
4389
  import { homedir as homedir4 } from "os";
4037
- import { basename as basename2, join as join25 } from "path";
4038
- var UNINSTALL_BACKUPS_DIR = join25(homedir4(), ".avatar", "uninstall-backups");
4390
+ import { basename as basename2, join as join29 } from "path";
4391
+ var UNINSTALL_BACKUPS_DIR = join29(homedir4(), ".avatar", "uninstall-backups");
4039
4392
  async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersion) {
4040
4393
  const projectName = basename2(projectRoot);
4041
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4042
- const backupDir = join25(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp}`);
4394
+ const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4395
+ const backupDir = join29(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp2}`);
4043
4396
  await mkdir(backupDir, { recursive: true, mode: 448 });
4044
4397
  if (artifacts.claudeDir) {
4045
- await cp(artifacts.claudeDir, join25(backupDir, ".claude"), { recursive: true });
4398
+ await cp(artifacts.claudeDir, join29(backupDir, ".claude"), { recursive: true });
4046
4399
  }
4047
4400
  if (artifacts.claudeMd) {
4048
- await cp(artifacts.claudeMd, join25(backupDir, "CLAUDE.md"));
4401
+ await cp(artifacts.claudeMd, join29(backupDir, "CLAUDE.md"));
4049
4402
  }
4050
4403
  if (artifacts.postMergeHook || artifacts.prePushHook) {
4051
- const hooksBackupDir = join25(backupDir, "hooks");
4404
+ const hooksBackupDir = join29(backupDir, "hooks");
4052
4405
  await mkdir(hooksBackupDir, { recursive: true });
4053
4406
  if (artifacts.postMergeHook) {
4054
- await cp(artifacts.postMergeHook, join25(hooksBackupDir, "post-merge"));
4407
+ await cp(artifacts.postMergeHook, join29(hooksBackupDir, "post-merge"));
4055
4408
  }
4056
4409
  if (artifacts.prePushHook) {
4057
- await cp(artifacts.prePushHook, join25(hooksBackupDir, "pre-push"));
4410
+ await cp(artifacts.prePushHook, join29(hooksBackupDir, "pre-push"));
4058
4411
  }
4059
4412
  }
4060
4413
  const manifest = {
4061
4414
  projectName,
4062
4415
  projectPath: projectRoot,
4063
- timestamp,
4416
+ timestamp: timestamp2,
4064
4417
  avatarVersion,
4065
4418
  artifacts: {
4066
4419
  claudeDir: !!artifacts.claudeDir,
@@ -4069,27 +4422,27 @@ async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersi
4069
4422
  prePushHook: !!artifacts.prePushHook
4070
4423
  }
4071
4424
  };
4072
- await writeFile(join25(backupDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
4425
+ await writeFile(join29(backupDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
4073
4426
  return backupDir;
4074
4427
  }
4075
4428
 
4076
4429
  // src/lib/detect-avatar-project-artifacts.ts
4077
4430
  import { existsSync as existsSync9 } from "fs";
4078
- import { join as join26 } from "path";
4431
+ import { join as join30 } from "path";
4079
4432
  function existsOrNull(path) {
4080
4433
  return existsSync9(path) ? path : null;
4081
4434
  }
4082
4435
  function detectAvatarProjectArtifacts(projectRoot) {
4083
- const claudeDir = existsOrNull(join26(projectRoot, ".claude"));
4084
- const claudeMd = existsOrNull(join26(projectRoot, "CLAUDE.md"));
4085
- const postMergeHook = existsOrNull(join26(projectRoot, ".git", "hooks", "post-merge"));
4436
+ const claudeDir = existsOrNull(join30(projectRoot, ".claude"));
4437
+ const claudeMd = existsOrNull(join30(projectRoot, "CLAUDE.md"));
4438
+ const postMergeHook = existsOrNull(join30(projectRoot, ".git", "hooks", "post-merge"));
4086
4439
  const prePushHook = existsOrNull(
4087
- join26(projectRoot, ".git", "modules", "src", "hooks", "pre-push")
4440
+ join30(projectRoot, ".git", "modules", "src", "hooks", "pre-push")
4088
4441
  );
4089
- const gitignorePath = existsOrNull(join26(projectRoot, ".gitignore"));
4090
- const gitmodulesPath = existsOrNull(join26(projectRoot, ".gitmodules"));
4091
- const notesDir = existsOrNull(join26(projectRoot, "notes"));
4092
- const scriptsDir = existsOrNull(join26(projectRoot, "scripts"));
4442
+ const gitignorePath = existsOrNull(join30(projectRoot, ".gitignore"));
4443
+ const gitmodulesPath = existsOrNull(join30(projectRoot, ".gitmodules"));
4444
+ const notesDir = existsOrNull(join30(projectRoot, "notes"));
4445
+ const scriptsDir = existsOrNull(join30(projectRoot, "scripts"));
4093
4446
  const hasAnyArtifact = !!(claudeDir || claudeMd || postMergeHook || prePushHook);
4094
4447
  return {
4095
4448
  hasAnyArtifact,
@@ -4110,11 +4463,11 @@ async function executeUninstallDeletion(artifacts, flags) {
4110
4463
  if (artifacts.claudeDir) {
4111
4464
  if (flags.keepSubmodule) {
4112
4465
  const { readdir: readdir2 } = await import("fs/promises");
4113
- const { join: join27 } = await import("path");
4466
+ const { join: join31 } = await import("path");
4114
4467
  const entries = await readdir2(artifacts.claudeDir);
4115
4468
  for (const entry of entries) {
4116
4469
  if (entry === "pack") continue;
4117
- await rm(join27(artifacts.claudeDir, entry), { recursive: true, force: true });
4470
+ await rm(join31(artifacts.claudeDir, entry), { recursive: true, force: true });
4118
4471
  }
4119
4472
  } else {
4120
4473
  await rm(artifacts.claudeDir, { recursive: true, force: true });
@@ -4183,7 +4536,7 @@ async function removeSubmoduleEntry(gitmodulesPath, submodulePath) {
4183
4536
  }
4184
4537
 
4185
4538
  // src/commands/uninstall.ts
4186
- var CLI_VERSION = "1.4.2";
4539
+ var CLI_VERSION = "1.5.0";
4187
4540
  function registerUninstallCommand(program2) {
4188
4541
  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) => {
4189
4542
  try {
@@ -4233,7 +4586,7 @@ function printUninstallSummary(projectRoot, artifacts, opts) {
4233
4586
  log.plain("");
4234
4587
  log.plain("C\xE1c artifact s\u1EBD g\u1EE1:");
4235
4588
  if (artifacts.claudeDir)
4236
- log.plain(` ${chalk.red("\u2717")} ${relative3(projectRoot, artifacts.claudeDir) || ".claude/"}`);
4589
+ log.plain(` ${chalk.red("\u2717")} ${relative4(projectRoot, artifacts.claudeDir) || ".claude/"}`);
4237
4590
  if (artifacts.claudeMd) log.plain(` ${chalk.red("\u2717")} CLAUDE.md`);
4238
4591
  if (artifacts.postMergeHook && !opts.keepHooks) {
4239
4592
  log.plain(` ${chalk.red("\u2717")} .git/hooks/post-merge`);
@@ -4265,7 +4618,7 @@ function printUninstallSuccessBox(backupPath) {
4265
4618
  }
4266
4619
 
4267
4620
  // src/index.ts
4268
- var CLI_VERSION2 = "1.4.2";
4621
+ var CLI_VERSION2 = "1.5.0";
4269
4622
  var program = new Command();
4270
4623
  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(
4271
4624
  "beforeAll",