@nalvietnam/avatar-cli 1.3.0 → 1.3.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 +164 -151
- 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 +29 -13
- package/package.json +1 -1
- package/src/templates/CLAUDE.md.tpl +29 -13
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 {};
|
|
@@ -928,24 +968,8 @@ async function commitWorkspace(workspaceRoot, opts) {
|
|
|
928
968
|
}
|
|
929
969
|
return { sha, pushed };
|
|
930
970
|
}
|
|
931
|
-
function warnIfOtherTargetDirty(workspaceRoot, requestedTarget) {
|
|
932
|
-
if (requestedTarget === "all") return;
|
|
933
|
-
const srcDirty = isDirty(join6(workspaceRoot, "src"));
|
|
934
|
-
const workspaceDirty = isDirty(workspaceRoot);
|
|
935
|
-
if (requestedTarget === "src" && workspaceDirty) {
|
|
936
|
-
log.warn(
|
|
937
|
-
"Workspace c\u0169ng c\xF3 changes ch\u01B0a commit. Ch\u1EA1y 'avatar commit workspace' (ho\u1EB7c 'avatar commit all') \u0111\u1EC3 commit lu\xF4n."
|
|
938
|
-
);
|
|
939
|
-
}
|
|
940
|
-
if (requestedTarget === "workspace" && srcDirty) {
|
|
941
|
-
log.warn(
|
|
942
|
-
"src/ c\u0169ng c\xF3 changes ch\u01B0a commit. Ch\u1EA1y 'avatar commit src' (ho\u1EB7c 'avatar commit all') \u0111\u1EC3 commit lu\xF4n."
|
|
943
|
-
);
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
971
|
async function executeCommitWithTargetSelection(args) {
|
|
947
972
|
assertAvatarWorkspaceRoot(args.workspaceRoot);
|
|
948
|
-
warnIfOtherTargetDirty(args.workspaceRoot, args.target);
|
|
949
973
|
const result = { target: args.target, skipped: [] };
|
|
950
974
|
if (args.target === "src" || args.target === "all") {
|
|
951
975
|
const srcOutcome = await commitSrc(args.workspaceRoot, args.options);
|
|
@@ -964,18 +988,12 @@ async function executeCommitWithTargetSelection(args) {
|
|
|
964
988
|
|
|
965
989
|
// src/commands/commit.ts
|
|
966
990
|
function registerCommitCommand(program2) {
|
|
967
|
-
const commit = program2.command("commit").description("Commit code
|
|
991
|
+
const commit = program2.command("commit").description("Commit code kh\xE1ch trong src/ (Avatar state do admin sync) (M07)");
|
|
968
992
|
commit.command("src").description("Commit src/ \u2192 push l\xEAn client repo remote").option("-m, --message <msg>", "Commit message").option("--push", "Auto push sau commit (default: ch\u1EC9 commit)").action(async (opts) => {
|
|
969
|
-
await
|
|
970
|
-
});
|
|
971
|
-
commit.command("workspace").description("Commit workspace root \u2192 push l\xEAn team remote (Avatar state)").option("-m, --message <msg>", "Commit message").option("--push", "Auto push sau commit (default: ch\u1EC9 commit)").action(async (opts) => {
|
|
972
|
-
await runCommit("workspace", opts);
|
|
973
|
-
});
|
|
974
|
-
commit.command("all").description("Commit c\u1EA3 src/ v\xE0 workspace (src tr\u01B0\u1EDBc, gitlink update, workspace sau)").option("-m, --message <msg>", "Commit message").option("--push", "Auto push sau commit (default: ch\u1EC9 commit)").action(async (opts) => {
|
|
975
|
-
await runCommit("all", opts);
|
|
993
|
+
await runCommitSrc(opts);
|
|
976
994
|
});
|
|
977
995
|
}
|
|
978
|
-
async function
|
|
996
|
+
async function runCommitSrc(opts) {
|
|
979
997
|
try {
|
|
980
998
|
const message = opts.message ?? await input2({
|
|
981
999
|
message: "Commit message:",
|
|
@@ -983,17 +1001,12 @@ async function runCommit(target, opts) {
|
|
|
983
1001
|
});
|
|
984
1002
|
const result = await executeCommitWithTargetSelection({
|
|
985
1003
|
workspaceRoot: process.cwd(),
|
|
986
|
-
target,
|
|
1004
|
+
target: "src",
|
|
987
1005
|
options: { message, push: opts.push }
|
|
988
1006
|
});
|
|
989
1007
|
if (result.srcCommitSha) {
|
|
990
1008
|
log.success(`src/: ${result.srcCommitSha.slice(0, 7)}${result.srcPushed ? " (pushed)" : ""}`);
|
|
991
1009
|
}
|
|
992
|
-
if (result.workspaceCommitSha) {
|
|
993
|
-
log.success(
|
|
994
|
-
`workspace: ${result.workspaceCommitSha.slice(0, 7)}${result.workspacePushed ? " (pushed)" : ""}`
|
|
995
|
-
);
|
|
996
|
-
}
|
|
997
1010
|
if (result.skipped && result.skipped.length > 0) {
|
|
998
1011
|
log.dim(`Skipped (nothing to commit): ${result.skipped.join(", ")}`);
|
|
999
1012
|
}
|
|
@@ -1006,23 +1019,23 @@ async function runCommit(target, opts) {
|
|
|
1006
1019
|
// src/commands/doctor.ts
|
|
1007
1020
|
import { spawnSync as spawnSync6 } from "child_process";
|
|
1008
1021
|
import { promises as fs6 } from "fs";
|
|
1009
|
-
import { join as
|
|
1022
|
+
import { join as join11 } from "path";
|
|
1010
1023
|
import boxen from "boxen";
|
|
1011
1024
|
|
|
1012
1025
|
// src/lib/git-operations.ts
|
|
1013
|
-
import { join as
|
|
1026
|
+
import { join as join8 } from "path";
|
|
1014
1027
|
import { simpleGit } from "simple-git";
|
|
1015
1028
|
function git(cwd = process.cwd()) {
|
|
1016
1029
|
return simpleGit({ baseDir: cwd, binary: "git" });
|
|
1017
1030
|
}
|
|
1018
1031
|
async function isGitRepo(cwd = process.cwd()) {
|
|
1019
|
-
return await pathExists(
|
|
1032
|
+
return await pathExists(join8(cwd, ".git"));
|
|
1020
1033
|
}
|
|
1021
1034
|
async function addSubmodule(repoUrl, destPath, cwd = process.cwd()) {
|
|
1022
1035
|
await git(cwd).subModule(["add", repoUrl, destPath]);
|
|
1023
1036
|
}
|
|
1024
1037
|
async function checkoutTagInSubmodule(submodulePath, tag, cwd = process.cwd()) {
|
|
1025
|
-
const submoduleCwd =
|
|
1038
|
+
const submoduleCwd = join8(cwd, submodulePath);
|
|
1026
1039
|
await git(submoduleCwd).fetch(["--tags"]);
|
|
1027
1040
|
await git(submoduleCwd).checkout(tag);
|
|
1028
1041
|
}
|
|
@@ -1041,11 +1054,11 @@ async function currentCommitSha(cwd = process.cwd()) {
|
|
|
1041
1054
|
|
|
1042
1055
|
// src/lib/project-tree-scaffolder.ts
|
|
1043
1056
|
import { promises as fs5 } from "fs";
|
|
1044
|
-
import { join as
|
|
1057
|
+
import { join as join10 } from "path";
|
|
1045
1058
|
|
|
1046
1059
|
// src/lib/template-bundle-loader.ts
|
|
1047
|
-
import { existsSync as
|
|
1048
|
-
import { dirname as
|
|
1060
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1061
|
+
import { dirname as dirname3, join as join9 } from "path";
|
|
1049
1062
|
import { fileURLToPath } from "url";
|
|
1050
1063
|
|
|
1051
1064
|
// src/lib/mustache-template-engine.ts
|
|
@@ -1059,15 +1072,15 @@ function renderTemplate(source, variables) {
|
|
|
1059
1072
|
}
|
|
1060
1073
|
|
|
1061
1074
|
// src/lib/template-bundle-loader.ts
|
|
1062
|
-
var HERE =
|
|
1075
|
+
var HERE = dirname3(fileURLToPath(import.meta.url));
|
|
1063
1076
|
var PACKAGE_ROOT = findPackageRoot(HERE);
|
|
1064
|
-
var TEMPLATES_ROOT =
|
|
1065
|
-
var HOOKS_ROOT =
|
|
1077
|
+
var TEMPLATES_ROOT = join9(PACKAGE_ROOT, "src", "templates");
|
|
1078
|
+
var HOOKS_ROOT = join9(PACKAGE_ROOT, "src", "hooks");
|
|
1066
1079
|
function findPackageRoot(startDir) {
|
|
1067
1080
|
let dir = startDir;
|
|
1068
1081
|
while (true) {
|
|
1069
|
-
if (
|
|
1070
|
-
const parent =
|
|
1082
|
+
if (existsSync3(join9(dir, "package.json"))) return dir;
|
|
1083
|
+
const parent = dirname3(dir);
|
|
1071
1084
|
if (parent === dir) {
|
|
1072
1085
|
throw new Error(`Cannot locate package root from ${startDir}`);
|
|
1073
1086
|
}
|
|
@@ -1075,14 +1088,14 @@ function findPackageRoot(startDir) {
|
|
|
1075
1088
|
}
|
|
1076
1089
|
}
|
|
1077
1090
|
async function loadTemplate(name) {
|
|
1078
|
-
return await readText(
|
|
1091
|
+
return await readText(join9(TEMPLATES_ROOT, `${name}.tpl`));
|
|
1079
1092
|
}
|
|
1080
1093
|
async function renderTemplateByName(name, variables) {
|
|
1081
1094
|
const source = await loadTemplate(name);
|
|
1082
1095
|
return renderTemplate(source, variables);
|
|
1083
1096
|
}
|
|
1084
1097
|
async function loadHook(name) {
|
|
1085
|
-
return await readText(
|
|
1098
|
+
return await readText(join9(HOOKS_ROOT, `${name}.sh.tpl`));
|
|
1086
1099
|
}
|
|
1087
1100
|
|
|
1088
1101
|
// src/lib/project-tree-scaffolder.ts
|
|
@@ -1116,12 +1129,12 @@ var PROJECT_KNOWLEDGE_TEMPLATES = [
|
|
|
1116
1129
|
"project/gotchas.md"
|
|
1117
1130
|
];
|
|
1118
1131
|
async function createClaudeDirTree(projectRoot) {
|
|
1119
|
-
const claudeRoot =
|
|
1132
|
+
const claudeRoot = join10(projectRoot, ".claude");
|
|
1120
1133
|
await ensureDir(claudeRoot);
|
|
1121
1134
|
for (const sub of CLAUDE_SUBDIRS) {
|
|
1122
|
-
const dir =
|
|
1135
|
+
const dir = join10(claudeRoot, sub);
|
|
1123
1136
|
await ensureDir(dir);
|
|
1124
|
-
await writeTextAtomic(
|
|
1137
|
+
await writeTextAtomic(join10(dir, ".gitkeep"), "");
|
|
1125
1138
|
}
|
|
1126
1139
|
}
|
|
1127
1140
|
async function writeProjectKnowledgeFiles(projectRoot, vars) {
|
|
@@ -1152,7 +1165,7 @@ async function writeProjectKnowledgeFiles(projectRoot, vars) {
|
|
|
1152
1165
|
for (const tpl of PROJECT_KNOWLEDGE_TEMPLATES) {
|
|
1153
1166
|
const content = await renderTemplateByName(tpl, baseVars);
|
|
1154
1167
|
const relative4 = tpl.replace(/^project\//, "");
|
|
1155
|
-
const outPath =
|
|
1168
|
+
const outPath = join10(projectRoot, ".claude", "project", relative4);
|
|
1156
1169
|
const backup = await writeWithBackup(outPath, content);
|
|
1157
1170
|
if (backup) backups.push(backup);
|
|
1158
1171
|
}
|
|
@@ -1160,14 +1173,14 @@ async function writeProjectKnowledgeFiles(projectRoot, vars) {
|
|
|
1160
1173
|
}
|
|
1161
1174
|
async function writeRootClaudeMd(projectRoot, vars) {
|
|
1162
1175
|
const content = await renderTemplateByName("CLAUDE.md", vars);
|
|
1163
|
-
return await writeWithBackup(
|
|
1176
|
+
return await writeWithBackup(join10(projectRoot, "CLAUDE.md"), content);
|
|
1164
1177
|
}
|
|
1165
1178
|
async function writeProjectSettings(projectRoot, vars) {
|
|
1166
1179
|
const content = await renderTemplateByName("settings.json", vars);
|
|
1167
|
-
return await writeWithBackup(
|
|
1180
|
+
return await writeWithBackup(join10(projectRoot, ".claude", "settings.json"), content);
|
|
1168
1181
|
}
|
|
1169
1182
|
async function appendGitignoreEntries(projectRoot) {
|
|
1170
|
-
const path =
|
|
1183
|
+
const path = join10(projectRoot, ".gitignore");
|
|
1171
1184
|
const tpl = await renderTemplateByName("gitignore", {});
|
|
1172
1185
|
const marker = "# Avatar \u2014 git-ignored entries injected on `avatar init`";
|
|
1173
1186
|
let existing = "";
|
|
@@ -1181,9 +1194,9 @@ ${tpl}`);
|
|
|
1181
1194
|
}
|
|
1182
1195
|
async function installGitHook(gitDir, hookName) {
|
|
1183
1196
|
const content = await loadHook(hookName);
|
|
1184
|
-
const hooksDir =
|
|
1197
|
+
const hooksDir = join10(gitDir, "hooks");
|
|
1185
1198
|
await ensureDir(hooksDir);
|
|
1186
|
-
const dest =
|
|
1199
|
+
const dest = join10(hooksDir, hookName);
|
|
1187
1200
|
await writeTextAtomic(dest, content, 493);
|
|
1188
1201
|
}
|
|
1189
1202
|
|
|
@@ -1241,7 +1254,7 @@ async function runChecks(cwd) {
|
|
|
1241
1254
|
detail: gitRepo ? cwd : "Kh\xF4ng ph\u1EA3i git repo (c\u1EA7n cho 'avatar init')",
|
|
1242
1255
|
fixable: false
|
|
1243
1256
|
});
|
|
1244
|
-
const packPath =
|
|
1257
|
+
const packPath = join11(cwd, ".claude", "pack");
|
|
1245
1258
|
const hasPack = await pathExists(packPath);
|
|
1246
1259
|
checks.push({
|
|
1247
1260
|
name: "team-ai-pack submodule",
|
|
@@ -1249,7 +1262,7 @@ async function runChecks(cwd) {
|
|
|
1249
1262
|
detail: hasPack ? packPath : "Avatar ch\u01B0a init \u2014 ch\u1EA1y 'avatar init'",
|
|
1250
1263
|
fixable: false
|
|
1251
1264
|
});
|
|
1252
|
-
const claudeMdPath =
|
|
1265
|
+
const claudeMdPath = join11(cwd, "CLAUDE.md");
|
|
1253
1266
|
const hasClaudeMd = await pathExists(claudeMdPath);
|
|
1254
1267
|
checks.push({
|
|
1255
1268
|
name: "CLAUDE.md",
|
|
@@ -1257,7 +1270,7 @@ async function runChecks(cwd) {
|
|
|
1257
1270
|
detail: hasClaudeMd ? "t\u1ED3n t\u1EA1i \u1EDF project root" : "thi\u1EBFu \u2014 ch\u1EA1y 'avatar init'",
|
|
1258
1271
|
fixable: false
|
|
1259
1272
|
});
|
|
1260
|
-
const hookPath =
|
|
1273
|
+
const hookPath = join11(cwd, ".git", "hooks", "post-merge");
|
|
1261
1274
|
const hasHook = await pathExists(hookPath);
|
|
1262
1275
|
if (gitRepo && hasPack) {
|
|
1263
1276
|
checks.push({
|
|
@@ -1266,11 +1279,11 @@ async function runChecks(cwd) {
|
|
|
1266
1279
|
detail: hasHook ? "installed" : "missing \u2014 fixable",
|
|
1267
1280
|
fixable: !hasHook,
|
|
1268
1281
|
fix: hasHook ? void 0 : async () => {
|
|
1269
|
-
await installGitHook(
|
|
1282
|
+
await installGitHook(join11(cwd, ".git"), "post-merge");
|
|
1270
1283
|
}
|
|
1271
1284
|
});
|
|
1272
1285
|
}
|
|
1273
|
-
const gitignorePath =
|
|
1286
|
+
const gitignorePath = join11(cwd, ".gitignore");
|
|
1274
1287
|
if (gitRepo) {
|
|
1275
1288
|
let gitignoreOk = false;
|
|
1276
1289
|
if (await pathExists(gitignorePath)) {
|
|
@@ -1332,7 +1345,7 @@ async function applyFixes(checks) {
|
|
|
1332
1345
|
}
|
|
1333
1346
|
|
|
1334
1347
|
// src/commands/init.ts
|
|
1335
|
-
import { basename, join as
|
|
1348
|
+
import { basename, join as join18, relative as relative2, resolve } from "path";
|
|
1336
1349
|
import { confirm as confirm3, input as input5, select as select8 } from "@inquirer/prompts";
|
|
1337
1350
|
import boxen4 from "boxen";
|
|
1338
1351
|
|
|
@@ -1365,7 +1378,7 @@ async function promptRetryOrSkip(args) {
|
|
|
1365
1378
|
}
|
|
1366
1379
|
|
|
1367
1380
|
// src/lib/team-pack-submodule-manager.ts
|
|
1368
|
-
import { join as
|
|
1381
|
+
import { join as join12 } from "path";
|
|
1369
1382
|
|
|
1370
1383
|
// src/lib/check-team-pack-access-with-retry-loop.ts
|
|
1371
1384
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
@@ -1530,7 +1543,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
|
|
|
1530
1543
|
}
|
|
1531
1544
|
let target = tag ?? null;
|
|
1532
1545
|
if (!target) {
|
|
1533
|
-
target = await latestTag(
|
|
1546
|
+
target = await latestTag(join12(projectRoot, TEAM_PACK_RELATIVE_PATH));
|
|
1534
1547
|
}
|
|
1535
1548
|
if (target) {
|
|
1536
1549
|
await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, target, projectRoot);
|
|
@@ -1538,7 +1551,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
|
|
|
1538
1551
|
return { pinnedTag: target };
|
|
1539
1552
|
}
|
|
1540
1553
|
async function readPinnedPackVersion(projectRoot) {
|
|
1541
|
-
const submoduleRoot =
|
|
1554
|
+
const submoduleRoot = join12(projectRoot, TEAM_PACK_RELATIVE_PATH);
|
|
1542
1555
|
const tag = await latestTag(submoduleRoot);
|
|
1543
1556
|
if (tag) return tag;
|
|
1544
1557
|
const sha = await currentCommitSha(submoduleRoot);
|
|
@@ -2225,11 +2238,11 @@ import { select as select7 } from "@inquirer/prompts";
|
|
|
2225
2238
|
import { simpleGit as simpleGit3 } from "simple-git";
|
|
2226
2239
|
|
|
2227
2240
|
// src/lib/check-folder-has-git.ts
|
|
2228
|
-
import { existsSync as
|
|
2229
|
-
import { join as
|
|
2241
|
+
import { existsSync as existsSync4, statSync } from "fs";
|
|
2242
|
+
import { join as join13 } from "path";
|
|
2230
2243
|
function checkFolderHasGit(folderPath) {
|
|
2231
|
-
const gitPath =
|
|
2232
|
-
if (!
|
|
2244
|
+
const gitPath = join13(folderPath, ".git");
|
|
2245
|
+
if (!existsSync4(gitPath)) return false;
|
|
2233
2246
|
const stat = statSync(gitPath);
|
|
2234
2247
|
return stat.isDirectory() || stat.isFile();
|
|
2235
2248
|
}
|
|
@@ -2259,8 +2272,8 @@ async function createInitialGitCommit(folderPath) {
|
|
|
2259
2272
|
}
|
|
2260
2273
|
|
|
2261
2274
|
// src/lib/detect-folder-tech-stack.ts
|
|
2262
|
-
import { existsSync as
|
|
2263
|
-
import { join as
|
|
2275
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2276
|
+
import { join as join14 } from "path";
|
|
2264
2277
|
var SIGNATURES = {
|
|
2265
2278
|
node: ["package.json"],
|
|
2266
2279
|
python: ["pyproject.toml", "requirements.txt", "setup.py", "Pipfile"],
|
|
@@ -2272,7 +2285,7 @@ var SIGNATURES = {
|
|
|
2272
2285
|
function detectFolderTechStack(folderPath) {
|
|
2273
2286
|
const matched = [];
|
|
2274
2287
|
for (const [stack, files] of Object.entries(SIGNATURES)) {
|
|
2275
|
-
if (files.some((f) =>
|
|
2288
|
+
if (files.some((f) => existsSync5(join14(folderPath, f)))) {
|
|
2276
2289
|
matched.push(stack);
|
|
2277
2290
|
}
|
|
2278
2291
|
}
|
|
@@ -2280,26 +2293,26 @@ function detectFolderTechStack(folderPath) {
|
|
|
2280
2293
|
}
|
|
2281
2294
|
|
|
2282
2295
|
// src/lib/gitignore-template-loader.ts
|
|
2283
|
-
import { readFileSync as
|
|
2284
|
-
import { dirname as
|
|
2296
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2297
|
+
import { dirname as dirname4, join as join15 } from "path";
|
|
2285
2298
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2286
|
-
var __dirname =
|
|
2299
|
+
var __dirname = dirname4(fileURLToPath2(import.meta.url));
|
|
2287
2300
|
var CANDIDATE_DIRS = [
|
|
2288
2301
|
// Bundled production: dist/index.js → __dirname = .../dist/, sibling dist/templates
|
|
2289
|
-
|
|
2302
|
+
join15(__dirname, "templates", "gitignore"),
|
|
2290
2303
|
// Legacy bundled: nếu file là dist/lib/*.js (sub-bundle), templates ở dist/templates
|
|
2291
|
-
|
|
2304
|
+
join15(__dirname, "..", "templates", "gitignore"),
|
|
2292
2305
|
// Dev mode (vitest/tsx run src/ trực tiếp): __dirname = src/lib/
|
|
2293
|
-
|
|
2306
|
+
join15(__dirname, "..", "..", "src", "templates", "gitignore"),
|
|
2294
2307
|
// npm-installed alt: __dirname = .../dist/ → package_root/src/templates
|
|
2295
|
-
|
|
2308
|
+
join15(__dirname, "..", "src", "templates", "gitignore")
|
|
2296
2309
|
];
|
|
2297
2310
|
var AVATAR_MARKER_START = "# === avatar ===";
|
|
2298
2311
|
var AVATAR_MARKER_END = "# === /avatar ===";
|
|
2299
2312
|
function readTemplate(stack) {
|
|
2300
2313
|
for (const dir of CANDIDATE_DIRS) {
|
|
2301
2314
|
try {
|
|
2302
|
-
return
|
|
2315
|
+
return readFileSync3(join15(dir, `${stack}.txt`), "utf8");
|
|
2303
2316
|
} catch {
|
|
2304
2317
|
}
|
|
2305
2318
|
}
|
|
@@ -2313,15 +2326,15 @@ ${readTemplate(s).trim()}`);
|
|
|
2313
2326
|
}
|
|
2314
2327
|
|
|
2315
2328
|
// src/lib/write-or-merge-gitignore.ts
|
|
2316
|
-
import { existsSync as
|
|
2317
|
-
import { join as
|
|
2329
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
2330
|
+
import { join as join16 } from "path";
|
|
2318
2331
|
function writeOrMergeGitignore(folderPath, avatarBlock) {
|
|
2319
|
-
const path =
|
|
2320
|
-
if (!
|
|
2332
|
+
const path = join16(folderPath, ".gitignore");
|
|
2333
|
+
if (!existsSync6(path)) {
|
|
2321
2334
|
writeFileSync(path, avatarBlock, "utf8");
|
|
2322
2335
|
return;
|
|
2323
2336
|
}
|
|
2324
|
-
const existing =
|
|
2337
|
+
const existing = readFileSync4(path, "utf8");
|
|
2325
2338
|
const startIdx = existing.indexOf(AVATAR_MARKER_START);
|
|
2326
2339
|
const endIdx = existing.indexOf(AVATAR_MARKER_END);
|
|
2327
2340
|
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
@@ -2493,7 +2506,7 @@ async function safeBootstrapGitInFolder(folderPath, opts = {}) {
|
|
|
2493
2506
|
|
|
2494
2507
|
// src/commands/init-conflict-detection-helpers.ts
|
|
2495
2508
|
import { readdir } from "fs/promises";
|
|
2496
|
-
import { join as
|
|
2509
|
+
import { join as join17 } from "path";
|
|
2497
2510
|
async function isEmptyOrMissing(path) {
|
|
2498
2511
|
if (!await pathExists(path)) return true;
|
|
2499
2512
|
try {
|
|
@@ -2506,7 +2519,7 @@ async function isEmptyOrMissing(path) {
|
|
|
2506
2519
|
}
|
|
2507
2520
|
async function findAlternativeWorkspaceName(parent, desiredName, maxAttempts = 10) {
|
|
2508
2521
|
for (let i = 2; i < maxAttempts; i++) {
|
|
2509
|
-
const candidate =
|
|
2522
|
+
const candidate = join17(parent, `${desiredName}-${i}`);
|
|
2510
2523
|
if (await isEmptyOrMissing(candidate)) return candidate;
|
|
2511
2524
|
}
|
|
2512
2525
|
return null;
|
|
@@ -2898,7 +2911,7 @@ async function runInitFromScratch(opts, ownerEmail) {
|
|
|
2898
2911
|
const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
|
|
2899
2912
|
const workspaceParent = resolve(opts.workspaceParent ?? ".");
|
|
2900
2913
|
const workspacePath = await resolveWorkspacePath(workspaceParent, projectName, opts.force);
|
|
2901
|
-
const srcPath =
|
|
2914
|
+
const srcPath = join18(workspacePath, "src");
|
|
2902
2915
|
await ensureDir(workspacePath);
|
|
2903
2916
|
await ensureDir(srcPath);
|
|
2904
2917
|
await safeBootstrapGitInFolder(srcPath, { autoYes: true });
|
|
@@ -3034,10 +3047,10 @@ async function finalizeWorkspaceScaffold(args) {
|
|
|
3034
3047
|
await writeRootClaudeMd(args.workspacePath, vars);
|
|
3035
3048
|
await writeProjectSettings(args.workspacePath, vars);
|
|
3036
3049
|
await appendGitignoreEntries(args.workspacePath);
|
|
3037
|
-
await ensureDir(
|
|
3038
|
-
await ensureDir(
|
|
3039
|
-
await installGitHook(
|
|
3040
|
-
await installGitHook(
|
|
3050
|
+
await ensureDir(join18(args.workspacePath, "notes"));
|
|
3051
|
+
await ensureDir(join18(args.workspacePath, "scripts"));
|
|
3052
|
+
await installGitHook(join18(args.workspacePath, ".git"), "post-merge");
|
|
3053
|
+
await installGitHook(join18(args.workspacePath, ".git", "modules", "src"), "pre-push");
|
|
3041
3054
|
log.success("C\xE0i post-merge (workspace) + pre-push (src/)");
|
|
3042
3055
|
await appendAuditEntry("init", `flow=${args.flow},workspace=${args.workspaceName}`);
|
|
3043
3056
|
await maybeCommitWorkspace(args.workspacePath, args.skipCommit);
|
|
@@ -3137,7 +3150,7 @@ async function maybeCreateWorkspaceRemote(args) {
|
|
|
3137
3150
|
}
|
|
3138
3151
|
}
|
|
3139
3152
|
async function resolveWorkspacePath(parent, desiredName, force) {
|
|
3140
|
-
const desired =
|
|
3153
|
+
const desired = join18(parent, desiredName);
|
|
3141
3154
|
if (await isEmptyOrMissing(desired)) return desired;
|
|
3142
3155
|
log.warn(`Workspace path "${desired}" \u0111\xE3 c\xF3 n\u1ED9i dung.`);
|
|
3143
3156
|
while (true) {
|
|
@@ -3168,7 +3181,7 @@ async function resolveWorkspacePath(parent, desiredName, force) {
|
|
|
3168
3181
|
message: "T\xEAn workspace m\u1EDBi:",
|
|
3169
3182
|
validate: (v) => v.trim().length > 0 ? true : "T\xEAn kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng"
|
|
3170
3183
|
});
|
|
3171
|
-
const newPath =
|
|
3184
|
+
const newPath = join18(parent, newName.trim());
|
|
3172
3185
|
if (await isEmptyOrMissing(newPath)) return newPath;
|
|
3173
3186
|
log.warn(`"${newPath}" c\u0169ng \u0111\xE3 c\xF3 n\u1ED9i dung. Th\u1EED t\xEAn kh\xE1c.`);
|
|
3174
3187
|
}
|
|
@@ -3262,15 +3275,15 @@ function registerSecretsCommand(program2) {
|
|
|
3262
3275
|
|
|
3263
3276
|
// src/commands/status.ts
|
|
3264
3277
|
import { promises as fs8 } from "fs";
|
|
3265
|
-
import { join as
|
|
3278
|
+
import { join as join20 } from "path";
|
|
3266
3279
|
import boxen5 from "boxen";
|
|
3267
3280
|
|
|
3268
3281
|
// src/lib/pack-backup-manager.ts
|
|
3269
3282
|
import { promises as fs7 } from "fs";
|
|
3270
|
-
import { join as
|
|
3283
|
+
import { join as join19 } from "path";
|
|
3271
3284
|
var BACKUP_DIR_NAME = "_backup";
|
|
3272
3285
|
async function listBackups(projectRoot) {
|
|
3273
|
-
const dir =
|
|
3286
|
+
const dir = join19(projectRoot, ".claude", BACKUP_DIR_NAME);
|
|
3274
3287
|
if (!await pathExists(dir)) return [];
|
|
3275
3288
|
const entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
3276
3289
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort().reverse();
|
|
@@ -3296,7 +3309,7 @@ function registerStatusCommand(program2) {
|
|
|
3296
3309
|
}
|
|
3297
3310
|
async function gatherStatus(cwd) {
|
|
3298
3311
|
const projectName = cwd.split("/").filter(Boolean).pop() ?? "unknown";
|
|
3299
|
-
const claudeRoot =
|
|
3312
|
+
const claudeRoot = join20(cwd, ".claude");
|
|
3300
3313
|
const hasAvatar = await pathExists(claudeRoot);
|
|
3301
3314
|
if (!hasAvatar) {
|
|
3302
3315
|
return {
|
|
@@ -3309,8 +3322,8 @@ async function gatherStatus(cwd) {
|
|
|
3309
3322
|
hasAvatar: false
|
|
3310
3323
|
};
|
|
3311
3324
|
}
|
|
3312
|
-
const packVersion = await isGitRepo(
|
|
3313
|
-
const pendingDir =
|
|
3325
|
+
const packVersion = await isGitRepo(join20(claudeRoot, "pack")) ? await readPinnedPackVersion(cwd).catch(() => null) : null;
|
|
3326
|
+
const pendingDir = join20(claudeRoot, "_pending");
|
|
3314
3327
|
const pendingCount = await pathExists(pendingDir) ? (await fs8.readdir(pendingDir)).filter((n) => n.endsWith(".diff.md")).length : 0;
|
|
3315
3328
|
const backupCount = (await listBackups(cwd)).length;
|
|
3316
3329
|
const techStackSummary = await readTechStackFirstLine(claudeRoot);
|
|
@@ -3325,7 +3338,7 @@ async function gatherStatus(cwd) {
|
|
|
3325
3338
|
};
|
|
3326
3339
|
}
|
|
3327
3340
|
async function readTechStackFirstLine(claudeRoot) {
|
|
3328
|
-
const techStackPath =
|
|
3341
|
+
const techStackPath = join20(claudeRoot, "project", "tech-stack.md");
|
|
3329
3342
|
if (!await pathExists(techStackPath)) return "(no tech-stack.md)";
|
|
3330
3343
|
const content = await readText(techStackPath);
|
|
3331
3344
|
const firstNonHeaderLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#") && !l.startsWith(">"));
|
|
@@ -3366,27 +3379,27 @@ import boxen6 from "boxen";
|
|
|
3366
3379
|
// src/lib/create-uninstall-backup-snapshot.ts
|
|
3367
3380
|
import { cp, mkdir, writeFile } from "fs/promises";
|
|
3368
3381
|
import { homedir as homedir3 } from "os";
|
|
3369
|
-
import { basename as basename2, join as
|
|
3370
|
-
var UNINSTALL_BACKUPS_DIR =
|
|
3382
|
+
import { basename as basename2, join as join21 } from "path";
|
|
3383
|
+
var UNINSTALL_BACKUPS_DIR = join21(homedir3(), ".avatar", "uninstall-backups");
|
|
3371
3384
|
async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersion) {
|
|
3372
3385
|
const projectName = basename2(projectRoot);
|
|
3373
3386
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3374
|
-
const backupDir =
|
|
3387
|
+
const backupDir = join21(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp}`);
|
|
3375
3388
|
await mkdir(backupDir, { recursive: true, mode: 448 });
|
|
3376
3389
|
if (artifacts.claudeDir) {
|
|
3377
|
-
await cp(artifacts.claudeDir,
|
|
3390
|
+
await cp(artifacts.claudeDir, join21(backupDir, ".claude"), { recursive: true });
|
|
3378
3391
|
}
|
|
3379
3392
|
if (artifacts.claudeMd) {
|
|
3380
|
-
await cp(artifacts.claudeMd,
|
|
3393
|
+
await cp(artifacts.claudeMd, join21(backupDir, "CLAUDE.md"));
|
|
3381
3394
|
}
|
|
3382
3395
|
if (artifacts.postMergeHook || artifacts.prePushHook) {
|
|
3383
|
-
const hooksBackupDir =
|
|
3396
|
+
const hooksBackupDir = join21(backupDir, "hooks");
|
|
3384
3397
|
await mkdir(hooksBackupDir, { recursive: true });
|
|
3385
3398
|
if (artifacts.postMergeHook) {
|
|
3386
|
-
await cp(artifacts.postMergeHook,
|
|
3399
|
+
await cp(artifacts.postMergeHook, join21(hooksBackupDir, "post-merge"));
|
|
3387
3400
|
}
|
|
3388
3401
|
if (artifacts.prePushHook) {
|
|
3389
|
-
await cp(artifacts.prePushHook,
|
|
3402
|
+
await cp(artifacts.prePushHook, join21(hooksBackupDir, "pre-push"));
|
|
3390
3403
|
}
|
|
3391
3404
|
}
|
|
3392
3405
|
const manifest = {
|
|
@@ -3401,27 +3414,27 @@ async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersi
|
|
|
3401
3414
|
prePushHook: !!artifacts.prePushHook
|
|
3402
3415
|
}
|
|
3403
3416
|
};
|
|
3404
|
-
await writeFile(
|
|
3417
|
+
await writeFile(join21(backupDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
|
|
3405
3418
|
return backupDir;
|
|
3406
3419
|
}
|
|
3407
3420
|
|
|
3408
3421
|
// src/lib/detect-avatar-project-artifacts.ts
|
|
3409
|
-
import { existsSync as
|
|
3410
|
-
import { join as
|
|
3422
|
+
import { existsSync as existsSync7 } from "fs";
|
|
3423
|
+
import { join as join22 } from "path";
|
|
3411
3424
|
function existsOrNull(path) {
|
|
3412
|
-
return
|
|
3425
|
+
return existsSync7(path) ? path : null;
|
|
3413
3426
|
}
|
|
3414
3427
|
function detectAvatarProjectArtifacts(projectRoot) {
|
|
3415
|
-
const claudeDir = existsOrNull(
|
|
3416
|
-
const claudeMd = existsOrNull(
|
|
3417
|
-
const postMergeHook = existsOrNull(
|
|
3428
|
+
const claudeDir = existsOrNull(join22(projectRoot, ".claude"));
|
|
3429
|
+
const claudeMd = existsOrNull(join22(projectRoot, "CLAUDE.md"));
|
|
3430
|
+
const postMergeHook = existsOrNull(join22(projectRoot, ".git", "hooks", "post-merge"));
|
|
3418
3431
|
const prePushHook = existsOrNull(
|
|
3419
|
-
|
|
3432
|
+
join22(projectRoot, ".git", "modules", "src", "hooks", "pre-push")
|
|
3420
3433
|
);
|
|
3421
|
-
const gitignorePath = existsOrNull(
|
|
3422
|
-
const gitmodulesPath = existsOrNull(
|
|
3423
|
-
const notesDir = existsOrNull(
|
|
3424
|
-
const scriptsDir = existsOrNull(
|
|
3434
|
+
const gitignorePath = existsOrNull(join22(projectRoot, ".gitignore"));
|
|
3435
|
+
const gitmodulesPath = existsOrNull(join22(projectRoot, ".gitmodules"));
|
|
3436
|
+
const notesDir = existsOrNull(join22(projectRoot, "notes"));
|
|
3437
|
+
const scriptsDir = existsOrNull(join22(projectRoot, "scripts"));
|
|
3425
3438
|
const hasAnyArtifact = !!(claudeDir || claudeMd || postMergeHook || prePushHook);
|
|
3426
3439
|
return {
|
|
3427
3440
|
hasAnyArtifact,
|
|
@@ -3442,11 +3455,11 @@ async function executeUninstallDeletion(artifacts, flags) {
|
|
|
3442
3455
|
if (artifacts.claudeDir) {
|
|
3443
3456
|
if (flags.keepSubmodule) {
|
|
3444
3457
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
3445
|
-
const { join:
|
|
3458
|
+
const { join: join23 } = await import("path");
|
|
3446
3459
|
const entries = await readdir2(artifacts.claudeDir);
|
|
3447
3460
|
for (const entry of entries) {
|
|
3448
3461
|
if (entry === "pack") continue;
|
|
3449
|
-
await rm(
|
|
3462
|
+
await rm(join23(artifacts.claudeDir, entry), { recursive: true, force: true });
|
|
3450
3463
|
}
|
|
3451
3464
|
} else {
|
|
3452
3465
|
await rm(artifacts.claudeDir, { recursive: true, force: true });
|
|
@@ -3515,7 +3528,7 @@ async function removeSubmoduleEntry(gitmodulesPath, submodulePath) {
|
|
|
3515
3528
|
}
|
|
3516
3529
|
|
|
3517
3530
|
// src/commands/uninstall.ts
|
|
3518
|
-
var CLI_VERSION = "1.3.
|
|
3531
|
+
var CLI_VERSION = "1.3.2";
|
|
3519
3532
|
function registerUninstallCommand(program2) {
|
|
3520
3533
|
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
3534
|
try {
|
|
@@ -3597,7 +3610,7 @@ function printUninstallSuccessBox(backupPath) {
|
|
|
3597
3610
|
}
|
|
3598
3611
|
|
|
3599
3612
|
// src/index.ts
|
|
3600
|
-
var CLI_VERSION2 = "1.3.
|
|
3613
|
+
var CLI_VERSION2 = "1.3.2";
|
|
3601
3614
|
var program = new Command();
|
|
3602
3615
|
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
3616
|
"beforeAll",
|