@nalvietnam/avatar-cli 1.3.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +161 -121
- 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
|
@@ -5,7 +5,7 @@ import { Command } from "commander";
|
|
|
5
5
|
|
|
6
6
|
// src/commands/ai.ts
|
|
7
7
|
import { promises as fs4 } from "fs";
|
|
8
|
-
import { join as
|
|
8
|
+
import { join as join6 } from "path";
|
|
9
9
|
import { confirm } from "@inquirer/prompts";
|
|
10
10
|
|
|
11
11
|
// src/lib/filesystem-helpers.ts
|
|
@@ -42,12 +42,44 @@ async function writeJsonAtomic(path, data, mode) {
|
|
|
42
42
|
`, mode);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// src/lib/resolve-avatar-workspace-root-from-cwd.ts
|
|
46
|
+
import { existsSync, readFileSync } from "fs";
|
|
47
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
48
|
+
var MAX_WALKUP_LEVELS = 5;
|
|
49
|
+
function isAvatarWorkspace(dir) {
|
|
50
|
+
const hasClaudeDir = existsSync(join2(dir, ".claude"));
|
|
51
|
+
const hasClaudeMd = existsSync(join2(dir, "CLAUDE.md"));
|
|
52
|
+
if (!hasClaudeDir || !hasClaudeMd) return false;
|
|
53
|
+
const hasSrcDir = existsSync(join2(dir, "src"));
|
|
54
|
+
const gitmodulesPath = join2(dir, ".gitmodules");
|
|
55
|
+
if (hasSrcDir) return true;
|
|
56
|
+
if (existsSync(gitmodulesPath)) {
|
|
57
|
+
try {
|
|
58
|
+
const content = readFileSync(gitmodulesPath, "utf8");
|
|
59
|
+
return content.includes("path = src") || content.includes("path = ./src");
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
function resolveAvatarWorkspaceRootFromCwd(startDir) {
|
|
67
|
+
let current = startDir;
|
|
68
|
+
for (let i = 0; i < MAX_WALKUP_LEVELS; i++) {
|
|
69
|
+
if (isAvatarWorkspace(current)) return current;
|
|
70
|
+
const parent = dirname2(current);
|
|
71
|
+
if (parent === current) return null;
|
|
72
|
+
current = parent;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
45
77
|
// src/lib/audit-log-appender.ts
|
|
46
78
|
import { promises as fs2 } from "fs";
|
|
47
79
|
|
|
48
80
|
// src/lib/user-config-store.ts
|
|
49
81
|
import { homedir } from "os";
|
|
50
|
-
import { join as
|
|
82
|
+
import { join as join3 } from "path";
|
|
51
83
|
|
|
52
84
|
// src/types/config-schema.ts
|
|
53
85
|
import { z } from "zod";
|
|
@@ -80,11 +112,11 @@ var projectSettingsSchema = z.object({
|
|
|
80
112
|
var initModeSchema = z.enum(["internal", "client", "library"]);
|
|
81
113
|
|
|
82
114
|
// src/lib/user-config-store.ts
|
|
83
|
-
var AVATAR_HOME =
|
|
84
|
-
var USER_CONFIG_PATH =
|
|
85
|
-
var USER_STATE_PATH =
|
|
86
|
-
var AUDIT_LOG_PATH =
|
|
87
|
-
var BACKUPS_DIR =
|
|
115
|
+
var AVATAR_HOME = join3(homedir(), ".avatar");
|
|
116
|
+
var USER_CONFIG_PATH = join3(AVATAR_HOME, "config.json");
|
|
117
|
+
var USER_STATE_PATH = join3(AVATAR_HOME, "state.json");
|
|
118
|
+
var AUDIT_LOG_PATH = join3(AVATAR_HOME, "audit.log");
|
|
119
|
+
var BACKUPS_DIR = join3(AVATAR_HOME, "backups");
|
|
88
120
|
var SECRET_FILE_MODE = 384;
|
|
89
121
|
async function ensureAvatarHome() {
|
|
90
122
|
await ensureDir(AVATAR_HOME);
|
|
@@ -348,18 +380,18 @@ function installClaudeCodeViaNpm() {
|
|
|
348
380
|
}
|
|
349
381
|
|
|
350
382
|
// src/lib/prompt-ai-provider-choice.ts
|
|
351
|
-
import { readFileSync } from "fs";
|
|
383
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
352
384
|
import { homedir as homedir2 } from "os";
|
|
353
|
-
import { join as
|
|
385
|
+
import { join as join4 } from "path";
|
|
354
386
|
import { select } from "@inquirer/prompts";
|
|
355
387
|
function getGlobalSettingsPath() {
|
|
356
|
-
return
|
|
388
|
+
return join4(homedir2(), ".claude", "settings.json");
|
|
357
389
|
}
|
|
358
390
|
function detectGlobalClaudeSettings() {
|
|
359
391
|
const path = getGlobalSettingsPath();
|
|
360
392
|
let raw;
|
|
361
393
|
try {
|
|
362
|
-
raw =
|
|
394
|
+
raw = readFileSync2(path, "utf8");
|
|
363
395
|
} catch {
|
|
364
396
|
return { exists: false, hasBaseUrl: false, hasToken: false };
|
|
365
397
|
}
|
|
@@ -497,10 +529,10 @@ async function setupLLMLiteApiKeyAndModel() {
|
|
|
497
529
|
|
|
498
530
|
// src/lib/write-claude-settings-json-per-project.ts
|
|
499
531
|
import { promises as fs3 } from "fs";
|
|
500
|
-
import { join as
|
|
532
|
+
import { join as join5 } from "path";
|
|
501
533
|
var SECRET_FILE_MODE2 = 384;
|
|
502
534
|
function getClaudeSettingsPath(workspacePath) {
|
|
503
|
-
return
|
|
535
|
+
return join5(workspacePath, ".claude", "settings.json");
|
|
504
536
|
}
|
|
505
537
|
async function readExistingSettings(path) {
|
|
506
538
|
if (!await pathExists(path)) return {};
|
|
@@ -752,15 +784,23 @@ async function testAiProviderByDetectedMode(settings) {
|
|
|
752
784
|
// src/commands/ai.ts
|
|
753
785
|
async function ensureWorkspaceCwd() {
|
|
754
786
|
const cwd = process.cwd();
|
|
755
|
-
const
|
|
756
|
-
if (!
|
|
757
|
-
log.error(
|
|
787
|
+
const workspaceRoot = resolveAvatarWorkspaceRootFromCwd(cwd);
|
|
788
|
+
if (!workspaceRoot) {
|
|
789
|
+
log.error(
|
|
790
|
+
`Kh\xF4ng t\xECm th\u1EA5y Avatar workspace t\u1EEB th\u01B0 m\u1EE5c hi\u1EC7n t\u1EA1i.
|
|
791
|
+
Avatar workspace c\u1EA7n c\xF3: .claude/ + CLAUDE.md + src/ (ho\u1EB7c .gitmodules).
|
|
792
|
+
B\u1EA1n \u0111ang \u1EDF: ${cwd}
|
|
793
|
+
Cd v\xE0o workspace dir (vd /path/to/<project>-workspace) r\u1ED3i ch\u1EA1y l\u1EA1i.`
|
|
794
|
+
);
|
|
758
795
|
process.exit(1);
|
|
759
796
|
}
|
|
760
|
-
|
|
797
|
+
if (workspaceRoot !== cwd) {
|
|
798
|
+
log.dim(`Detected workspace root: ${workspaceRoot}`);
|
|
799
|
+
}
|
|
800
|
+
return workspaceRoot;
|
|
761
801
|
}
|
|
762
802
|
async function readWorkspaceSettings(workspacePath) {
|
|
763
|
-
const settingsPath =
|
|
803
|
+
const settingsPath = join6(workspacePath, ".claude", "settings.json");
|
|
764
804
|
if (!await pathExists(settingsPath)) return {};
|
|
765
805
|
try {
|
|
766
806
|
return await readJson(settingsPath);
|
|
@@ -798,7 +838,7 @@ async function runAiTest() {
|
|
|
798
838
|
}
|
|
799
839
|
async function runAiReset(opts) {
|
|
800
840
|
const workspacePath = await ensureWorkspaceCwd();
|
|
801
|
-
const settingsPath =
|
|
841
|
+
const settingsPath = join6(workspacePath, ".claude", "settings.json");
|
|
802
842
|
const settings = await readWorkspaceSettings(workspacePath);
|
|
803
843
|
if (!opts.yes) {
|
|
804
844
|
const ok = await confirm({
|
|
@@ -848,25 +888,25 @@ import { input as input2 } from "@inquirer/prompts";
|
|
|
848
888
|
|
|
849
889
|
// src/lib/execute-commit-with-target-selection.ts
|
|
850
890
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
851
|
-
import { existsSync } from "fs";
|
|
852
|
-
import { join as
|
|
891
|
+
import { existsSync as existsSync2 } from "fs";
|
|
892
|
+
import { join as join7 } from "path";
|
|
853
893
|
function assertAvatarWorkspaceRoot(cwd) {
|
|
854
|
-
const srcGit =
|
|
855
|
-
const workspaceGit =
|
|
856
|
-
const claudeDir =
|
|
857
|
-
if (!
|
|
894
|
+
const srcGit = join7(cwd, "src", ".git");
|
|
895
|
+
const workspaceGit = join7(cwd, ".git");
|
|
896
|
+
const claudeDir = join7(cwd, ".claude");
|
|
897
|
+
if (!existsSync2(workspaceGit)) {
|
|
858
898
|
throw new Error(
|
|
859
899
|
`Kh\xF4ng ph\u1EA3i workspace root: ${cwd}
|
|
860
900
|
Ch\u1EA1y 'avatar commit' trong workspace dir (c\xF3 .git v\xE0 .claude/).`
|
|
861
901
|
);
|
|
862
902
|
}
|
|
863
|
-
if (!
|
|
903
|
+
if (!existsSync2(claudeDir)) {
|
|
864
904
|
throw new Error(
|
|
865
905
|
`Kh\xF4ng th\u1EA5y .claude/ trong ${cwd}.
|
|
866
906
|
Ch\u1EA1y 'avatar commit' trong Avatar workspace, kh\xF4ng ph\u1EA3i project b\xECnh th\u01B0\u1EDDng.`
|
|
867
907
|
);
|
|
868
908
|
}
|
|
869
|
-
if (!
|
|
909
|
+
if (!existsSync2(srcGit)) {
|
|
870
910
|
throw new Error(
|
|
871
911
|
`Kh\xF4ng th\u1EA5y src/.git trong ${cwd}.
|
|
872
912
|
Workspace thi\u1EBFu submodule src/. Ch\u1EA1y 'avatar init' l\u1EA1i?`
|
|
@@ -890,7 +930,7 @@ function isDirty(cwd) {
|
|
|
890
930
|
return status.length > 0;
|
|
891
931
|
}
|
|
892
932
|
async function commitSrc(workspaceRoot, opts) {
|
|
893
|
-
const srcPath =
|
|
933
|
+
const srcPath = join7(workspaceRoot, "src");
|
|
894
934
|
if (!isDirty(srcPath)) {
|
|
895
935
|
log.dim("src/: nothing to commit (clean)");
|
|
896
936
|
return {};
|
|
@@ -930,7 +970,7 @@ async function commitWorkspace(workspaceRoot, opts) {
|
|
|
930
970
|
}
|
|
931
971
|
function warnIfOtherTargetDirty(workspaceRoot, requestedTarget) {
|
|
932
972
|
if (requestedTarget === "all") return;
|
|
933
|
-
const srcDirty = isDirty(
|
|
973
|
+
const srcDirty = isDirty(join7(workspaceRoot, "src"));
|
|
934
974
|
const workspaceDirty = isDirty(workspaceRoot);
|
|
935
975
|
if (requestedTarget === "src" && workspaceDirty) {
|
|
936
976
|
log.warn(
|
|
@@ -1006,23 +1046,23 @@ async function runCommit(target, opts) {
|
|
|
1006
1046
|
// src/commands/doctor.ts
|
|
1007
1047
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
1008
1048
|
import { promises as fs6 } from "fs";
|
|
1009
|
-
import { join as
|
|
1049
|
+
import { join as join11 } from "path";
|
|
1010
1050
|
import boxen from "boxen";
|
|
1011
1051
|
|
|
1012
1052
|
// src/lib/git-operations.ts
|
|
1013
|
-
import { join as
|
|
1053
|
+
import { join as join8 } from "path";
|
|
1014
1054
|
import { simpleGit } from "simple-git";
|
|
1015
1055
|
function git(cwd = process.cwd()) {
|
|
1016
1056
|
return simpleGit({ baseDir: cwd, binary: "git" });
|
|
1017
1057
|
}
|
|
1018
1058
|
async function isGitRepo(cwd = process.cwd()) {
|
|
1019
|
-
return await pathExists(
|
|
1059
|
+
return await pathExists(join8(cwd, ".git"));
|
|
1020
1060
|
}
|
|
1021
1061
|
async function addSubmodule(repoUrl, destPath, cwd = process.cwd()) {
|
|
1022
1062
|
await git(cwd).subModule(["add", repoUrl, destPath]);
|
|
1023
1063
|
}
|
|
1024
1064
|
async function checkoutTagInSubmodule(submodulePath, tag, cwd = process.cwd()) {
|
|
1025
|
-
const submoduleCwd =
|
|
1065
|
+
const submoduleCwd = join8(cwd, submodulePath);
|
|
1026
1066
|
await git(submoduleCwd).fetch(["--tags"]);
|
|
1027
1067
|
await git(submoduleCwd).checkout(tag);
|
|
1028
1068
|
}
|
|
@@ -1041,11 +1081,11 @@ async function currentCommitSha(cwd = process.cwd()) {
|
|
|
1041
1081
|
|
|
1042
1082
|
// src/lib/project-tree-scaffolder.ts
|
|
1043
1083
|
import { promises as fs5 } from "fs";
|
|
1044
|
-
import { join as
|
|
1084
|
+
import { join as join10 } from "path";
|
|
1045
1085
|
|
|
1046
1086
|
// src/lib/template-bundle-loader.ts
|
|
1047
|
-
import { existsSync as
|
|
1048
|
-
import { dirname as
|
|
1087
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1088
|
+
import { dirname as dirname3, join as join9 } from "path";
|
|
1049
1089
|
import { fileURLToPath } from "url";
|
|
1050
1090
|
|
|
1051
1091
|
// src/lib/mustache-template-engine.ts
|
|
@@ -1059,15 +1099,15 @@ function renderTemplate(source, variables) {
|
|
|
1059
1099
|
}
|
|
1060
1100
|
|
|
1061
1101
|
// src/lib/template-bundle-loader.ts
|
|
1062
|
-
var HERE =
|
|
1102
|
+
var HERE = dirname3(fileURLToPath(import.meta.url));
|
|
1063
1103
|
var PACKAGE_ROOT = findPackageRoot(HERE);
|
|
1064
|
-
var TEMPLATES_ROOT =
|
|
1065
|
-
var HOOKS_ROOT =
|
|
1104
|
+
var TEMPLATES_ROOT = join9(PACKAGE_ROOT, "src", "templates");
|
|
1105
|
+
var HOOKS_ROOT = join9(PACKAGE_ROOT, "src", "hooks");
|
|
1066
1106
|
function findPackageRoot(startDir) {
|
|
1067
1107
|
let dir = startDir;
|
|
1068
1108
|
while (true) {
|
|
1069
|
-
if (
|
|
1070
|
-
const parent =
|
|
1109
|
+
if (existsSync3(join9(dir, "package.json"))) return dir;
|
|
1110
|
+
const parent = dirname3(dir);
|
|
1071
1111
|
if (parent === dir) {
|
|
1072
1112
|
throw new Error(`Cannot locate package root from ${startDir}`);
|
|
1073
1113
|
}
|
|
@@ -1075,14 +1115,14 @@ function findPackageRoot(startDir) {
|
|
|
1075
1115
|
}
|
|
1076
1116
|
}
|
|
1077
1117
|
async function loadTemplate(name) {
|
|
1078
|
-
return await readText(
|
|
1118
|
+
return await readText(join9(TEMPLATES_ROOT, `${name}.tpl`));
|
|
1079
1119
|
}
|
|
1080
1120
|
async function renderTemplateByName(name, variables) {
|
|
1081
1121
|
const source = await loadTemplate(name);
|
|
1082
1122
|
return renderTemplate(source, variables);
|
|
1083
1123
|
}
|
|
1084
1124
|
async function loadHook(name) {
|
|
1085
|
-
return await readText(
|
|
1125
|
+
return await readText(join9(HOOKS_ROOT, `${name}.sh.tpl`));
|
|
1086
1126
|
}
|
|
1087
1127
|
|
|
1088
1128
|
// src/lib/project-tree-scaffolder.ts
|
|
@@ -1116,12 +1156,12 @@ var PROJECT_KNOWLEDGE_TEMPLATES = [
|
|
|
1116
1156
|
"project/gotchas.md"
|
|
1117
1157
|
];
|
|
1118
1158
|
async function createClaudeDirTree(projectRoot) {
|
|
1119
|
-
const claudeRoot =
|
|
1159
|
+
const claudeRoot = join10(projectRoot, ".claude");
|
|
1120
1160
|
await ensureDir(claudeRoot);
|
|
1121
1161
|
for (const sub of CLAUDE_SUBDIRS) {
|
|
1122
|
-
const dir =
|
|
1162
|
+
const dir = join10(claudeRoot, sub);
|
|
1123
1163
|
await ensureDir(dir);
|
|
1124
|
-
await writeTextAtomic(
|
|
1164
|
+
await writeTextAtomic(join10(dir, ".gitkeep"), "");
|
|
1125
1165
|
}
|
|
1126
1166
|
}
|
|
1127
1167
|
async function writeProjectKnowledgeFiles(projectRoot, vars) {
|
|
@@ -1152,7 +1192,7 @@ async function writeProjectKnowledgeFiles(projectRoot, vars) {
|
|
|
1152
1192
|
for (const tpl of PROJECT_KNOWLEDGE_TEMPLATES) {
|
|
1153
1193
|
const content = await renderTemplateByName(tpl, baseVars);
|
|
1154
1194
|
const relative4 = tpl.replace(/^project\//, "");
|
|
1155
|
-
const outPath =
|
|
1195
|
+
const outPath = join10(projectRoot, ".claude", "project", relative4);
|
|
1156
1196
|
const backup = await writeWithBackup(outPath, content);
|
|
1157
1197
|
if (backup) backups.push(backup);
|
|
1158
1198
|
}
|
|
@@ -1160,14 +1200,14 @@ async function writeProjectKnowledgeFiles(projectRoot, vars) {
|
|
|
1160
1200
|
}
|
|
1161
1201
|
async function writeRootClaudeMd(projectRoot, vars) {
|
|
1162
1202
|
const content = await renderTemplateByName("CLAUDE.md", vars);
|
|
1163
|
-
return await writeWithBackup(
|
|
1203
|
+
return await writeWithBackup(join10(projectRoot, "CLAUDE.md"), content);
|
|
1164
1204
|
}
|
|
1165
1205
|
async function writeProjectSettings(projectRoot, vars) {
|
|
1166
1206
|
const content = await renderTemplateByName("settings.json", vars);
|
|
1167
|
-
return await writeWithBackup(
|
|
1207
|
+
return await writeWithBackup(join10(projectRoot, ".claude", "settings.json"), content);
|
|
1168
1208
|
}
|
|
1169
1209
|
async function appendGitignoreEntries(projectRoot) {
|
|
1170
|
-
const path =
|
|
1210
|
+
const path = join10(projectRoot, ".gitignore");
|
|
1171
1211
|
const tpl = await renderTemplateByName("gitignore", {});
|
|
1172
1212
|
const marker = "# Avatar \u2014 git-ignored entries injected on `avatar init`";
|
|
1173
1213
|
let existing = "";
|
|
@@ -1181,9 +1221,9 @@ ${tpl}`);
|
|
|
1181
1221
|
}
|
|
1182
1222
|
async function installGitHook(gitDir, hookName) {
|
|
1183
1223
|
const content = await loadHook(hookName);
|
|
1184
|
-
const hooksDir =
|
|
1224
|
+
const hooksDir = join10(gitDir, "hooks");
|
|
1185
1225
|
await ensureDir(hooksDir);
|
|
1186
|
-
const dest =
|
|
1226
|
+
const dest = join10(hooksDir, hookName);
|
|
1187
1227
|
await writeTextAtomic(dest, content, 493);
|
|
1188
1228
|
}
|
|
1189
1229
|
|
|
@@ -1241,7 +1281,7 @@ async function runChecks(cwd) {
|
|
|
1241
1281
|
detail: gitRepo ? cwd : "Kh\xF4ng ph\u1EA3i git repo (c\u1EA7n cho 'avatar init')",
|
|
1242
1282
|
fixable: false
|
|
1243
1283
|
});
|
|
1244
|
-
const packPath =
|
|
1284
|
+
const packPath = join11(cwd, ".claude", "pack");
|
|
1245
1285
|
const hasPack = await pathExists(packPath);
|
|
1246
1286
|
checks.push({
|
|
1247
1287
|
name: "team-ai-pack submodule",
|
|
@@ -1249,7 +1289,7 @@ async function runChecks(cwd) {
|
|
|
1249
1289
|
detail: hasPack ? packPath : "Avatar ch\u01B0a init \u2014 ch\u1EA1y 'avatar init'",
|
|
1250
1290
|
fixable: false
|
|
1251
1291
|
});
|
|
1252
|
-
const claudeMdPath =
|
|
1292
|
+
const claudeMdPath = join11(cwd, "CLAUDE.md");
|
|
1253
1293
|
const hasClaudeMd = await pathExists(claudeMdPath);
|
|
1254
1294
|
checks.push({
|
|
1255
1295
|
name: "CLAUDE.md",
|
|
@@ -1257,7 +1297,7 @@ async function runChecks(cwd) {
|
|
|
1257
1297
|
detail: hasClaudeMd ? "t\u1ED3n t\u1EA1i \u1EDF project root" : "thi\u1EBFu \u2014 ch\u1EA1y 'avatar init'",
|
|
1258
1298
|
fixable: false
|
|
1259
1299
|
});
|
|
1260
|
-
const hookPath =
|
|
1300
|
+
const hookPath = join11(cwd, ".git", "hooks", "post-merge");
|
|
1261
1301
|
const hasHook = await pathExists(hookPath);
|
|
1262
1302
|
if (gitRepo && hasPack) {
|
|
1263
1303
|
checks.push({
|
|
@@ -1266,11 +1306,11 @@ async function runChecks(cwd) {
|
|
|
1266
1306
|
detail: hasHook ? "installed" : "missing \u2014 fixable",
|
|
1267
1307
|
fixable: !hasHook,
|
|
1268
1308
|
fix: hasHook ? void 0 : async () => {
|
|
1269
|
-
await installGitHook(
|
|
1309
|
+
await installGitHook(join11(cwd, ".git"), "post-merge");
|
|
1270
1310
|
}
|
|
1271
1311
|
});
|
|
1272
1312
|
}
|
|
1273
|
-
const gitignorePath =
|
|
1313
|
+
const gitignorePath = join11(cwd, ".gitignore");
|
|
1274
1314
|
if (gitRepo) {
|
|
1275
1315
|
let gitignoreOk = false;
|
|
1276
1316
|
if (await pathExists(gitignorePath)) {
|
|
@@ -1332,7 +1372,7 @@ async function applyFixes(checks) {
|
|
|
1332
1372
|
}
|
|
1333
1373
|
|
|
1334
1374
|
// src/commands/init.ts
|
|
1335
|
-
import { basename, join as
|
|
1375
|
+
import { basename, join as join18, relative as relative2, resolve } from "path";
|
|
1336
1376
|
import { confirm as confirm3, input as input5, select as select8 } from "@inquirer/prompts";
|
|
1337
1377
|
import boxen4 from "boxen";
|
|
1338
1378
|
|
|
@@ -1365,7 +1405,7 @@ async function promptRetryOrSkip(args) {
|
|
|
1365
1405
|
}
|
|
1366
1406
|
|
|
1367
1407
|
// src/lib/team-pack-submodule-manager.ts
|
|
1368
|
-
import { join as
|
|
1408
|
+
import { join as join12 } from "path";
|
|
1369
1409
|
|
|
1370
1410
|
// src/lib/check-team-pack-access-with-retry-loop.ts
|
|
1371
1411
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
@@ -1530,7 +1570,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
|
|
|
1530
1570
|
}
|
|
1531
1571
|
let target = tag ?? null;
|
|
1532
1572
|
if (!target) {
|
|
1533
|
-
target = await latestTag(
|
|
1573
|
+
target = await latestTag(join12(projectRoot, TEAM_PACK_RELATIVE_PATH));
|
|
1534
1574
|
}
|
|
1535
1575
|
if (target) {
|
|
1536
1576
|
await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, target, projectRoot);
|
|
@@ -1538,7 +1578,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
|
|
|
1538
1578
|
return { pinnedTag: target };
|
|
1539
1579
|
}
|
|
1540
1580
|
async function readPinnedPackVersion(projectRoot) {
|
|
1541
|
-
const submoduleRoot =
|
|
1581
|
+
const submoduleRoot = join12(projectRoot, TEAM_PACK_RELATIVE_PATH);
|
|
1542
1582
|
const tag = await latestTag(submoduleRoot);
|
|
1543
1583
|
if (tag) return tag;
|
|
1544
1584
|
const sha = await currentCommitSha(submoduleRoot);
|
|
@@ -2225,11 +2265,11 @@ import { select as select7 } from "@inquirer/prompts";
|
|
|
2225
2265
|
import { simpleGit as simpleGit3 } from "simple-git";
|
|
2226
2266
|
|
|
2227
2267
|
// src/lib/check-folder-has-git.ts
|
|
2228
|
-
import { existsSync as
|
|
2229
|
-
import { join as
|
|
2268
|
+
import { existsSync as existsSync4, statSync } from "fs";
|
|
2269
|
+
import { join as join13 } from "path";
|
|
2230
2270
|
function checkFolderHasGit(folderPath) {
|
|
2231
|
-
const gitPath =
|
|
2232
|
-
if (!
|
|
2271
|
+
const gitPath = join13(folderPath, ".git");
|
|
2272
|
+
if (!existsSync4(gitPath)) return false;
|
|
2233
2273
|
const stat = statSync(gitPath);
|
|
2234
2274
|
return stat.isDirectory() || stat.isFile();
|
|
2235
2275
|
}
|
|
@@ -2259,8 +2299,8 @@ async function createInitialGitCommit(folderPath) {
|
|
|
2259
2299
|
}
|
|
2260
2300
|
|
|
2261
2301
|
// src/lib/detect-folder-tech-stack.ts
|
|
2262
|
-
import { existsSync as
|
|
2263
|
-
import { join as
|
|
2302
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2303
|
+
import { join as join14 } from "path";
|
|
2264
2304
|
var SIGNATURES = {
|
|
2265
2305
|
node: ["package.json"],
|
|
2266
2306
|
python: ["pyproject.toml", "requirements.txt", "setup.py", "Pipfile"],
|
|
@@ -2272,7 +2312,7 @@ var SIGNATURES = {
|
|
|
2272
2312
|
function detectFolderTechStack(folderPath) {
|
|
2273
2313
|
const matched = [];
|
|
2274
2314
|
for (const [stack, files] of Object.entries(SIGNATURES)) {
|
|
2275
|
-
if (files.some((f) =>
|
|
2315
|
+
if (files.some((f) => existsSync5(join14(folderPath, f)))) {
|
|
2276
2316
|
matched.push(stack);
|
|
2277
2317
|
}
|
|
2278
2318
|
}
|
|
@@ -2280,26 +2320,26 @@ function detectFolderTechStack(folderPath) {
|
|
|
2280
2320
|
}
|
|
2281
2321
|
|
|
2282
2322
|
// src/lib/gitignore-template-loader.ts
|
|
2283
|
-
import { readFileSync as
|
|
2284
|
-
import { dirname as
|
|
2323
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2324
|
+
import { dirname as dirname4, join as join15 } from "path";
|
|
2285
2325
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2286
|
-
var __dirname =
|
|
2326
|
+
var __dirname = dirname4(fileURLToPath2(import.meta.url));
|
|
2287
2327
|
var CANDIDATE_DIRS = [
|
|
2288
2328
|
// Bundled production: dist/index.js → __dirname = .../dist/, sibling dist/templates
|
|
2289
|
-
|
|
2329
|
+
join15(__dirname, "templates", "gitignore"),
|
|
2290
2330
|
// Legacy bundled: nếu file là dist/lib/*.js (sub-bundle), templates ở dist/templates
|
|
2291
|
-
|
|
2331
|
+
join15(__dirname, "..", "templates", "gitignore"),
|
|
2292
2332
|
// Dev mode (vitest/tsx run src/ trực tiếp): __dirname = src/lib/
|
|
2293
|
-
|
|
2333
|
+
join15(__dirname, "..", "..", "src", "templates", "gitignore"),
|
|
2294
2334
|
// npm-installed alt: __dirname = .../dist/ → package_root/src/templates
|
|
2295
|
-
|
|
2335
|
+
join15(__dirname, "..", "src", "templates", "gitignore")
|
|
2296
2336
|
];
|
|
2297
2337
|
var AVATAR_MARKER_START = "# === avatar ===";
|
|
2298
2338
|
var AVATAR_MARKER_END = "# === /avatar ===";
|
|
2299
2339
|
function readTemplate(stack) {
|
|
2300
2340
|
for (const dir of CANDIDATE_DIRS) {
|
|
2301
2341
|
try {
|
|
2302
|
-
return
|
|
2342
|
+
return readFileSync3(join15(dir, `${stack}.txt`), "utf8");
|
|
2303
2343
|
} catch {
|
|
2304
2344
|
}
|
|
2305
2345
|
}
|
|
@@ -2313,15 +2353,15 @@ ${readTemplate(s).trim()}`);
|
|
|
2313
2353
|
}
|
|
2314
2354
|
|
|
2315
2355
|
// src/lib/write-or-merge-gitignore.ts
|
|
2316
|
-
import { existsSync as
|
|
2317
|
-
import { join as
|
|
2356
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
2357
|
+
import { join as join16 } from "path";
|
|
2318
2358
|
function writeOrMergeGitignore(folderPath, avatarBlock) {
|
|
2319
|
-
const path =
|
|
2320
|
-
if (!
|
|
2359
|
+
const path = join16(folderPath, ".gitignore");
|
|
2360
|
+
if (!existsSync6(path)) {
|
|
2321
2361
|
writeFileSync(path, avatarBlock, "utf8");
|
|
2322
2362
|
return;
|
|
2323
2363
|
}
|
|
2324
|
-
const existing =
|
|
2364
|
+
const existing = readFileSync4(path, "utf8");
|
|
2325
2365
|
const startIdx = existing.indexOf(AVATAR_MARKER_START);
|
|
2326
2366
|
const endIdx = existing.indexOf(AVATAR_MARKER_END);
|
|
2327
2367
|
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
@@ -2493,7 +2533,7 @@ async function safeBootstrapGitInFolder(folderPath, opts = {}) {
|
|
|
2493
2533
|
|
|
2494
2534
|
// src/commands/init-conflict-detection-helpers.ts
|
|
2495
2535
|
import { readdir } from "fs/promises";
|
|
2496
|
-
import { join as
|
|
2536
|
+
import { join as join17 } from "path";
|
|
2497
2537
|
async function isEmptyOrMissing(path) {
|
|
2498
2538
|
if (!await pathExists(path)) return true;
|
|
2499
2539
|
try {
|
|
@@ -2506,7 +2546,7 @@ async function isEmptyOrMissing(path) {
|
|
|
2506
2546
|
}
|
|
2507
2547
|
async function findAlternativeWorkspaceName(parent, desiredName, maxAttempts = 10) {
|
|
2508
2548
|
for (let i = 2; i < maxAttempts; i++) {
|
|
2509
|
-
const candidate =
|
|
2549
|
+
const candidate = join17(parent, `${desiredName}-${i}`);
|
|
2510
2550
|
if (await isEmptyOrMissing(candidate)) return candidate;
|
|
2511
2551
|
}
|
|
2512
2552
|
return null;
|
|
@@ -2898,7 +2938,7 @@ async function runInitFromScratch(opts, ownerEmail) {
|
|
|
2898
2938
|
const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
|
|
2899
2939
|
const workspaceParent = resolve(opts.workspaceParent ?? ".");
|
|
2900
2940
|
const workspacePath = await resolveWorkspacePath(workspaceParent, projectName, opts.force);
|
|
2901
|
-
const srcPath =
|
|
2941
|
+
const srcPath = join18(workspacePath, "src");
|
|
2902
2942
|
await ensureDir(workspacePath);
|
|
2903
2943
|
await ensureDir(srcPath);
|
|
2904
2944
|
await safeBootstrapGitInFolder(srcPath, { autoYes: true });
|
|
@@ -3034,10 +3074,10 @@ async function finalizeWorkspaceScaffold(args) {
|
|
|
3034
3074
|
await writeRootClaudeMd(args.workspacePath, vars);
|
|
3035
3075
|
await writeProjectSettings(args.workspacePath, vars);
|
|
3036
3076
|
await appendGitignoreEntries(args.workspacePath);
|
|
3037
|
-
await ensureDir(
|
|
3038
|
-
await ensureDir(
|
|
3039
|
-
await installGitHook(
|
|
3040
|
-
await installGitHook(
|
|
3077
|
+
await ensureDir(join18(args.workspacePath, "notes"));
|
|
3078
|
+
await ensureDir(join18(args.workspacePath, "scripts"));
|
|
3079
|
+
await installGitHook(join18(args.workspacePath, ".git"), "post-merge");
|
|
3080
|
+
await installGitHook(join18(args.workspacePath, ".git", "modules", "src"), "pre-push");
|
|
3041
3081
|
log.success("C\xE0i post-merge (workspace) + pre-push (src/)");
|
|
3042
3082
|
await appendAuditEntry("init", `flow=${args.flow},workspace=${args.workspaceName}`);
|
|
3043
3083
|
await maybeCommitWorkspace(args.workspacePath, args.skipCommit);
|
|
@@ -3137,7 +3177,7 @@ async function maybeCreateWorkspaceRemote(args) {
|
|
|
3137
3177
|
}
|
|
3138
3178
|
}
|
|
3139
3179
|
async function resolveWorkspacePath(parent, desiredName, force) {
|
|
3140
|
-
const desired =
|
|
3180
|
+
const desired = join18(parent, desiredName);
|
|
3141
3181
|
if (await isEmptyOrMissing(desired)) return desired;
|
|
3142
3182
|
log.warn(`Workspace path "${desired}" \u0111\xE3 c\xF3 n\u1ED9i dung.`);
|
|
3143
3183
|
while (true) {
|
|
@@ -3168,7 +3208,7 @@ async function resolveWorkspacePath(parent, desiredName, force) {
|
|
|
3168
3208
|
message: "T\xEAn workspace m\u1EDBi:",
|
|
3169
3209
|
validate: (v) => v.trim().length > 0 ? true : "T\xEAn kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng"
|
|
3170
3210
|
});
|
|
3171
|
-
const newPath =
|
|
3211
|
+
const newPath = join18(parent, newName.trim());
|
|
3172
3212
|
if (await isEmptyOrMissing(newPath)) return newPath;
|
|
3173
3213
|
log.warn(`"${newPath}" c\u0169ng \u0111\xE3 c\xF3 n\u1ED9i dung. Th\u1EED t\xEAn kh\xE1c.`);
|
|
3174
3214
|
}
|
|
@@ -3262,15 +3302,15 @@ function registerSecretsCommand(program2) {
|
|
|
3262
3302
|
|
|
3263
3303
|
// src/commands/status.ts
|
|
3264
3304
|
import { promises as fs8 } from "fs";
|
|
3265
|
-
import { join as
|
|
3305
|
+
import { join as join20 } from "path";
|
|
3266
3306
|
import boxen5 from "boxen";
|
|
3267
3307
|
|
|
3268
3308
|
// src/lib/pack-backup-manager.ts
|
|
3269
3309
|
import { promises as fs7 } from "fs";
|
|
3270
|
-
import { join as
|
|
3310
|
+
import { join as join19 } from "path";
|
|
3271
3311
|
var BACKUP_DIR_NAME = "_backup";
|
|
3272
3312
|
async function listBackups(projectRoot) {
|
|
3273
|
-
const dir =
|
|
3313
|
+
const dir = join19(projectRoot, ".claude", BACKUP_DIR_NAME);
|
|
3274
3314
|
if (!await pathExists(dir)) return [];
|
|
3275
3315
|
const entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
3276
3316
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort().reverse();
|
|
@@ -3296,7 +3336,7 @@ function registerStatusCommand(program2) {
|
|
|
3296
3336
|
}
|
|
3297
3337
|
async function gatherStatus(cwd) {
|
|
3298
3338
|
const projectName = cwd.split("/").filter(Boolean).pop() ?? "unknown";
|
|
3299
|
-
const claudeRoot =
|
|
3339
|
+
const claudeRoot = join20(cwd, ".claude");
|
|
3300
3340
|
const hasAvatar = await pathExists(claudeRoot);
|
|
3301
3341
|
if (!hasAvatar) {
|
|
3302
3342
|
return {
|
|
@@ -3309,8 +3349,8 @@ async function gatherStatus(cwd) {
|
|
|
3309
3349
|
hasAvatar: false
|
|
3310
3350
|
};
|
|
3311
3351
|
}
|
|
3312
|
-
const packVersion = await isGitRepo(
|
|
3313
|
-
const pendingDir =
|
|
3352
|
+
const packVersion = await isGitRepo(join20(claudeRoot, "pack")) ? await readPinnedPackVersion(cwd).catch(() => null) : null;
|
|
3353
|
+
const pendingDir = join20(claudeRoot, "_pending");
|
|
3314
3354
|
const pendingCount = await pathExists(pendingDir) ? (await fs8.readdir(pendingDir)).filter((n) => n.endsWith(".diff.md")).length : 0;
|
|
3315
3355
|
const backupCount = (await listBackups(cwd)).length;
|
|
3316
3356
|
const techStackSummary = await readTechStackFirstLine(claudeRoot);
|
|
@@ -3325,7 +3365,7 @@ async function gatherStatus(cwd) {
|
|
|
3325
3365
|
};
|
|
3326
3366
|
}
|
|
3327
3367
|
async function readTechStackFirstLine(claudeRoot) {
|
|
3328
|
-
const techStackPath =
|
|
3368
|
+
const techStackPath = join20(claudeRoot, "project", "tech-stack.md");
|
|
3329
3369
|
if (!await pathExists(techStackPath)) return "(no tech-stack.md)";
|
|
3330
3370
|
const content = await readText(techStackPath);
|
|
3331
3371
|
const firstNonHeaderLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#") && !l.startsWith(">"));
|
|
@@ -3366,27 +3406,27 @@ import boxen6 from "boxen";
|
|
|
3366
3406
|
// src/lib/create-uninstall-backup-snapshot.ts
|
|
3367
3407
|
import { cp, mkdir, writeFile } from "fs/promises";
|
|
3368
3408
|
import { homedir as homedir3 } from "os";
|
|
3369
|
-
import { basename as basename2, join as
|
|
3370
|
-
var UNINSTALL_BACKUPS_DIR =
|
|
3409
|
+
import { basename as basename2, join as join21 } from "path";
|
|
3410
|
+
var UNINSTALL_BACKUPS_DIR = join21(homedir3(), ".avatar", "uninstall-backups");
|
|
3371
3411
|
async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersion) {
|
|
3372
3412
|
const projectName = basename2(projectRoot);
|
|
3373
3413
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3374
|
-
const backupDir =
|
|
3414
|
+
const backupDir = join21(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp}`);
|
|
3375
3415
|
await mkdir(backupDir, { recursive: true, mode: 448 });
|
|
3376
3416
|
if (artifacts.claudeDir) {
|
|
3377
|
-
await cp(artifacts.claudeDir,
|
|
3417
|
+
await cp(artifacts.claudeDir, join21(backupDir, ".claude"), { recursive: true });
|
|
3378
3418
|
}
|
|
3379
3419
|
if (artifacts.claudeMd) {
|
|
3380
|
-
await cp(artifacts.claudeMd,
|
|
3420
|
+
await cp(artifacts.claudeMd, join21(backupDir, "CLAUDE.md"));
|
|
3381
3421
|
}
|
|
3382
3422
|
if (artifacts.postMergeHook || artifacts.prePushHook) {
|
|
3383
|
-
const hooksBackupDir =
|
|
3423
|
+
const hooksBackupDir = join21(backupDir, "hooks");
|
|
3384
3424
|
await mkdir(hooksBackupDir, { recursive: true });
|
|
3385
3425
|
if (artifacts.postMergeHook) {
|
|
3386
|
-
await cp(artifacts.postMergeHook,
|
|
3426
|
+
await cp(artifacts.postMergeHook, join21(hooksBackupDir, "post-merge"));
|
|
3387
3427
|
}
|
|
3388
3428
|
if (artifacts.prePushHook) {
|
|
3389
|
-
await cp(artifacts.prePushHook,
|
|
3429
|
+
await cp(artifacts.prePushHook, join21(hooksBackupDir, "pre-push"));
|
|
3390
3430
|
}
|
|
3391
3431
|
}
|
|
3392
3432
|
const manifest = {
|
|
@@ -3401,27 +3441,27 @@ async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersi
|
|
|
3401
3441
|
prePushHook: !!artifacts.prePushHook
|
|
3402
3442
|
}
|
|
3403
3443
|
};
|
|
3404
|
-
await writeFile(
|
|
3444
|
+
await writeFile(join21(backupDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
|
|
3405
3445
|
return backupDir;
|
|
3406
3446
|
}
|
|
3407
3447
|
|
|
3408
3448
|
// src/lib/detect-avatar-project-artifacts.ts
|
|
3409
|
-
import { existsSync as
|
|
3410
|
-
import { join as
|
|
3449
|
+
import { existsSync as existsSync7 } from "fs";
|
|
3450
|
+
import { join as join22 } from "path";
|
|
3411
3451
|
function existsOrNull(path) {
|
|
3412
|
-
return
|
|
3452
|
+
return existsSync7(path) ? path : null;
|
|
3413
3453
|
}
|
|
3414
3454
|
function detectAvatarProjectArtifacts(projectRoot) {
|
|
3415
|
-
const claudeDir = existsOrNull(
|
|
3416
|
-
const claudeMd = existsOrNull(
|
|
3417
|
-
const postMergeHook = existsOrNull(
|
|
3455
|
+
const claudeDir = existsOrNull(join22(projectRoot, ".claude"));
|
|
3456
|
+
const claudeMd = existsOrNull(join22(projectRoot, "CLAUDE.md"));
|
|
3457
|
+
const postMergeHook = existsOrNull(join22(projectRoot, ".git", "hooks", "post-merge"));
|
|
3418
3458
|
const prePushHook = existsOrNull(
|
|
3419
|
-
|
|
3459
|
+
join22(projectRoot, ".git", "modules", "src", "hooks", "pre-push")
|
|
3420
3460
|
);
|
|
3421
|
-
const gitignorePath = existsOrNull(
|
|
3422
|
-
const gitmodulesPath = existsOrNull(
|
|
3423
|
-
const notesDir = existsOrNull(
|
|
3424
|
-
const scriptsDir = existsOrNull(
|
|
3461
|
+
const gitignorePath = existsOrNull(join22(projectRoot, ".gitignore"));
|
|
3462
|
+
const gitmodulesPath = existsOrNull(join22(projectRoot, ".gitmodules"));
|
|
3463
|
+
const notesDir = existsOrNull(join22(projectRoot, "notes"));
|
|
3464
|
+
const scriptsDir = existsOrNull(join22(projectRoot, "scripts"));
|
|
3425
3465
|
const hasAnyArtifact = !!(claudeDir || claudeMd || postMergeHook || prePushHook);
|
|
3426
3466
|
return {
|
|
3427
3467
|
hasAnyArtifact,
|
|
@@ -3442,11 +3482,11 @@ async function executeUninstallDeletion(artifacts, flags) {
|
|
|
3442
3482
|
if (artifacts.claudeDir) {
|
|
3443
3483
|
if (flags.keepSubmodule) {
|
|
3444
3484
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
3445
|
-
const { join:
|
|
3485
|
+
const { join: join23 } = await import("path");
|
|
3446
3486
|
const entries = await readdir2(artifacts.claudeDir);
|
|
3447
3487
|
for (const entry of entries) {
|
|
3448
3488
|
if (entry === "pack") continue;
|
|
3449
|
-
await rm(
|
|
3489
|
+
await rm(join23(artifacts.claudeDir, entry), { recursive: true, force: true });
|
|
3450
3490
|
}
|
|
3451
3491
|
} else {
|
|
3452
3492
|
await rm(artifacts.claudeDir, { recursive: true, force: true });
|
|
@@ -3515,7 +3555,7 @@ async function removeSubmoduleEntry(gitmodulesPath, submodulePath) {
|
|
|
3515
3555
|
}
|
|
3516
3556
|
|
|
3517
3557
|
// src/commands/uninstall.ts
|
|
3518
|
-
var CLI_VERSION = "1.3.
|
|
3558
|
+
var CLI_VERSION = "1.3.1";
|
|
3519
3559
|
function registerUninstallCommand(program2) {
|
|
3520
3560
|
program2.command("uninstall").description("G\u1EE1 Avatar kh\u1ECFi project \u2014 backup t\u1EF1 \u0111\u1ED9ng (M11)").option("--yes", "Skip confirm prompt").option("--no-backup", "Kh\xF4ng t\u1EA1o backup tr\u01B0\u1EDBc khi x\xF3a (nguy hi\u1EC3m)").option("--keep-submodule", "Gi\u1EEF submodule .claude/pack/").option("--keep-hooks", "Gi\u1EEF git hooks post-merge, pre-push").option("--dry-run", "Hi\u1EC3n th\u1ECB danh s\xE1ch s\u1EBD x\xF3a, kh\xF4ng th\u1EF1c thi").action(async (opts) => {
|
|
3521
3561
|
try {
|
|
@@ -3597,7 +3637,7 @@ function printUninstallSuccessBox(backupPath) {
|
|
|
3597
3637
|
}
|
|
3598
3638
|
|
|
3599
3639
|
// src/index.ts
|
|
3600
|
-
var CLI_VERSION2 = "1.3.
|
|
3640
|
+
var CLI_VERSION2 = "1.3.1";
|
|
3601
3641
|
var program = new Command();
|
|
3602
3642
|
program.name("avatar").description("AI harness CLI for NAL Vietnam engineering").version(CLI_VERSION2, "-v, --version", "Hi\u1EC3n th\u1ECB phi\xEAn b\u1EA3n Avatar CLI").addHelpText(
|
|
3603
3643
|
"beforeAll",
|