@nalvietnam/avatar-cli 1.6.0 → 1.6.2
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 +296 -236
- 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/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1852,17 +1852,41 @@ import { join as join14 } from "path";
|
|
|
1852
1852
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
1853
1853
|
var WIKI_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
1854
1854
|
var DEFAULT_LLMLITE_MODEL = "nal-claude";
|
|
1855
|
+
var DEFAULT_ANTHROPIC_MODEL = "claude-sonnet-4-5";
|
|
1856
|
+
function normalizeAnthropicBaseUrl(rawBaseUrl) {
|
|
1857
|
+
const cleaned = rawBaseUrl.replace(/\/+$/, "");
|
|
1858
|
+
if (cleaned.endsWith("/v1")) return `${cleaned}/`;
|
|
1859
|
+
if (cleaned.endsWith("/v1/")) return cleaned;
|
|
1860
|
+
return `${cleaned}/v1/`;
|
|
1861
|
+
}
|
|
1855
1862
|
async function readSettingsForWikiCredentials(workspacePath) {
|
|
1856
1863
|
const settingsPath = join14(workspacePath, ".claude", "settings.json");
|
|
1857
1864
|
if (!await pathExists(settingsPath)) return null;
|
|
1858
1865
|
try {
|
|
1859
1866
|
const settings = await readJson(settingsPath);
|
|
1860
1867
|
const env = settings.env || {};
|
|
1861
|
-
const apiKey = typeof env.ANTHROPIC_AUTH_TOKEN === "string" ? env.ANTHROPIC_AUTH_TOKEN : null;
|
|
1862
1868
|
const baseUrl = typeof env.ANTHROPIC_BASE_URL === "string" ? env.ANTHROPIC_BASE_URL : null;
|
|
1863
|
-
if (!
|
|
1864
|
-
const
|
|
1865
|
-
|
|
1869
|
+
if (!baseUrl) return null;
|
|
1870
|
+
const userModel = typeof env.ANTHROPIC_MODEL === "string" ? env.ANTHROPIC_MODEL : "";
|
|
1871
|
+
const anthropicKey = typeof env.ANTHROPIC_API_KEY === "string" ? env.ANTHROPIC_API_KEY : null;
|
|
1872
|
+
if (anthropicKey) {
|
|
1873
|
+
return {
|
|
1874
|
+
provider: "anthropic",
|
|
1875
|
+
apiKey: anthropicKey,
|
|
1876
|
+
baseUrl: normalizeAnthropicBaseUrl(baseUrl),
|
|
1877
|
+
model: userModel.length > 0 ? userModel : DEFAULT_ANTHROPIC_MODEL
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
const llmliteToken = typeof env.ANTHROPIC_AUTH_TOKEN === "string" ? env.ANTHROPIC_AUTH_TOKEN : null;
|
|
1881
|
+
if (llmliteToken) {
|
|
1882
|
+
return {
|
|
1883
|
+
provider: "llmlite",
|
|
1884
|
+
apiKey: llmliteToken,
|
|
1885
|
+
baseUrl,
|
|
1886
|
+
model: userModel.length > 0 ? userModel : DEFAULT_LLMLITE_MODEL
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1889
|
+
return null;
|
|
1866
1890
|
} catch {
|
|
1867
1891
|
return null;
|
|
1868
1892
|
}
|
|
@@ -1880,8 +1904,10 @@ function tailLines2(text, n) {
|
|
|
1880
1904
|
async function runGitnexusWikiConditional(workspacePath) {
|
|
1881
1905
|
const creds = await readSettingsForWikiCredentials(workspacePath);
|
|
1882
1906
|
if (!creds) {
|
|
1883
|
-
log.warn("Subscription mode (
|
|
1884
|
-
log.dim(
|
|
1907
|
+
log.warn("Subscription mode (OAuth, kh\xF4ng c\xF3 API key trong settings.json) \u2192 skip wiki gen.");
|
|
1908
|
+
log.dim(
|
|
1909
|
+
"\u0110\u1EC3 gen wiki sau, ch\u1EA1y manual:\n gitnexus wiki . --api-key <key> --base-url <url> --model <model>"
|
|
1910
|
+
);
|
|
1885
1911
|
return { ran: false, skipped: true, reason: "subscription-mode" };
|
|
1886
1912
|
}
|
|
1887
1913
|
const proceed = await confirmWikiGeneration(creds.baseUrl, creds.model);
|
|
@@ -1891,7 +1917,9 @@ async function runGitnexusWikiConditional(workspacePath) {
|
|
|
1891
1917
|
);
|
|
1892
1918
|
return { ran: false, skipped: true, reason: "user-declined" };
|
|
1893
1919
|
}
|
|
1894
|
-
const sp = spinnerWithElapsed(
|
|
1920
|
+
const sp = spinnerWithElapsed(
|
|
1921
|
+
`Generating wiki via ${creds.baseUrl} (${creds.provider}) model=${creds.model}`
|
|
1922
|
+
);
|
|
1895
1923
|
const result = spawnSync10(
|
|
1896
1924
|
"gitnexus",
|
|
1897
1925
|
["wiki", ".", "--api-key", creds.apiKey, "--base-url", creds.baseUrl, "--model", creds.model],
|
|
@@ -2169,7 +2197,7 @@ function registerGitnexusCommand(program2) {
|
|
|
2169
2197
|
}
|
|
2170
2198
|
|
|
2171
2199
|
// src/commands/init.ts
|
|
2172
|
-
import { basename, join as
|
|
2200
|
+
import { basename, join as join24, relative as relative3, resolve } from "path";
|
|
2173
2201
|
import { confirm as confirm5, input as input5, select as select9 } from "@inquirer/prompts";
|
|
2174
2202
|
import boxen5 from "boxen";
|
|
2175
2203
|
|
|
@@ -3032,6 +3060,131 @@ function linkExistingRemoteToWorkspace(args) {
|
|
|
3032
3060
|
return { sshUrl, httpsUrl };
|
|
3033
3061
|
}
|
|
3034
3062
|
|
|
3063
|
+
// src/lib/merge-pack-settings-into-project-settings.ts
|
|
3064
|
+
import { promises as fs9 } from "fs";
|
|
3065
|
+
import { join as join17 } from "path";
|
|
3066
|
+
function backupFilename(originalPath) {
|
|
3067
|
+
const d = /* @__PURE__ */ new Date();
|
|
3068
|
+
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")}`;
|
|
3069
|
+
return `${originalPath}.backup-${stamp}`;
|
|
3070
|
+
}
|
|
3071
|
+
function unionDedupe(a, b) {
|
|
3072
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3073
|
+
const out = [];
|
|
3074
|
+
for (const item of [...a, ...b]) {
|
|
3075
|
+
const key = typeof item === "string" ? item : JSON.stringify(item);
|
|
3076
|
+
if (!seen.has(key)) {
|
|
3077
|
+
seen.add(key);
|
|
3078
|
+
out.push(item);
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
return out;
|
|
3082
|
+
}
|
|
3083
|
+
function mergeHooksPerEvent(packHooks, userHooks) {
|
|
3084
|
+
const touched = [];
|
|
3085
|
+
const merged = { ...userHooks };
|
|
3086
|
+
for (const [event, packEntries] of Object.entries(packHooks)) {
|
|
3087
|
+
const userEntries = userHooks[event] || [];
|
|
3088
|
+
const union = unionDedupe(userEntries, packEntries);
|
|
3089
|
+
if (union.length !== userEntries.length) {
|
|
3090
|
+
touched.push(event);
|
|
3091
|
+
}
|
|
3092
|
+
merged[event] = union;
|
|
3093
|
+
}
|
|
3094
|
+
return { merged, touchedEvents: touched };
|
|
3095
|
+
}
|
|
3096
|
+
async function mergePackSettingsIntoProjectSettings(workspacePath) {
|
|
3097
|
+
const packTemplatePath = join17(workspacePath, ".claude", "pack", "templates", "settings.json.tpl");
|
|
3098
|
+
const projectSettingsPath = join17(workspacePath, ".claude", "settings.json");
|
|
3099
|
+
if (!await pathExists(packTemplatePath)) {
|
|
3100
|
+
return { action: "no-pack-template", changes: [] };
|
|
3101
|
+
}
|
|
3102
|
+
let packTemplate;
|
|
3103
|
+
try {
|
|
3104
|
+
const raw = await readText(packTemplatePath);
|
|
3105
|
+
packTemplate = JSON.parse(raw);
|
|
3106
|
+
} catch (err) {
|
|
3107
|
+
throw new Error(
|
|
3108
|
+
`Pack settings template kh\xF4ng parse \u0111\u01B0\u1EE3c JSON: ${err.message}. Path: ${packTemplatePath}`
|
|
3109
|
+
);
|
|
3110
|
+
}
|
|
3111
|
+
let userSettings = {};
|
|
3112
|
+
let projectHasSettings = false;
|
|
3113
|
+
if (await pathExists(projectSettingsPath)) {
|
|
3114
|
+
projectHasSettings = true;
|
|
3115
|
+
try {
|
|
3116
|
+
userSettings = await readJson(projectSettingsPath);
|
|
3117
|
+
} catch (err) {
|
|
3118
|
+
throw new Error(
|
|
3119
|
+
`Project settings.json kh\xF4ng parse \u0111\u01B0\u1EE3c: ${err.message}. Manual fix tr\u01B0\u1EDBc khi sync.`
|
|
3120
|
+
);
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
const changes = [];
|
|
3124
|
+
const merged = { ...userSettings };
|
|
3125
|
+
if (packTemplate.statusLine && !userSettings.statusLine) {
|
|
3126
|
+
merged.statusLine = packTemplate.statusLine;
|
|
3127
|
+
changes.push("statusLine added");
|
|
3128
|
+
}
|
|
3129
|
+
if (typeof packTemplate.includeCoAuthoredBy === "boolean" && typeof userSettings.includeCoAuthoredBy !== "boolean") {
|
|
3130
|
+
merged.includeCoAuthoredBy = packTemplate.includeCoAuthoredBy;
|
|
3131
|
+
changes.push("includeCoAuthoredBy added");
|
|
3132
|
+
}
|
|
3133
|
+
if (packTemplate.model && !userSettings.model) {
|
|
3134
|
+
merged.model = packTemplate.model;
|
|
3135
|
+
changes.push("model added");
|
|
3136
|
+
}
|
|
3137
|
+
if (packTemplate.env) {
|
|
3138
|
+
const mergedEnv = { ...userSettings.env || {} };
|
|
3139
|
+
let envChanged = false;
|
|
3140
|
+
for (const [k, v] of Object.entries(packTemplate.env)) {
|
|
3141
|
+
if (!(k in mergedEnv)) {
|
|
3142
|
+
mergedEnv[k] = v;
|
|
3143
|
+
envChanged = true;
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
if (envChanged) {
|
|
3147
|
+
merged.env = mergedEnv;
|
|
3148
|
+
changes.push("env vars added from pack");
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
if (packTemplate.permissions) {
|
|
3152
|
+
const userAllow = userSettings.permissions?.allow || [];
|
|
3153
|
+
const userDeny = userSettings.permissions?.deny || [];
|
|
3154
|
+
const packAllow = packTemplate.permissions.allow || [];
|
|
3155
|
+
const packDeny = packTemplate.permissions.deny || [];
|
|
3156
|
+
const mergedAllow = unionDedupe(userAllow, packAllow);
|
|
3157
|
+
const mergedDeny = unionDedupe(userDeny, packDeny);
|
|
3158
|
+
if (mergedAllow.length !== userAllow.length || mergedDeny.length !== userDeny.length) {
|
|
3159
|
+
merged.permissions = { allow: mergedAllow, deny: mergedDeny };
|
|
3160
|
+
changes.push(
|
|
3161
|
+
`permissions union (+${mergedAllow.length - userAllow.length} allow, +${mergedDeny.length - userDeny.length} deny)`
|
|
3162
|
+
);
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
if (packTemplate.hooks) {
|
|
3166
|
+
const userHooks = userSettings.hooks || {};
|
|
3167
|
+
const { merged: mergedHooks, touchedEvents } = mergeHooksPerEvent(
|
|
3168
|
+
packTemplate.hooks,
|
|
3169
|
+
userHooks
|
|
3170
|
+
);
|
|
3171
|
+
if (touchedEvents.length > 0) {
|
|
3172
|
+
merged.hooks = mergedHooks;
|
|
3173
|
+
changes.push(`hooks added for events: ${touchedEvents.join(", ")}`);
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3176
|
+
if (changes.length === 0) {
|
|
3177
|
+
return { action: "no-change", changes: [] };
|
|
3178
|
+
}
|
|
3179
|
+
let backupPath;
|
|
3180
|
+
if (projectHasSettings) {
|
|
3181
|
+
backupPath = backupFilename(projectSettingsPath);
|
|
3182
|
+
await fs9.copyFile(projectSettingsPath, backupPath);
|
|
3183
|
+
}
|
|
3184
|
+
await writeJsonAtomic(projectSettingsPath, merged);
|
|
3185
|
+
return { action: "merged", backupPath, changes };
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3035
3188
|
// src/lib/safe-bootstrap-for-dirty-folder.ts
|
|
3036
3189
|
import { readdirSync } from "fs";
|
|
3037
3190
|
import { select as select8 } from "@inquirer/prompts";
|
|
@@ -3039,9 +3192,9 @@ import { simpleGit as simpleGit3 } from "simple-git";
|
|
|
3039
3192
|
|
|
3040
3193
|
// src/lib/check-folder-has-git.ts
|
|
3041
3194
|
import { existsSync as existsSync6, statSync } from "fs";
|
|
3042
|
-
import { join as
|
|
3195
|
+
import { join as join18 } from "path";
|
|
3043
3196
|
function checkFolderHasGit(folderPath) {
|
|
3044
|
-
const gitPath =
|
|
3197
|
+
const gitPath = join18(folderPath, ".git");
|
|
3045
3198
|
if (!existsSync6(gitPath)) return false;
|
|
3046
3199
|
const stat = statSync(gitPath);
|
|
3047
3200
|
return stat.isDirectory() || stat.isFile();
|
|
@@ -3073,7 +3226,7 @@ async function createInitialGitCommit(folderPath) {
|
|
|
3073
3226
|
|
|
3074
3227
|
// src/lib/detect-folder-tech-stack.ts
|
|
3075
3228
|
import { existsSync as existsSync7 } from "fs";
|
|
3076
|
-
import { join as
|
|
3229
|
+
import { join as join19 } from "path";
|
|
3077
3230
|
var SIGNATURES = {
|
|
3078
3231
|
node: ["package.json"],
|
|
3079
3232
|
python: ["pyproject.toml", "requirements.txt", "setup.py", "Pipfile"],
|
|
@@ -3085,7 +3238,7 @@ var SIGNATURES = {
|
|
|
3085
3238
|
function detectFolderTechStack(folderPath) {
|
|
3086
3239
|
const matched = [];
|
|
3087
3240
|
for (const [stack, files] of Object.entries(SIGNATURES)) {
|
|
3088
|
-
if (files.some((f) => existsSync7(
|
|
3241
|
+
if (files.some((f) => existsSync7(join19(folderPath, f)))) {
|
|
3089
3242
|
matched.push(stack);
|
|
3090
3243
|
}
|
|
3091
3244
|
}
|
|
@@ -3094,25 +3247,25 @@ function detectFolderTechStack(folderPath) {
|
|
|
3094
3247
|
|
|
3095
3248
|
// src/lib/gitignore-template-loader.ts
|
|
3096
3249
|
import { readFileSync as readFileSync3 } from "fs";
|
|
3097
|
-
import { dirname as dirname4, join as
|
|
3250
|
+
import { dirname as dirname4, join as join20 } from "path";
|
|
3098
3251
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3099
3252
|
var __dirname = dirname4(fileURLToPath2(import.meta.url));
|
|
3100
3253
|
var CANDIDATE_DIRS = [
|
|
3101
3254
|
// Bundled production: dist/index.js → __dirname = .../dist/, sibling dist/templates
|
|
3102
|
-
|
|
3255
|
+
join20(__dirname, "templates", "gitignore"),
|
|
3103
3256
|
// Legacy bundled: nếu file là dist/lib/*.js (sub-bundle), templates ở dist/templates
|
|
3104
|
-
|
|
3257
|
+
join20(__dirname, "..", "templates", "gitignore"),
|
|
3105
3258
|
// Dev mode (vitest/tsx run src/ trực tiếp): __dirname = src/lib/
|
|
3106
|
-
|
|
3259
|
+
join20(__dirname, "..", "..", "src", "templates", "gitignore"),
|
|
3107
3260
|
// npm-installed alt: __dirname = .../dist/ → package_root/src/templates
|
|
3108
|
-
|
|
3261
|
+
join20(__dirname, "..", "src", "templates", "gitignore")
|
|
3109
3262
|
];
|
|
3110
3263
|
var AVATAR_MARKER_START = "# === avatar ===";
|
|
3111
3264
|
var AVATAR_MARKER_END = "# === /avatar ===";
|
|
3112
3265
|
function readTemplate(stack) {
|
|
3113
3266
|
for (const dir of CANDIDATE_DIRS) {
|
|
3114
3267
|
try {
|
|
3115
|
-
return readFileSync3(
|
|
3268
|
+
return readFileSync3(join20(dir, `${stack}.txt`), "utf8");
|
|
3116
3269
|
} catch {
|
|
3117
3270
|
}
|
|
3118
3271
|
}
|
|
@@ -3127,9 +3280,9 @@ ${readTemplate(s).trim()}`);
|
|
|
3127
3280
|
|
|
3128
3281
|
// src/lib/write-or-merge-gitignore.ts
|
|
3129
3282
|
import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
3130
|
-
import { join as
|
|
3283
|
+
import { join as join21 } from "path";
|
|
3131
3284
|
function writeOrMergeGitignore(folderPath, avatarBlock) {
|
|
3132
|
-
const path =
|
|
3285
|
+
const path = join21(folderPath, ".gitignore");
|
|
3133
3286
|
if (!existsSync8(path)) {
|
|
3134
3287
|
writeFileSync(path, avatarBlock, "utf8");
|
|
3135
3288
|
return;
|
|
@@ -3304,9 +3457,75 @@ async function safeBootstrapGitInFolder(folderPath, opts = {}) {
|
|
|
3304
3457
|
await appendAuditEntry("bootstrap", `state=${state},strategy=${strategy}`);
|
|
3305
3458
|
}
|
|
3306
3459
|
|
|
3460
|
+
// src/lib/symlink-farm-for-team-pack-mount-dirs.ts
|
|
3461
|
+
import { promises as fs11 } from "fs";
|
|
3462
|
+
import { dirname as dirname5, join as join22, relative as relative2 } from "path";
|
|
3463
|
+
|
|
3464
|
+
// src/lib/backup-existing-dir-before-symlink-override.ts
|
|
3465
|
+
import { promises as fs10 } from "fs";
|
|
3466
|
+
function timestamp() {
|
|
3467
|
+
const d = /* @__PURE__ */ new Date();
|
|
3468
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
3469
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}`;
|
|
3470
|
+
}
|
|
3471
|
+
async function backupDirBeforeReplace(targetPath) {
|
|
3472
|
+
const backupPath = `${targetPath}.backup-${timestamp()}`;
|
|
3473
|
+
await fs10.rename(targetPath, backupPath);
|
|
3474
|
+
return backupPath;
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3477
|
+
// src/lib/symlink-farm-for-team-pack-mount-dirs.ts
|
|
3478
|
+
var TEAM_PACK_MOUNT_DIRS = [
|
|
3479
|
+
"skills",
|
|
3480
|
+
"agents",
|
|
3481
|
+
"commands",
|
|
3482
|
+
"hooks",
|
|
3483
|
+
"workflows",
|
|
3484
|
+
"scripts",
|
|
3485
|
+
"knowledge"
|
|
3486
|
+
];
|
|
3487
|
+
async function isSymbolicLink(path) {
|
|
3488
|
+
try {
|
|
3489
|
+
const st = await fs11.lstat(path);
|
|
3490
|
+
return st.isSymbolicLink();
|
|
3491
|
+
} catch {
|
|
3492
|
+
return false;
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
async function syncMountedDir(source, dest, force) {
|
|
3496
|
+
const dir = relative2(dirname5(dest), dest) || dest;
|
|
3497
|
+
if (!await pathExists(source)) {
|
|
3498
|
+
return { dir, action: "source-missing" };
|
|
3499
|
+
}
|
|
3500
|
+
if (await pathExists(dest)) {
|
|
3501
|
+
if (await isSymbolicLink(dest)) {
|
|
3502
|
+
await fs11.unlink(dest);
|
|
3503
|
+
} else if (force) {
|
|
3504
|
+
const backupPath = await backupDirBeforeReplace(dest);
|
|
3505
|
+
const relativeSource2 = relative2(dirname5(dest), source);
|
|
3506
|
+
await fs11.symlink(relativeSource2, dest);
|
|
3507
|
+
return { dir, action: "backed-up-and-linked", backupPath };
|
|
3508
|
+
} else {
|
|
3509
|
+
return { dir, action: "skipped-conflict" };
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
const relativeSource = relative2(dirname5(dest), source);
|
|
3513
|
+
await fs11.symlink(relativeSource, dest);
|
|
3514
|
+
return { dir, action: "created" };
|
|
3515
|
+
}
|
|
3516
|
+
async function syncAllMountDirs(packDir, claudeDir, force) {
|
|
3517
|
+
const results = [];
|
|
3518
|
+
for (const dir of TEAM_PACK_MOUNT_DIRS) {
|
|
3519
|
+
const source = join22(packDir, dir);
|
|
3520
|
+
const dest = join22(claudeDir, dir);
|
|
3521
|
+
results.push(await syncMountedDir(source, dest, force));
|
|
3522
|
+
}
|
|
3523
|
+
return results;
|
|
3524
|
+
}
|
|
3525
|
+
|
|
3307
3526
|
// src/commands/init-conflict-detection-helpers.ts
|
|
3308
3527
|
import { readdir } from "fs/promises";
|
|
3309
|
-
import { join as
|
|
3528
|
+
import { join as join23 } from "path";
|
|
3310
3529
|
async function isEmptyOrMissing(path) {
|
|
3311
3530
|
if (!await pathExists(path)) return true;
|
|
3312
3531
|
try {
|
|
@@ -3319,7 +3538,7 @@ async function isEmptyOrMissing(path) {
|
|
|
3319
3538
|
}
|
|
3320
3539
|
async function findAlternativeWorkspaceName(parent, desiredName, maxAttempts = 10) {
|
|
3321
3540
|
for (let i = 2; i < maxAttempts; i++) {
|
|
3322
|
-
const candidate =
|
|
3541
|
+
const candidate = join23(parent, `${desiredName}-${i}`);
|
|
3323
3542
|
if (await isEmptyOrMissing(candidate)) return candidate;
|
|
3324
3543
|
}
|
|
3325
3544
|
return null;
|
|
@@ -3749,7 +3968,7 @@ async function runInitFromScratch(opts, ownerEmail) {
|
|
|
3749
3968
|
const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
|
|
3750
3969
|
const workspaceParent = resolve(opts.workspaceParent ?? ".");
|
|
3751
3970
|
const workspacePath = await resolveWorkspacePath(workspaceParent, projectName, opts.force);
|
|
3752
|
-
const srcPath =
|
|
3971
|
+
const srcPath = join24(workspacePath, "src");
|
|
3753
3972
|
await ensureDir(workspacePath);
|
|
3754
3973
|
await ensureDir(srcPath);
|
|
3755
3974
|
await safeBootstrapGitInFolder(srcPath, { autoYes: true });
|
|
@@ -3887,11 +4106,12 @@ async function finalizeWorkspaceScaffold(args) {
|
|
|
3887
4106
|
await writeRootClaudeMd(args.workspacePath, vars);
|
|
3888
4107
|
await writeProjectSettings(args.workspacePath, vars);
|
|
3889
4108
|
await appendGitignoreEntries(args.workspacePath);
|
|
3890
|
-
await ensureDir(
|
|
3891
|
-
await ensureDir(
|
|
3892
|
-
await installGitHook(
|
|
3893
|
-
await installGitHook(
|
|
4109
|
+
await ensureDir(join24(args.workspacePath, "notes"));
|
|
4110
|
+
await ensureDir(join24(args.workspacePath, "scripts"));
|
|
4111
|
+
await installGitHook(join24(args.workspacePath, ".git"), "post-merge");
|
|
4112
|
+
await installGitHook(join24(args.workspacePath, ".git", "modules", "src"), "pre-push");
|
|
3894
4113
|
log.success("C\xE0i post-merge (workspace) + pre-push (src/)");
|
|
4114
|
+
await autoSyncPackOnInit(args.workspacePath);
|
|
3895
4115
|
await appendAuditEntry("init", `flow=${args.flow},workspace=${args.workspaceName}`);
|
|
3896
4116
|
await maybeCommitWorkspace(args.workspacePath, args.skipCommit);
|
|
3897
4117
|
await maybeCreateWorkspaceRemote(args);
|
|
@@ -3926,6 +4146,39 @@ async function finalizeWorkspaceScaffold(args) {
|
|
|
3926
4146
|
}
|
|
3927
4147
|
printInitSuccessBox(args.workspacePath, args.flow, aiResult, gitnexusResult);
|
|
3928
4148
|
}
|
|
4149
|
+
async function autoSyncPackOnInit(workspacePath) {
|
|
4150
|
+
const packDir = join24(workspacePath, TEAM_PACK_RELATIVE_PATH);
|
|
4151
|
+
if (!await pathExists(packDir)) {
|
|
4152
|
+
log.dim("Pack submodule kh\xF4ng t\u1ED3n t\u1EA1i (skip auto-sync). C\xF3 th\u1EC3 ch\u1EA1y `avatar sync` sau.");
|
|
4153
|
+
return;
|
|
4154
|
+
}
|
|
4155
|
+
const claudeDir = join24(workspacePath, ".claude");
|
|
4156
|
+
log.info("Auto-sync pack content v\xE0o .claude/ (symlinks + settings merge)...");
|
|
4157
|
+
try {
|
|
4158
|
+
const results = await syncAllMountDirs(packDir, claudeDir, false);
|
|
4159
|
+
const created = results.filter((r) => r.action === "created" || r.action === "updated").length;
|
|
4160
|
+
const missing = results.filter((r) => r.action === "source-missing").length;
|
|
4161
|
+
log.success(
|
|
4162
|
+
` \u2713 Symlinks: ${created} created${missing > 0 ? `, ${missing} source-missing (pack thi\u1EBFu dir)` : ""}`
|
|
4163
|
+
);
|
|
4164
|
+
const mergeResult = await mergePackSettingsIntoProjectSettings(workspacePath);
|
|
4165
|
+
switch (mergeResult.action) {
|
|
4166
|
+
case "merged":
|
|
4167
|
+
log.success(` \u2713 settings.json merged (${mergeResult.changes.join("; ")})`);
|
|
4168
|
+
break;
|
|
4169
|
+
case "no-change":
|
|
4170
|
+
log.dim(" - settings.json \u0111\xE3 sync, kh\xF4ng c\u1EA7n thay \u0111\u1ED5i.");
|
|
4171
|
+
break;
|
|
4172
|
+
case "no-pack-template":
|
|
4173
|
+
log.dim(" - Pack kh\xF4ng c\xF3 templates/settings.json.tpl, skip merge.");
|
|
4174
|
+
break;
|
|
4175
|
+
}
|
|
4176
|
+
} catch (err) {
|
|
4177
|
+
log.warn(
|
|
4178
|
+
`Auto-sync pack fail: ${err instanceof Error ? err.message : err}. Ch\u1EA1y \`avatar sync\` th\u1EE7 c\xF4ng \u0111\u1EC3 retry.`
|
|
4179
|
+
);
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
3929
4182
|
async function maybeCreateWorkspaceRemote(args) {
|
|
3930
4183
|
if (args.skipCommit) {
|
|
3931
4184
|
log.dim("Skip workspace remote (ch\u01B0a commit). Setup sau qua: gh repo create ...");
|
|
@@ -4013,7 +4266,7 @@ async function maybeCreateWorkspaceRemote(args) {
|
|
|
4013
4266
|
}
|
|
4014
4267
|
}
|
|
4015
4268
|
async function resolveWorkspacePath(parent, desiredName, force) {
|
|
4016
|
-
const desired =
|
|
4269
|
+
const desired = join24(parent, desiredName);
|
|
4017
4270
|
if (await isEmptyOrMissing(desired)) return desired;
|
|
4018
4271
|
log.warn(`Workspace path "${desired}" \u0111\xE3 c\xF3 n\u1ED9i dung.`);
|
|
4019
4272
|
while (true) {
|
|
@@ -4044,7 +4297,7 @@ async function resolveWorkspacePath(parent, desiredName, force) {
|
|
|
4044
4297
|
message: "T\xEAn workspace m\u1EDBi:",
|
|
4045
4298
|
validate: (v) => v.trim().length > 0 ? true : "T\xEAn kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng"
|
|
4046
4299
|
});
|
|
4047
|
-
const newPath =
|
|
4300
|
+
const newPath = join24(parent, newName.trim());
|
|
4048
4301
|
if (await isEmptyOrMissing(newPath)) return newPath;
|
|
4049
4302
|
log.warn(`"${newPath}" c\u0169ng \u0111\xE3 c\xF3 n\u1ED9i dung. Th\u1EED t\xEAn kh\xE1c.`);
|
|
4050
4303
|
}
|
|
@@ -4087,7 +4340,7 @@ function formatGitnexusStatusLine(result) {
|
|
|
4087
4340
|
}
|
|
4088
4341
|
function printInitSuccessBox(rootPath, flow, aiResult = null, gitnexusResult = null) {
|
|
4089
4342
|
const lines = [
|
|
4090
|
-
`${chalk.green("\u2713")} Workspace s\u1EB5n s\xE0ng: ${
|
|
4343
|
+
`${chalk.green("\u2713")} Workspace s\u1EB5n s\xE0ng: ${relative3(process.cwd(), rootPath) || rootPath}`,
|
|
4091
4344
|
` ${chalk.dim(`(flow: ${flow})`)}`,
|
|
4092
4345
|
formatAiStatusLine(aiResult),
|
|
4093
4346
|
formatGitnexusStatusLine(gitnexusResult),
|
|
@@ -4150,18 +4403,18 @@ function registerSecretsCommand(program2) {
|
|
|
4150
4403
|
}
|
|
4151
4404
|
|
|
4152
4405
|
// src/commands/status.ts
|
|
4153
|
-
import { promises as
|
|
4154
|
-
import { join as
|
|
4406
|
+
import { promises as fs13 } from "fs";
|
|
4407
|
+
import { join as join26 } from "path";
|
|
4155
4408
|
import boxen6 from "boxen";
|
|
4156
4409
|
|
|
4157
4410
|
// src/lib/pack-backup-manager.ts
|
|
4158
|
-
import { promises as
|
|
4159
|
-
import { join as
|
|
4411
|
+
import { promises as fs12 } from "fs";
|
|
4412
|
+
import { join as join25 } from "path";
|
|
4160
4413
|
var BACKUP_DIR_NAME = "_backup";
|
|
4161
4414
|
async function listBackups(projectRoot) {
|
|
4162
|
-
const dir =
|
|
4415
|
+
const dir = join25(projectRoot, ".claude", BACKUP_DIR_NAME);
|
|
4163
4416
|
if (!await pathExists(dir)) return [];
|
|
4164
|
-
const entries = await
|
|
4417
|
+
const entries = await fs12.readdir(dir, { withFileTypes: true });
|
|
4165
4418
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort().reverse();
|
|
4166
4419
|
}
|
|
4167
4420
|
|
|
@@ -4185,7 +4438,7 @@ function registerStatusCommand(program2) {
|
|
|
4185
4438
|
}
|
|
4186
4439
|
async function gatherStatus(cwd) {
|
|
4187
4440
|
const projectName = cwd.split("/").filter(Boolean).pop() ?? "unknown";
|
|
4188
|
-
const claudeRoot =
|
|
4441
|
+
const claudeRoot = join26(cwd, ".claude");
|
|
4189
4442
|
const hasAvatar = await pathExists(claudeRoot);
|
|
4190
4443
|
if (!hasAvatar) {
|
|
4191
4444
|
return {
|
|
@@ -4198,9 +4451,9 @@ async function gatherStatus(cwd) {
|
|
|
4198
4451
|
hasAvatar: false
|
|
4199
4452
|
};
|
|
4200
4453
|
}
|
|
4201
|
-
const packVersion = await isGitRepo(
|
|
4202
|
-
const pendingDir =
|
|
4203
|
-
const pendingCount = await pathExists(pendingDir) ? (await
|
|
4454
|
+
const packVersion = await isGitRepo(join26(claudeRoot, "pack")) ? await readPinnedPackVersion(cwd).catch(() => null) : null;
|
|
4455
|
+
const pendingDir = join26(claudeRoot, "_pending");
|
|
4456
|
+
const pendingCount = await pathExists(pendingDir) ? (await fs13.readdir(pendingDir)).filter((n) => n.endsWith(".diff.md")).length : 0;
|
|
4204
4457
|
const backupCount = (await listBackups(cwd)).length;
|
|
4205
4458
|
const techStackSummary = await readTechStackFirstLine(claudeRoot);
|
|
4206
4459
|
return {
|
|
@@ -4214,7 +4467,7 @@ async function gatherStatus(cwd) {
|
|
|
4214
4467
|
};
|
|
4215
4468
|
}
|
|
4216
4469
|
async function readTechStackFirstLine(claudeRoot) {
|
|
4217
|
-
const techStackPath =
|
|
4470
|
+
const techStackPath = join26(claudeRoot, "project", "tech-stack.md");
|
|
4218
4471
|
if (!await pathExists(techStackPath)) return "(no tech-stack.md)";
|
|
4219
4472
|
const content = await readText(techStackPath);
|
|
4220
4473
|
const firstNonHeaderLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#") && !l.startsWith(">"));
|
|
@@ -4237,201 +4490,8 @@ function renderStatusBox(s) {
|
|
|
4237
4490
|
// src/commands/sync.ts
|
|
4238
4491
|
import { join as join28 } from "path";
|
|
4239
4492
|
|
|
4240
|
-
// src/lib/merge-pack-settings-into-project-settings.ts
|
|
4241
|
-
import { promises as fs11 } from "fs";
|
|
4242
|
-
import { join as join25 } from "path";
|
|
4243
|
-
function backupFilename(originalPath) {
|
|
4244
|
-
const d = /* @__PURE__ */ new Date();
|
|
4245
|
-
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")}`;
|
|
4246
|
-
return `${originalPath}.backup-${stamp}`;
|
|
4247
|
-
}
|
|
4248
|
-
function unionDedupe(a, b) {
|
|
4249
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4250
|
-
const out = [];
|
|
4251
|
-
for (const item of [...a, ...b]) {
|
|
4252
|
-
const key = typeof item === "string" ? item : JSON.stringify(item);
|
|
4253
|
-
if (!seen.has(key)) {
|
|
4254
|
-
seen.add(key);
|
|
4255
|
-
out.push(item);
|
|
4256
|
-
}
|
|
4257
|
-
}
|
|
4258
|
-
return out;
|
|
4259
|
-
}
|
|
4260
|
-
function mergeHooksPerEvent(packHooks, userHooks) {
|
|
4261
|
-
const touched = [];
|
|
4262
|
-
const merged = { ...userHooks };
|
|
4263
|
-
for (const [event, packEntries] of Object.entries(packHooks)) {
|
|
4264
|
-
const userEntries = userHooks[event] || [];
|
|
4265
|
-
const union = unionDedupe(userEntries, packEntries);
|
|
4266
|
-
if (union.length !== userEntries.length) {
|
|
4267
|
-
touched.push(event);
|
|
4268
|
-
}
|
|
4269
|
-
merged[event] = union;
|
|
4270
|
-
}
|
|
4271
|
-
return { merged, touchedEvents: touched };
|
|
4272
|
-
}
|
|
4273
|
-
async function mergePackSettingsIntoProjectSettings(workspacePath) {
|
|
4274
|
-
const packTemplatePath = join25(workspacePath, ".claude", "pack", "templates", "settings.json.tpl");
|
|
4275
|
-
const projectSettingsPath = join25(workspacePath, ".claude", "settings.json");
|
|
4276
|
-
if (!await pathExists(packTemplatePath)) {
|
|
4277
|
-
return { action: "no-pack-template", changes: [] };
|
|
4278
|
-
}
|
|
4279
|
-
let packTemplate;
|
|
4280
|
-
try {
|
|
4281
|
-
const raw = await readText(packTemplatePath);
|
|
4282
|
-
packTemplate = JSON.parse(raw);
|
|
4283
|
-
} catch (err) {
|
|
4284
|
-
throw new Error(
|
|
4285
|
-
`Pack settings template kh\xF4ng parse \u0111\u01B0\u1EE3c JSON: ${err.message}. Path: ${packTemplatePath}`
|
|
4286
|
-
);
|
|
4287
|
-
}
|
|
4288
|
-
let userSettings = {};
|
|
4289
|
-
let projectHasSettings = false;
|
|
4290
|
-
if (await pathExists(projectSettingsPath)) {
|
|
4291
|
-
projectHasSettings = true;
|
|
4292
|
-
try {
|
|
4293
|
-
userSettings = await readJson(projectSettingsPath);
|
|
4294
|
-
} catch (err) {
|
|
4295
|
-
throw new Error(
|
|
4296
|
-
`Project settings.json kh\xF4ng parse \u0111\u01B0\u1EE3c: ${err.message}. Manual fix tr\u01B0\u1EDBc khi sync.`
|
|
4297
|
-
);
|
|
4298
|
-
}
|
|
4299
|
-
}
|
|
4300
|
-
const changes = [];
|
|
4301
|
-
const merged = { ...userSettings };
|
|
4302
|
-
if (packTemplate.statusLine && !userSettings.statusLine) {
|
|
4303
|
-
merged.statusLine = packTemplate.statusLine;
|
|
4304
|
-
changes.push("statusLine added");
|
|
4305
|
-
}
|
|
4306
|
-
if (typeof packTemplate.includeCoAuthoredBy === "boolean" && typeof userSettings.includeCoAuthoredBy !== "boolean") {
|
|
4307
|
-
merged.includeCoAuthoredBy = packTemplate.includeCoAuthoredBy;
|
|
4308
|
-
changes.push("includeCoAuthoredBy added");
|
|
4309
|
-
}
|
|
4310
|
-
if (packTemplate.model && !userSettings.model) {
|
|
4311
|
-
merged.model = packTemplate.model;
|
|
4312
|
-
changes.push("model added");
|
|
4313
|
-
}
|
|
4314
|
-
if (packTemplate.env) {
|
|
4315
|
-
const mergedEnv = { ...userSettings.env || {} };
|
|
4316
|
-
let envChanged = false;
|
|
4317
|
-
for (const [k, v] of Object.entries(packTemplate.env)) {
|
|
4318
|
-
if (!(k in mergedEnv)) {
|
|
4319
|
-
mergedEnv[k] = v;
|
|
4320
|
-
envChanged = true;
|
|
4321
|
-
}
|
|
4322
|
-
}
|
|
4323
|
-
if (envChanged) {
|
|
4324
|
-
merged.env = mergedEnv;
|
|
4325
|
-
changes.push("env vars added from pack");
|
|
4326
|
-
}
|
|
4327
|
-
}
|
|
4328
|
-
if (packTemplate.permissions) {
|
|
4329
|
-
const userAllow = userSettings.permissions?.allow || [];
|
|
4330
|
-
const userDeny = userSettings.permissions?.deny || [];
|
|
4331
|
-
const packAllow = packTemplate.permissions.allow || [];
|
|
4332
|
-
const packDeny = packTemplate.permissions.deny || [];
|
|
4333
|
-
const mergedAllow = unionDedupe(userAllow, packAllow);
|
|
4334
|
-
const mergedDeny = unionDedupe(userDeny, packDeny);
|
|
4335
|
-
if (mergedAllow.length !== userAllow.length || mergedDeny.length !== userDeny.length) {
|
|
4336
|
-
merged.permissions = { allow: mergedAllow, deny: mergedDeny };
|
|
4337
|
-
changes.push(
|
|
4338
|
-
`permissions union (+${mergedAllow.length - userAllow.length} allow, +${mergedDeny.length - userDeny.length} deny)`
|
|
4339
|
-
);
|
|
4340
|
-
}
|
|
4341
|
-
}
|
|
4342
|
-
if (packTemplate.hooks) {
|
|
4343
|
-
const userHooks = userSettings.hooks || {};
|
|
4344
|
-
const { merged: mergedHooks, touchedEvents } = mergeHooksPerEvent(
|
|
4345
|
-
packTemplate.hooks,
|
|
4346
|
-
userHooks
|
|
4347
|
-
);
|
|
4348
|
-
if (touchedEvents.length > 0) {
|
|
4349
|
-
merged.hooks = mergedHooks;
|
|
4350
|
-
changes.push(`hooks added for events: ${touchedEvents.join(", ")}`);
|
|
4351
|
-
}
|
|
4352
|
-
}
|
|
4353
|
-
if (changes.length === 0) {
|
|
4354
|
-
return { action: "no-change", changes: [] };
|
|
4355
|
-
}
|
|
4356
|
-
let backupPath;
|
|
4357
|
-
if (projectHasSettings) {
|
|
4358
|
-
backupPath = backupFilename(projectSettingsPath);
|
|
4359
|
-
await fs11.copyFile(projectSettingsPath, backupPath);
|
|
4360
|
-
}
|
|
4361
|
-
await writeJsonAtomic(projectSettingsPath, merged);
|
|
4362
|
-
return { action: "merged", backupPath, changes };
|
|
4363
|
-
}
|
|
4364
|
-
|
|
4365
4493
|
// src/lib/preview-team-pack-sync-changes-for-dry-run.ts
|
|
4366
4494
|
import { join as join27 } from "path";
|
|
4367
|
-
|
|
4368
|
-
// src/lib/symlink-farm-for-team-pack-mount-dirs.ts
|
|
4369
|
-
import { promises as fs13 } from "fs";
|
|
4370
|
-
import { dirname as dirname5, join as join26, relative as relative3 } from "path";
|
|
4371
|
-
|
|
4372
|
-
// src/lib/backup-existing-dir-before-symlink-override.ts
|
|
4373
|
-
import { promises as fs12 } from "fs";
|
|
4374
|
-
function timestamp() {
|
|
4375
|
-
const d = /* @__PURE__ */ new Date();
|
|
4376
|
-
const pad = (n) => n.toString().padStart(2, "0");
|
|
4377
|
-
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}`;
|
|
4378
|
-
}
|
|
4379
|
-
async function backupDirBeforeReplace(targetPath) {
|
|
4380
|
-
const backupPath = `${targetPath}.backup-${timestamp()}`;
|
|
4381
|
-
await fs12.rename(targetPath, backupPath);
|
|
4382
|
-
return backupPath;
|
|
4383
|
-
}
|
|
4384
|
-
|
|
4385
|
-
// src/lib/symlink-farm-for-team-pack-mount-dirs.ts
|
|
4386
|
-
var TEAM_PACK_MOUNT_DIRS = [
|
|
4387
|
-
"skills",
|
|
4388
|
-
"agents",
|
|
4389
|
-
"commands",
|
|
4390
|
-
"hooks",
|
|
4391
|
-
"workflows",
|
|
4392
|
-
"scripts",
|
|
4393
|
-
"knowledge"
|
|
4394
|
-
];
|
|
4395
|
-
async function isSymbolicLink(path) {
|
|
4396
|
-
try {
|
|
4397
|
-
const st = await fs13.lstat(path);
|
|
4398
|
-
return st.isSymbolicLink();
|
|
4399
|
-
} catch {
|
|
4400
|
-
return false;
|
|
4401
|
-
}
|
|
4402
|
-
}
|
|
4403
|
-
async function syncMountedDir(source, dest, force) {
|
|
4404
|
-
const dir = relative3(dirname5(dest), dest) || dest;
|
|
4405
|
-
if (!await pathExists(source)) {
|
|
4406
|
-
return { dir, action: "source-missing" };
|
|
4407
|
-
}
|
|
4408
|
-
if (await pathExists(dest)) {
|
|
4409
|
-
if (await isSymbolicLink(dest)) {
|
|
4410
|
-
await fs13.unlink(dest);
|
|
4411
|
-
} else if (force) {
|
|
4412
|
-
const backupPath = await backupDirBeforeReplace(dest);
|
|
4413
|
-
const relativeSource2 = relative3(dirname5(dest), source);
|
|
4414
|
-
await fs13.symlink(relativeSource2, dest);
|
|
4415
|
-
return { dir, action: "backed-up-and-linked", backupPath };
|
|
4416
|
-
} else {
|
|
4417
|
-
return { dir, action: "skipped-conflict" };
|
|
4418
|
-
}
|
|
4419
|
-
}
|
|
4420
|
-
const relativeSource = relative3(dirname5(dest), source);
|
|
4421
|
-
await fs13.symlink(relativeSource, dest);
|
|
4422
|
-
return { dir, action: "created" };
|
|
4423
|
-
}
|
|
4424
|
-
async function syncAllMountDirs(packDir, claudeDir, force) {
|
|
4425
|
-
const results = [];
|
|
4426
|
-
for (const dir of TEAM_PACK_MOUNT_DIRS) {
|
|
4427
|
-
const source = join26(packDir, dir);
|
|
4428
|
-
const dest = join26(claudeDir, dir);
|
|
4429
|
-
results.push(await syncMountedDir(source, dest, force));
|
|
4430
|
-
}
|
|
4431
|
-
return results;
|
|
4432
|
-
}
|
|
4433
|
-
|
|
4434
|
-
// src/lib/preview-team-pack-sync-changes-for-dry-run.ts
|
|
4435
4495
|
async function inspectMountDir(packDir, claudeDir, dir) {
|
|
4436
4496
|
const source = join27(packDir, dir);
|
|
4437
4497
|
const dest = join27(claudeDir, dir);
|
|
@@ -4746,7 +4806,7 @@ async function removeSubmoduleEntry(gitmodulesPath, submodulePath) {
|
|
|
4746
4806
|
}
|
|
4747
4807
|
|
|
4748
4808
|
// src/commands/uninstall.ts
|
|
4749
|
-
var CLI_VERSION = "1.6.
|
|
4809
|
+
var CLI_VERSION = "1.6.2";
|
|
4750
4810
|
function registerUninstallCommand(program2) {
|
|
4751
4811
|
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) => {
|
|
4752
4812
|
try {
|
|
@@ -4828,7 +4888,7 @@ function printUninstallSuccessBox(backupPath) {
|
|
|
4828
4888
|
}
|
|
4829
4889
|
|
|
4830
4890
|
// src/index.ts
|
|
4831
|
-
var CLI_VERSION2 = "1.6.
|
|
4891
|
+
var CLI_VERSION2 = "1.6.2";
|
|
4832
4892
|
var program = new Command();
|
|
4833
4893
|
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(
|
|
4834
4894
|
"beforeAll",
|