@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 +386 -33
- package/dist/index.js.map +1 -1
- package/dist/lib/print-welcome-screen.js +1 -1
- package/dist/lib/print-welcome-screen.js.map +1 -1
- package/dist/templates/CLAUDE.md.tpl +49 -3
- package/dist/templates/settings.json.tpl +40 -26
- package/package.json +1 -1
- package/src/templates/CLAUDE.md.tpl +49 -3
- package/src/templates/settings.json.tpl +40 -26
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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:
|
|
138
|
-
await
|
|
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
|
|
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
|
|
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
|
|
4038
|
-
var UNINSTALL_BACKUPS_DIR =
|
|
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
|
|
4042
|
-
const backupDir =
|
|
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,
|
|
4398
|
+
await cp(artifacts.claudeDir, join29(backupDir, ".claude"), { recursive: true });
|
|
4046
4399
|
}
|
|
4047
4400
|
if (artifacts.claudeMd) {
|
|
4048
|
-
await cp(artifacts.claudeMd,
|
|
4401
|
+
await cp(artifacts.claudeMd, join29(backupDir, "CLAUDE.md"));
|
|
4049
4402
|
}
|
|
4050
4403
|
if (artifacts.postMergeHook || artifacts.prePushHook) {
|
|
4051
|
-
const hooksBackupDir =
|
|
4404
|
+
const hooksBackupDir = join29(backupDir, "hooks");
|
|
4052
4405
|
await mkdir(hooksBackupDir, { recursive: true });
|
|
4053
4406
|
if (artifacts.postMergeHook) {
|
|
4054
|
-
await cp(artifacts.postMergeHook,
|
|
4407
|
+
await cp(artifacts.postMergeHook, join29(hooksBackupDir, "post-merge"));
|
|
4055
4408
|
}
|
|
4056
4409
|
if (artifacts.prePushHook) {
|
|
4057
|
-
await cp(artifacts.prePushHook,
|
|
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(
|
|
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
|
|
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(
|
|
4084
|
-
const claudeMd = existsOrNull(
|
|
4085
|
-
const postMergeHook = existsOrNull(
|
|
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
|
-
|
|
4440
|
+
join30(projectRoot, ".git", "modules", "src", "hooks", "pre-push")
|
|
4088
4441
|
);
|
|
4089
|
-
const gitignorePath = existsOrNull(
|
|
4090
|
-
const gitmodulesPath = existsOrNull(
|
|
4091
|
-
const notesDir = existsOrNull(
|
|
4092
|
-
const scriptsDir = existsOrNull(
|
|
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:
|
|
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(
|
|
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.
|
|
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")} ${
|
|
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.
|
|
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",
|