@nalvietnam/avatar-cli 1.2.11 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -546,19 +546,19 @@ function applyUseGlobal(existing, source) {
|
|
|
546
546
|
...sourceModel ? { model: sourceModel } : {}
|
|
547
547
|
};
|
|
548
548
|
}
|
|
549
|
-
async function writeClaudeSettings(workspacePath,
|
|
549
|
+
async function writeClaudeSettings(workspacePath, input6) {
|
|
550
550
|
const path = getClaudeSettingsPath(workspacePath);
|
|
551
551
|
const existing = await readExistingSettings(path);
|
|
552
552
|
let merged;
|
|
553
|
-
switch (
|
|
553
|
+
switch (input6.provider) {
|
|
554
554
|
case "subscription":
|
|
555
|
-
merged = applySubscription(existing,
|
|
555
|
+
merged = applySubscription(existing, input6.model);
|
|
556
556
|
break;
|
|
557
557
|
case "llmlite":
|
|
558
|
-
merged = applyLLMLite(existing,
|
|
558
|
+
merged = applyLLMLite(existing, input6.apiKey, input6.baseUrl, input6.model);
|
|
559
559
|
break;
|
|
560
560
|
case "use-global":
|
|
561
|
-
merged = applyUseGlobal(existing,
|
|
561
|
+
merged = applyUseGlobal(existing, input6.sourceSettings);
|
|
562
562
|
break;
|
|
563
563
|
}
|
|
564
564
|
await writeJsonAtomic(path, merged, SECRET_FILE_MODE2);
|
|
@@ -843,47 +843,186 @@ function registerAiCommand(program2) {
|
|
|
843
843
|
});
|
|
844
844
|
}
|
|
845
845
|
|
|
846
|
-
// src/
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
846
|
+
// src/commands/commit.ts
|
|
847
|
+
import { input as input2 } from "@inquirer/prompts";
|
|
848
|
+
|
|
849
|
+
// src/lib/execute-commit-with-target-selection.ts
|
|
850
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
851
|
+
import { existsSync } from "fs";
|
|
852
|
+
import { join as join6 } from "path";
|
|
853
|
+
function assertAvatarWorkspaceRoot(cwd) {
|
|
854
|
+
const srcGit = join6(cwd, "src", ".git");
|
|
855
|
+
const workspaceGit = join6(cwd, ".git");
|
|
856
|
+
const claudeDir = join6(cwd, ".claude");
|
|
857
|
+
if (!existsSync(workspaceGit)) {
|
|
858
|
+
throw new Error(
|
|
859
|
+
`Kh\xF4ng ph\u1EA3i workspace root: ${cwd}
|
|
860
|
+
Ch\u1EA1y 'avatar commit' trong workspace dir (c\xF3 .git v\xE0 .claude/).`
|
|
852
861
|
);
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
}
|
|
862
|
+
}
|
|
863
|
+
if (!existsSync(claudeDir)) {
|
|
864
|
+
throw new Error(
|
|
865
|
+
`Kh\xF4ng th\u1EA5y .claude/ trong ${cwd}.
|
|
866
|
+
Ch\u1EA1y 'avatar commit' trong Avatar workspace, kh\xF4ng ph\u1EA3i project b\xECnh th\u01B0\u1EDDng.`
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
if (!existsSync(srcGit)) {
|
|
870
|
+
throw new Error(
|
|
871
|
+
`Kh\xF4ng th\u1EA5y src/.git trong ${cwd}.
|
|
872
|
+
Workspace thi\u1EBFu submodule src/. Ch\u1EA1y 'avatar init' l\u1EA1i?`
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
function gitExec(cwd, args) {
|
|
877
|
+
const r = spawnSync5("git", ["-C", cwd, ...args], {
|
|
878
|
+
encoding: "utf8",
|
|
879
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
880
|
+
});
|
|
881
|
+
if (r.status !== 0) {
|
|
882
|
+
const stderr = (r.stderr || "").trim();
|
|
883
|
+
throw new Error(`git ${args.join(" ")} (in ${cwd}) failed:
|
|
884
|
+
${stderr}`);
|
|
885
|
+
}
|
|
886
|
+
return (r.stdout || "").trim();
|
|
887
|
+
}
|
|
888
|
+
function isDirty(cwd) {
|
|
889
|
+
const status = gitExec(cwd, ["status", "--porcelain"]);
|
|
890
|
+
return status.length > 0;
|
|
891
|
+
}
|
|
892
|
+
async function commitSrc(workspaceRoot, opts) {
|
|
893
|
+
const srcPath = join6(workspaceRoot, "src");
|
|
894
|
+
if (!isDirty(srcPath)) {
|
|
895
|
+
log.dim("src/: nothing to commit (clean)");
|
|
896
|
+
return {};
|
|
897
|
+
}
|
|
898
|
+
log.info("Committing src/ ...");
|
|
899
|
+
gitExec(srcPath, ["add", "."]);
|
|
900
|
+
gitExec(srcPath, ["commit", "-m", opts.message]);
|
|
901
|
+
const sha = gitExec(srcPath, ["rev-parse", "HEAD"]);
|
|
902
|
+
log.success(`src/ committed: ${sha.slice(0, 7)}`);
|
|
903
|
+
let pushed = false;
|
|
904
|
+
if (opts.push) {
|
|
905
|
+
log.info("Pushing src/ ...");
|
|
906
|
+
gitExec(srcPath, ["push"]);
|
|
907
|
+
log.success("src/ pushed");
|
|
908
|
+
pushed = true;
|
|
909
|
+
}
|
|
910
|
+
return { sha, pushed };
|
|
911
|
+
}
|
|
912
|
+
async function commitWorkspace(workspaceRoot, opts) {
|
|
913
|
+
if (!isDirty(workspaceRoot)) {
|
|
914
|
+
log.dim("workspace: nothing to commit (clean)");
|
|
915
|
+
return {};
|
|
916
|
+
}
|
|
917
|
+
log.info("Committing workspace root ...");
|
|
918
|
+
gitExec(workspaceRoot, ["add", "."]);
|
|
919
|
+
gitExec(workspaceRoot, ["commit", "-m", opts.message]);
|
|
920
|
+
const sha = gitExec(workspaceRoot, ["rev-parse", "HEAD"]);
|
|
921
|
+
log.success(`workspace committed: ${sha.slice(0, 7)}`);
|
|
922
|
+
let pushed = false;
|
|
923
|
+
if (opts.push) {
|
|
924
|
+
log.info("Pushing workspace ...");
|
|
925
|
+
gitExec(workspaceRoot, ["push"]);
|
|
926
|
+
log.success("workspace pushed");
|
|
927
|
+
pushed = true;
|
|
928
|
+
}
|
|
929
|
+
return { sha, pushed };
|
|
930
|
+
}
|
|
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
|
+
async function executeCommitWithTargetSelection(args) {
|
|
947
|
+
assertAvatarWorkspaceRoot(args.workspaceRoot);
|
|
948
|
+
warnIfOtherTargetDirty(args.workspaceRoot, args.target);
|
|
949
|
+
const result = { target: args.target, skipped: [] };
|
|
950
|
+
if (args.target === "src" || args.target === "all") {
|
|
951
|
+
const srcOutcome = await commitSrc(args.workspaceRoot, args.options);
|
|
952
|
+
result.srcCommitSha = srcOutcome.sha;
|
|
953
|
+
result.srcPushed = srcOutcome.pushed;
|
|
954
|
+
if (!srcOutcome.sha) result.skipped?.push("src");
|
|
955
|
+
}
|
|
956
|
+
if (args.target === "workspace" || args.target === "all") {
|
|
957
|
+
const wsOutcome = await commitWorkspace(args.workspaceRoot, args.options);
|
|
958
|
+
result.workspaceCommitSha = wsOutcome.sha;
|
|
959
|
+
result.workspacePushed = wsOutcome.pushed;
|
|
960
|
+
if (!wsOutcome.sha) result.skipped?.push("workspace");
|
|
961
|
+
}
|
|
962
|
+
return result;
|
|
860
963
|
}
|
|
861
964
|
|
|
862
965
|
// src/commands/commit.ts
|
|
863
966
|
function registerCommitCommand(program2) {
|
|
864
|
-
program2.command("commit").description("Commit code
|
|
967
|
+
const commit = program2.command("commit").description("Commit code (src/) ho\u1EB7c Avatar state (workspace) \u2014 split-aware (M07)");
|
|
968
|
+
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 runCommit("src", opts);
|
|
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);
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
async function runCommit(target, opts) {
|
|
979
|
+
try {
|
|
980
|
+
const message = opts.message ?? await input2({
|
|
981
|
+
message: "Commit message:",
|
|
982
|
+
validate: (v) => v.trim().length > 0 ? true : "Message b\u1EAFt bu\u1ED9c"
|
|
983
|
+
});
|
|
984
|
+
const result = await executeCommitWithTargetSelection({
|
|
985
|
+
workspaceRoot: process.cwd(),
|
|
986
|
+
target,
|
|
987
|
+
options: { message, push: opts.push }
|
|
988
|
+
});
|
|
989
|
+
if (result.srcCommitSha) {
|
|
990
|
+
log.success(`src/: ${result.srcCommitSha.slice(0, 7)}${result.srcPushed ? " (pushed)" : ""}`);
|
|
991
|
+
}
|
|
992
|
+
if (result.workspaceCommitSha) {
|
|
993
|
+
log.success(
|
|
994
|
+
`workspace: ${result.workspaceCommitSha.slice(0, 7)}${result.workspacePushed ? " (pushed)" : ""}`
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
if (result.skipped && result.skipped.length > 0) {
|
|
998
|
+
log.dim(`Skipped (nothing to commit): ${result.skipped.join(", ")}`);
|
|
999
|
+
}
|
|
1000
|
+
} catch (err) {
|
|
1001
|
+
log.error(err instanceof Error ? err.message : String(err));
|
|
1002
|
+
process.exit(1);
|
|
1003
|
+
}
|
|
865
1004
|
}
|
|
866
1005
|
|
|
867
1006
|
// src/commands/doctor.ts
|
|
868
|
-
import { spawnSync as
|
|
1007
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
869
1008
|
import { promises as fs6 } from "fs";
|
|
870
|
-
import { join as
|
|
1009
|
+
import { join as join10 } from "path";
|
|
871
1010
|
import boxen from "boxen";
|
|
872
1011
|
|
|
873
1012
|
// src/lib/git-operations.ts
|
|
874
|
-
import { join as
|
|
1013
|
+
import { join as join7 } from "path";
|
|
875
1014
|
import { simpleGit } from "simple-git";
|
|
876
1015
|
function git(cwd = process.cwd()) {
|
|
877
1016
|
return simpleGit({ baseDir: cwd, binary: "git" });
|
|
878
1017
|
}
|
|
879
1018
|
async function isGitRepo(cwd = process.cwd()) {
|
|
880
|
-
return await pathExists(
|
|
1019
|
+
return await pathExists(join7(cwd, ".git"));
|
|
881
1020
|
}
|
|
882
1021
|
async function addSubmodule(repoUrl, destPath, cwd = process.cwd()) {
|
|
883
1022
|
await git(cwd).subModule(["add", repoUrl, destPath]);
|
|
884
1023
|
}
|
|
885
1024
|
async function checkoutTagInSubmodule(submodulePath, tag, cwd = process.cwd()) {
|
|
886
|
-
const submoduleCwd =
|
|
1025
|
+
const submoduleCwd = join7(cwd, submodulePath);
|
|
887
1026
|
await git(submoduleCwd).fetch(["--tags"]);
|
|
888
1027
|
await git(submoduleCwd).checkout(tag);
|
|
889
1028
|
}
|
|
@@ -902,11 +1041,11 @@ async function currentCommitSha(cwd = process.cwd()) {
|
|
|
902
1041
|
|
|
903
1042
|
// src/lib/project-tree-scaffolder.ts
|
|
904
1043
|
import { promises as fs5 } from "fs";
|
|
905
|
-
import { join as
|
|
1044
|
+
import { join as join9 } from "path";
|
|
906
1045
|
|
|
907
1046
|
// src/lib/template-bundle-loader.ts
|
|
908
|
-
import { existsSync } from "fs";
|
|
909
|
-
import { dirname as dirname2, join as
|
|
1047
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1048
|
+
import { dirname as dirname2, join as join8 } from "path";
|
|
910
1049
|
import { fileURLToPath } from "url";
|
|
911
1050
|
|
|
912
1051
|
// src/lib/mustache-template-engine.ts
|
|
@@ -922,12 +1061,12 @@ function renderTemplate(source, variables) {
|
|
|
922
1061
|
// src/lib/template-bundle-loader.ts
|
|
923
1062
|
var HERE = dirname2(fileURLToPath(import.meta.url));
|
|
924
1063
|
var PACKAGE_ROOT = findPackageRoot(HERE);
|
|
925
|
-
var TEMPLATES_ROOT =
|
|
926
|
-
var HOOKS_ROOT =
|
|
1064
|
+
var TEMPLATES_ROOT = join8(PACKAGE_ROOT, "src", "templates");
|
|
1065
|
+
var HOOKS_ROOT = join8(PACKAGE_ROOT, "src", "hooks");
|
|
927
1066
|
function findPackageRoot(startDir) {
|
|
928
1067
|
let dir = startDir;
|
|
929
1068
|
while (true) {
|
|
930
|
-
if (
|
|
1069
|
+
if (existsSync2(join8(dir, "package.json"))) return dir;
|
|
931
1070
|
const parent = dirname2(dir);
|
|
932
1071
|
if (parent === dir) {
|
|
933
1072
|
throw new Error(`Cannot locate package root from ${startDir}`);
|
|
@@ -936,14 +1075,14 @@ function findPackageRoot(startDir) {
|
|
|
936
1075
|
}
|
|
937
1076
|
}
|
|
938
1077
|
async function loadTemplate(name) {
|
|
939
|
-
return await readText(
|
|
1078
|
+
return await readText(join8(TEMPLATES_ROOT, `${name}.tpl`));
|
|
940
1079
|
}
|
|
941
1080
|
async function renderTemplateByName(name, variables) {
|
|
942
1081
|
const source = await loadTemplate(name);
|
|
943
1082
|
return renderTemplate(source, variables);
|
|
944
1083
|
}
|
|
945
1084
|
async function loadHook(name) {
|
|
946
|
-
return await readText(
|
|
1085
|
+
return await readText(join8(HOOKS_ROOT, `${name}.sh.tpl`));
|
|
947
1086
|
}
|
|
948
1087
|
|
|
949
1088
|
// src/lib/project-tree-scaffolder.ts
|
|
@@ -977,12 +1116,12 @@ var PROJECT_KNOWLEDGE_TEMPLATES = [
|
|
|
977
1116
|
"project/gotchas.md"
|
|
978
1117
|
];
|
|
979
1118
|
async function createClaudeDirTree(projectRoot) {
|
|
980
|
-
const claudeRoot =
|
|
1119
|
+
const claudeRoot = join9(projectRoot, ".claude");
|
|
981
1120
|
await ensureDir(claudeRoot);
|
|
982
1121
|
for (const sub of CLAUDE_SUBDIRS) {
|
|
983
|
-
const dir =
|
|
1122
|
+
const dir = join9(claudeRoot, sub);
|
|
984
1123
|
await ensureDir(dir);
|
|
985
|
-
await writeTextAtomic(
|
|
1124
|
+
await writeTextAtomic(join9(dir, ".gitkeep"), "");
|
|
986
1125
|
}
|
|
987
1126
|
}
|
|
988
1127
|
async function writeProjectKnowledgeFiles(projectRoot, vars) {
|
|
@@ -1013,7 +1152,7 @@ async function writeProjectKnowledgeFiles(projectRoot, vars) {
|
|
|
1013
1152
|
for (const tpl of PROJECT_KNOWLEDGE_TEMPLATES) {
|
|
1014
1153
|
const content = await renderTemplateByName(tpl, baseVars);
|
|
1015
1154
|
const relative4 = tpl.replace(/^project\//, "");
|
|
1016
|
-
const outPath =
|
|
1155
|
+
const outPath = join9(projectRoot, ".claude", "project", relative4);
|
|
1017
1156
|
const backup = await writeWithBackup(outPath, content);
|
|
1018
1157
|
if (backup) backups.push(backup);
|
|
1019
1158
|
}
|
|
@@ -1021,14 +1160,14 @@ async function writeProjectKnowledgeFiles(projectRoot, vars) {
|
|
|
1021
1160
|
}
|
|
1022
1161
|
async function writeRootClaudeMd(projectRoot, vars) {
|
|
1023
1162
|
const content = await renderTemplateByName("CLAUDE.md", vars);
|
|
1024
|
-
return await writeWithBackup(
|
|
1163
|
+
return await writeWithBackup(join9(projectRoot, "CLAUDE.md"), content);
|
|
1025
1164
|
}
|
|
1026
1165
|
async function writeProjectSettings(projectRoot, vars) {
|
|
1027
1166
|
const content = await renderTemplateByName("settings.json", vars);
|
|
1028
|
-
return await writeWithBackup(
|
|
1167
|
+
return await writeWithBackup(join9(projectRoot, ".claude", "settings.json"), content);
|
|
1029
1168
|
}
|
|
1030
1169
|
async function appendGitignoreEntries(projectRoot) {
|
|
1031
|
-
const path =
|
|
1170
|
+
const path = join9(projectRoot, ".gitignore");
|
|
1032
1171
|
const tpl = await renderTemplateByName("gitignore", {});
|
|
1033
1172
|
const marker = "# Avatar \u2014 git-ignored entries injected on `avatar init`";
|
|
1034
1173
|
let existing = "";
|
|
@@ -1042,9 +1181,9 @@ ${tpl}`);
|
|
|
1042
1181
|
}
|
|
1043
1182
|
async function installGitHook(gitDir, hookName) {
|
|
1044
1183
|
const content = await loadHook(hookName);
|
|
1045
|
-
const hooksDir =
|
|
1184
|
+
const hooksDir = join9(gitDir, "hooks");
|
|
1046
1185
|
await ensureDir(hooksDir);
|
|
1047
|
-
const dest =
|
|
1186
|
+
const dest = join9(hooksDir, hookName);
|
|
1048
1187
|
await writeTextAtomic(dest, content, 493);
|
|
1049
1188
|
}
|
|
1050
1189
|
|
|
@@ -1102,7 +1241,7 @@ async function runChecks(cwd) {
|
|
|
1102
1241
|
detail: gitRepo ? cwd : "Kh\xF4ng ph\u1EA3i git repo (c\u1EA7n cho 'avatar init')",
|
|
1103
1242
|
fixable: false
|
|
1104
1243
|
});
|
|
1105
|
-
const packPath =
|
|
1244
|
+
const packPath = join10(cwd, ".claude", "pack");
|
|
1106
1245
|
const hasPack = await pathExists(packPath);
|
|
1107
1246
|
checks.push({
|
|
1108
1247
|
name: "team-ai-pack submodule",
|
|
@@ -1110,7 +1249,7 @@ async function runChecks(cwd) {
|
|
|
1110
1249
|
detail: hasPack ? packPath : "Avatar ch\u01B0a init \u2014 ch\u1EA1y 'avatar init'",
|
|
1111
1250
|
fixable: false
|
|
1112
1251
|
});
|
|
1113
|
-
const claudeMdPath =
|
|
1252
|
+
const claudeMdPath = join10(cwd, "CLAUDE.md");
|
|
1114
1253
|
const hasClaudeMd = await pathExists(claudeMdPath);
|
|
1115
1254
|
checks.push({
|
|
1116
1255
|
name: "CLAUDE.md",
|
|
@@ -1118,7 +1257,7 @@ async function runChecks(cwd) {
|
|
|
1118
1257
|
detail: hasClaudeMd ? "t\u1ED3n t\u1EA1i \u1EDF project root" : "thi\u1EBFu \u2014 ch\u1EA1y 'avatar init'",
|
|
1119
1258
|
fixable: false
|
|
1120
1259
|
});
|
|
1121
|
-
const hookPath =
|
|
1260
|
+
const hookPath = join10(cwd, ".git", "hooks", "post-merge");
|
|
1122
1261
|
const hasHook = await pathExists(hookPath);
|
|
1123
1262
|
if (gitRepo && hasPack) {
|
|
1124
1263
|
checks.push({
|
|
@@ -1127,11 +1266,11 @@ async function runChecks(cwd) {
|
|
|
1127
1266
|
detail: hasHook ? "installed" : "missing \u2014 fixable",
|
|
1128
1267
|
fixable: !hasHook,
|
|
1129
1268
|
fix: hasHook ? void 0 : async () => {
|
|
1130
|
-
await installGitHook(
|
|
1269
|
+
await installGitHook(join10(cwd, ".git"), "post-merge");
|
|
1131
1270
|
}
|
|
1132
1271
|
});
|
|
1133
1272
|
}
|
|
1134
|
-
const gitignorePath =
|
|
1273
|
+
const gitignorePath = join10(cwd, ".gitignore");
|
|
1135
1274
|
if (gitRepo) {
|
|
1136
1275
|
let gitignoreOk = false;
|
|
1137
1276
|
if (await pathExists(gitignorePath)) {
|
|
@@ -1145,7 +1284,7 @@ async function runChecks(cwd) {
|
|
|
1145
1284
|
fixable: false
|
|
1146
1285
|
});
|
|
1147
1286
|
}
|
|
1148
|
-
const which =
|
|
1287
|
+
const which = spawnSync6("which", ["claude"]);
|
|
1149
1288
|
const hasClaudeCli = which.status === 0;
|
|
1150
1289
|
checks.push({
|
|
1151
1290
|
name: "Claude Code CLI",
|
|
@@ -1193,16 +1332,16 @@ async function applyFixes(checks) {
|
|
|
1193
1332
|
}
|
|
1194
1333
|
|
|
1195
1334
|
// src/commands/init.ts
|
|
1196
|
-
import { basename, join as
|
|
1197
|
-
import { confirm as confirm3, input as
|
|
1335
|
+
import { basename, join as join17, relative as relative2, resolve } from "path";
|
|
1336
|
+
import { confirm as confirm3, input as input5, select as select8 } from "@inquirer/prompts";
|
|
1198
1337
|
import boxen4 from "boxen";
|
|
1199
1338
|
|
|
1200
1339
|
// src/lib/add-team-pack-submodule-with-retry-on-network-fail.ts
|
|
1201
|
-
import { spawnSync as
|
|
1340
|
+
import { spawnSync as spawnSync8 } from "child_process";
|
|
1202
1341
|
import { select as select5 } from "@inquirer/prompts";
|
|
1203
1342
|
|
|
1204
1343
|
// src/lib/prompt-recovery-action-on-failure.ts
|
|
1205
|
-
import { input as
|
|
1344
|
+
import { input as input3, select as select3 } from "@inquirer/prompts";
|
|
1206
1345
|
var UserAbortedRecoveryError = class extends Error {
|
|
1207
1346
|
constructor(message) {
|
|
1208
1347
|
super(message);
|
|
@@ -1226,10 +1365,10 @@ async function promptRetryOrSkip(args) {
|
|
|
1226
1365
|
}
|
|
1227
1366
|
|
|
1228
1367
|
// src/lib/team-pack-submodule-manager.ts
|
|
1229
|
-
import { join as
|
|
1368
|
+
import { join as join11 } from "path";
|
|
1230
1369
|
|
|
1231
1370
|
// src/lib/check-team-pack-access-with-retry-loop.ts
|
|
1232
|
-
import { spawnSync as
|
|
1371
|
+
import { spawnSync as spawnSync7 } from "child_process";
|
|
1233
1372
|
import { confirm as confirm2, select as select4 } from "@inquirer/prompts";
|
|
1234
1373
|
import boxen2 from "boxen";
|
|
1235
1374
|
function parseRepoSlugFromGitUrl(url) {
|
|
@@ -1238,11 +1377,11 @@ function parseRepoSlugFromGitUrl(url) {
|
|
|
1238
1377
|
return null;
|
|
1239
1378
|
}
|
|
1240
1379
|
function checkRepoAccess(repoSlug) {
|
|
1241
|
-
const r =
|
|
1380
|
+
const r = spawnSync7("gh", ["api", `repos/${repoSlug}`], { stdio: "ignore" });
|
|
1242
1381
|
return r.status === 0;
|
|
1243
1382
|
}
|
|
1244
1383
|
function getCurrentGhUser() {
|
|
1245
|
-
const r =
|
|
1384
|
+
const r = spawnSync7("gh", ["api", "user", "--jq", ".login"], {
|
|
1246
1385
|
encoding: "utf8",
|
|
1247
1386
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1248
1387
|
});
|
|
@@ -1251,7 +1390,7 @@ function getCurrentGhUser() {
|
|
|
1251
1390
|
}
|
|
1252
1391
|
function triggerGhAuthLoginInteractive() {
|
|
1253
1392
|
log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
|
|
1254
|
-
const r =
|
|
1393
|
+
const r = spawnSync7("gh", ["auth", "login", "--web"], { stdio: "inherit" });
|
|
1255
1394
|
if (r.status !== 0) {
|
|
1256
1395
|
log.warn(`gh auth login exit ${r.status}. C\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
|
|
1257
1396
|
}
|
|
@@ -1391,7 +1530,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
|
|
|
1391
1530
|
}
|
|
1392
1531
|
let target = tag ?? null;
|
|
1393
1532
|
if (!target) {
|
|
1394
|
-
target = await latestTag(
|
|
1533
|
+
target = await latestTag(join11(projectRoot, TEAM_PACK_RELATIVE_PATH));
|
|
1395
1534
|
}
|
|
1396
1535
|
if (target) {
|
|
1397
1536
|
await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, target, projectRoot);
|
|
@@ -1399,7 +1538,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
|
|
|
1399
1538
|
return { pinnedTag: target };
|
|
1400
1539
|
}
|
|
1401
1540
|
async function readPinnedPackVersion(projectRoot) {
|
|
1402
|
-
const submoduleRoot =
|
|
1541
|
+
const submoduleRoot = join11(projectRoot, TEAM_PACK_RELATIVE_PATH);
|
|
1403
1542
|
const tag = await latestTag(submoduleRoot);
|
|
1404
1543
|
if (tag) return tag;
|
|
1405
1544
|
const sha = await currentCommitSha(submoduleRoot);
|
|
@@ -1413,14 +1552,14 @@ function isSshPermissionError(message) {
|
|
|
1413
1552
|
}
|
|
1414
1553
|
function triggerGhAuthLoginInteractive2() {
|
|
1415
1554
|
log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
|
|
1416
|
-
const r =
|
|
1555
|
+
const r = spawnSync8("gh", ["auth", "login", "--web"], { stdio: "inherit" });
|
|
1417
1556
|
if (r.status !== 0) {
|
|
1418
1557
|
log.warn(`gh auth login exit ${r.status}. C\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
|
|
1419
1558
|
}
|
|
1420
1559
|
}
|
|
1421
1560
|
function openGithubSshKeysPage() {
|
|
1422
1561
|
log.info("M\u1EDF trang GitHub Settings \u2192 SSH Keys...");
|
|
1423
|
-
const r =
|
|
1562
|
+
const r = spawnSync8("open", ["https://github.com/settings/keys"], { stdio: "ignore" });
|
|
1424
1563
|
if (r.status !== 0) {
|
|
1425
1564
|
log.info("URL: https://github.com/settings/keys");
|
|
1426
1565
|
}
|
|
@@ -1570,27 +1709,27 @@ ${renderAvatarBanner(opts)}
|
|
|
1570
1709
|
}
|
|
1571
1710
|
|
|
1572
1711
|
// src/lib/execute-gh-repo-create.ts
|
|
1573
|
-
import { spawnSync as
|
|
1712
|
+
import { spawnSync as spawnSync9 } from "child_process";
|
|
1574
1713
|
var RepoAlreadyExistsError = class extends Error {
|
|
1575
1714
|
constructor(fullName) {
|
|
1576
1715
|
super(`Repo "${fullName}" \u0111\xE3 t\u1ED3n t\u1EA1i tr\xEAn GitHub. \u0110\u1ED5i t\xEAn ho\u1EB7c x\xF3a repo c\u0169.`);
|
|
1577
1716
|
this.name = "RepoAlreadyExistsError";
|
|
1578
1717
|
}
|
|
1579
1718
|
};
|
|
1580
|
-
function executeGhRepoCreate(
|
|
1581
|
-
const fullName = `${
|
|
1719
|
+
function executeGhRepoCreate(input6) {
|
|
1720
|
+
const fullName = `${input6.org}/${input6.name}`;
|
|
1582
1721
|
const args = [
|
|
1583
1722
|
"repo",
|
|
1584
1723
|
"create",
|
|
1585
1724
|
fullName,
|
|
1586
|
-
`--${
|
|
1725
|
+
`--${input6.visibility}`,
|
|
1587
1726
|
"--source",
|
|
1588
|
-
|
|
1727
|
+
input6.folder,
|
|
1589
1728
|
"--remote",
|
|
1590
1729
|
"origin",
|
|
1591
1730
|
"--push"
|
|
1592
1731
|
];
|
|
1593
|
-
const r =
|
|
1732
|
+
const r = spawnSync9("gh", args, { stdio: "inherit" });
|
|
1594
1733
|
if (r.status !== 0) {
|
|
1595
1734
|
if (r.status === 1) {
|
|
1596
1735
|
throw new RepoAlreadyExistsError(fullName);
|
|
@@ -1604,9 +1743,9 @@ function executeGhRepoCreate(input5) {
|
|
|
1604
1743
|
}
|
|
1605
1744
|
|
|
1606
1745
|
// src/lib/resolve-github-username-default.ts
|
|
1607
|
-
import { spawnSync as
|
|
1746
|
+
import { spawnSync as spawnSync10 } from "child_process";
|
|
1608
1747
|
function resolveGithubUsernameDefault() {
|
|
1609
|
-
const r =
|
|
1748
|
+
const r = spawnSync10("gh", ["api", "user", "--jq", ".login"], {
|
|
1610
1749
|
encoding: "utf8",
|
|
1611
1750
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1612
1751
|
});
|
|
@@ -1638,28 +1777,28 @@ function validateRepoVisibility(v) {
|
|
|
1638
1777
|
}
|
|
1639
1778
|
|
|
1640
1779
|
// src/lib/create-github-remote-from-folder.ts
|
|
1641
|
-
function createGithubRemoteFromFolder(
|
|
1642
|
-
validateRepoName(
|
|
1643
|
-
validateRepoVisibility(
|
|
1644
|
-
const org =
|
|
1645
|
-
log.info(`T\u1EA1o GitHub repo ${org}/${
|
|
1780
|
+
function createGithubRemoteFromFolder(input6) {
|
|
1781
|
+
validateRepoName(input6.name);
|
|
1782
|
+
validateRepoVisibility(input6.visibility);
|
|
1783
|
+
const org = input6.org ?? resolveGithubUsernameDefault();
|
|
1784
|
+
log.info(`T\u1EA1o GitHub repo ${org}/${input6.name} (${input6.visibility})...`);
|
|
1646
1785
|
const urls = executeGhRepoCreate({
|
|
1647
|
-
folder:
|
|
1786
|
+
folder: input6.folder,
|
|
1648
1787
|
org,
|
|
1649
|
-
name:
|
|
1650
|
-
visibility:
|
|
1788
|
+
name: input6.name,
|
|
1789
|
+
visibility: input6.visibility
|
|
1651
1790
|
});
|
|
1652
1791
|
log.success(`\u0110\xE3 t\u1EA1o: ${urls.sshUrl}`);
|
|
1653
1792
|
return urls;
|
|
1654
1793
|
}
|
|
1655
1794
|
|
|
1656
1795
|
// src/lib/create-workspace-remote-via-gh.ts
|
|
1657
|
-
import { spawnSync as
|
|
1796
|
+
import { spawnSync as spawnSync18 } from "child_process";
|
|
1658
1797
|
|
|
1659
1798
|
// src/lib/check-gh-cli-auth-status.ts
|
|
1660
|
-
import { spawnSync as
|
|
1799
|
+
import { spawnSync as spawnSync11 } from "child_process";
|
|
1661
1800
|
function checkGhCliAuthStatus() {
|
|
1662
|
-
const r =
|
|
1801
|
+
const r = spawnSync11("gh", ["auth", "status"], { stdio: "ignore" });
|
|
1663
1802
|
if (r.error && r.error.code === "ENOENT") {
|
|
1664
1803
|
return "not-installed";
|
|
1665
1804
|
}
|
|
@@ -1667,12 +1806,12 @@ function checkGhCliAuthStatus() {
|
|
|
1667
1806
|
}
|
|
1668
1807
|
|
|
1669
1808
|
// src/lib/detect-package-manager.ts
|
|
1670
|
-
import { spawnSync as
|
|
1809
|
+
import { spawnSync as spawnSync12 } from "child_process";
|
|
1671
1810
|
function hasBinary(name) {
|
|
1672
1811
|
const platform2 = detectHostPlatform();
|
|
1673
1812
|
const probe = platform2 === "win32" ? "where" : "command";
|
|
1674
1813
|
const args = platform2 === "win32" ? [name] : ["-v", name];
|
|
1675
|
-
const r =
|
|
1814
|
+
const r = spawnSync12(probe, args, {
|
|
1676
1815
|
shell: platform2 !== "win32",
|
|
1677
1816
|
stdio: "ignore"
|
|
1678
1817
|
});
|
|
@@ -1688,11 +1827,11 @@ function detectPackageManager() {
|
|
|
1688
1827
|
}
|
|
1689
1828
|
|
|
1690
1829
|
// src/lib/handle-remote-access-failure-with-account-switch.ts
|
|
1691
|
-
import { spawnSync as
|
|
1692
|
-
import { input as
|
|
1830
|
+
import { spawnSync as spawnSync14 } from "child_process";
|
|
1831
|
+
import { input as input4, select as select6 } from "@inquirer/prompts";
|
|
1693
1832
|
|
|
1694
1833
|
// src/lib/verify-git-remote-accessible.ts
|
|
1695
|
-
import { spawnSync as
|
|
1834
|
+
import { spawnSync as spawnSync13 } from "child_process";
|
|
1696
1835
|
var TIMEOUT_MS = 5e3;
|
|
1697
1836
|
function classifyRemoteError(stderr) {
|
|
1698
1837
|
const text = stderr.toLowerCase();
|
|
@@ -1708,7 +1847,7 @@ function classifyRemoteError(stderr) {
|
|
|
1708
1847
|
return "unknown";
|
|
1709
1848
|
}
|
|
1710
1849
|
function tryVerifyGitRemoteAccessible(url) {
|
|
1711
|
-
const r =
|
|
1850
|
+
const r = spawnSync13("git", ["ls-remote", "--exit-code", url, "HEAD"], {
|
|
1712
1851
|
encoding: "utf8",
|
|
1713
1852
|
timeout: TIMEOUT_MS,
|
|
1714
1853
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -1730,7 +1869,7 @@ var RemoteAccessAbortedError = class extends Error {
|
|
|
1730
1869
|
}
|
|
1731
1870
|
};
|
|
1732
1871
|
function getCurrentGhUser2() {
|
|
1733
|
-
const r =
|
|
1872
|
+
const r = spawnSync14("gh", ["api", "user", "--jq", ".login"], {
|
|
1734
1873
|
encoding: "utf8",
|
|
1735
1874
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1736
1875
|
});
|
|
@@ -1739,7 +1878,7 @@ function getCurrentGhUser2() {
|
|
|
1739
1878
|
}
|
|
1740
1879
|
function triggerGhAuthLoginInteractive3() {
|
|
1741
1880
|
log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
|
|
1742
|
-
const r =
|
|
1881
|
+
const r = spawnSync14("gh", ["auth", "login", "--web"], { stdio: "inherit" });
|
|
1743
1882
|
if (r.status !== 0) {
|
|
1744
1883
|
log.warn(`gh auth login exit ${r.status}. B\u1EA1n c\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
|
|
1745
1884
|
}
|
|
@@ -1800,7 +1939,7 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
|
|
|
1800
1939
|
);
|
|
1801
1940
|
}
|
|
1802
1941
|
if (action === "re-input-url") {
|
|
1803
|
-
const newUrl = await
|
|
1942
|
+
const newUrl = await input4({
|
|
1804
1943
|
message: "URL git remote (https://github.com/owner/repo.git ho\u1EB7c git@github.com:owner/repo.git):",
|
|
1805
1944
|
default: currentUrl,
|
|
1806
1945
|
validate: (v) => isValidGitUrl(v) || "URL kh\xF4ng \u0111\xFAng format git remote"
|
|
@@ -1822,7 +1961,7 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
|
|
|
1822
1961
|
}
|
|
1823
1962
|
|
|
1824
1963
|
// src/lib/install-gh-cli-via-package-manager.ts
|
|
1825
|
-
import { spawnSync as
|
|
1964
|
+
import { spawnSync as spawnSync15 } from "child_process";
|
|
1826
1965
|
var INSTALL_COMMANDS = {
|
|
1827
1966
|
brew: { cmd: "brew", args: ["install", "gh"] },
|
|
1828
1967
|
apt: { cmd: "sudo", args: ["apt-get", "install", "-y", "gh"] },
|
|
@@ -1833,7 +1972,7 @@ var INSTALL_COMMANDS = {
|
|
|
1833
1972
|
function installGhCliViaPackageManager(pm) {
|
|
1834
1973
|
const spec = INSTALL_COMMANDS[pm];
|
|
1835
1974
|
log.info(`\u0110ang c\xE0i gh CLI qua ${pm}...`);
|
|
1836
|
-
const r =
|
|
1975
|
+
const r = spawnSync15(spec.cmd, spec.args, { stdio: "inherit" });
|
|
1837
1976
|
if (r.status !== 0) {
|
|
1838
1977
|
throw new Error(`C\xE0i gh CLI th\u1EA5t b\u1EA1i qua ${pm} (exit ${r.status}). C\xE0i tay r\u1ED3i ch\u1EA1y l\u1EA1i.`);
|
|
1839
1978
|
}
|
|
@@ -1841,9 +1980,9 @@ function installGhCliViaPackageManager(pm) {
|
|
|
1841
1980
|
}
|
|
1842
1981
|
|
|
1843
1982
|
// src/lib/setup-git-credential-via-gh.ts
|
|
1844
|
-
import { spawnSync as
|
|
1983
|
+
import { spawnSync as spawnSync16 } from "child_process";
|
|
1845
1984
|
function setupGitCredentialViaGh() {
|
|
1846
|
-
const r =
|
|
1985
|
+
const r = spawnSync16("gh", ["auth", "setup-git"], { stdio: "ignore" });
|
|
1847
1986
|
if (r.status !== 0) {
|
|
1848
1987
|
log.warn("gh auth setup-git fail (non-fatal). N\u1EBFu git clone l\u1ED7i 128 \u2192 ch\u1EA1y th\u1EE7 c\xF4ng.");
|
|
1849
1988
|
return;
|
|
@@ -1852,10 +1991,10 @@ function setupGitCredentialViaGh() {
|
|
|
1852
1991
|
}
|
|
1853
1992
|
|
|
1854
1993
|
// src/lib/trigger-gh-cli-auth-login.ts
|
|
1855
|
-
import { spawnSync as
|
|
1994
|
+
import { spawnSync as spawnSync17 } from "child_process";
|
|
1856
1995
|
function triggerGhCliAuthLogin() {
|
|
1857
1996
|
log.info("Kh\u1EDFi \u0111\u1ED9ng \u0111\u0103ng nh\u1EADp GitHub qua gh CLI (browser s\u1EBD m\u1EDF)...");
|
|
1858
|
-
const r =
|
|
1997
|
+
const r = spawnSync17(
|
|
1859
1998
|
"gh",
|
|
1860
1999
|
["auth", "login", "--hostname", "github.com", "--web", "--git-protocol", "ssh"],
|
|
1861
2000
|
{ stdio: "inherit" }
|
|
@@ -1973,20 +2112,20 @@ function classifyGhCreateError(stderr) {
|
|
|
1973
2112
|
return "unknown";
|
|
1974
2113
|
}
|
|
1975
2114
|
function repoExistsOnGitHub(fullName) {
|
|
1976
|
-
const r =
|
|
2115
|
+
const r = spawnSync18("gh", ["repo", "view", fullName, "--json", "name"], {
|
|
1977
2116
|
stdio: "ignore"
|
|
1978
2117
|
});
|
|
1979
2118
|
return r.status === 0;
|
|
1980
2119
|
}
|
|
1981
2120
|
function canCreateInNamespace(org, ghUser) {
|
|
1982
2121
|
if (org.toLowerCase() === ghUser.toLowerCase()) return { ok: true };
|
|
1983
|
-
const r =
|
|
2122
|
+
const r = spawnSync18("gh", ["api", `orgs/${org}/members/${ghUser}`, "--silent"], {
|
|
1984
2123
|
stdio: "ignore"
|
|
1985
2124
|
});
|
|
1986
2125
|
if (r.status === 0) return { ok: true };
|
|
1987
|
-
const orgCheck =
|
|
2126
|
+
const orgCheck = spawnSync18("gh", ["api", `orgs/${org}`, "--silent"], { stdio: "ignore" });
|
|
1988
2127
|
if (orgCheck.status !== 0) {
|
|
1989
|
-
const userCheck =
|
|
2128
|
+
const userCheck = spawnSync18("gh", ["api", `users/${org}`, "--silent"], { stdio: "ignore" });
|
|
1990
2129
|
if (userCheck.status === 0) {
|
|
1991
2130
|
return {
|
|
1992
2131
|
ok: false,
|
|
@@ -2003,17 +2142,17 @@ function canCreateInNamespace(org, ghUser) {
|
|
|
2003
2142
|
reason: `'${ghUser}' kh\xF4ng ph\u1EA3i member c\u1EE7a org '${org}'. Li\xEAn h\u1EC7 admin org \u0111\u1EC3 \u0111\u01B0\u1EE3c invite, ho\u1EB7c pass --repo-org=${ghUser} t\u1EA1o d\u01B0\u1EDBi personal account.`
|
|
2004
2143
|
};
|
|
2005
2144
|
}
|
|
2006
|
-
async function createWorkspaceRemoteViaGh(
|
|
2007
|
-
validateRepoName(
|
|
2008
|
-
validateRepoVisibility(
|
|
2145
|
+
async function createWorkspaceRemoteViaGh(input6) {
|
|
2146
|
+
validateRepoName(input6.workspaceName);
|
|
2147
|
+
validateRepoVisibility(input6.visibility);
|
|
2009
2148
|
await ensureGitHubReady();
|
|
2010
2149
|
const ghUser = resolveGithubUsernameDefault();
|
|
2011
|
-
const org =
|
|
2150
|
+
const org = input6.org ?? ghUser;
|
|
2012
2151
|
const namespaceCheck = canCreateInNamespace(org, ghUser);
|
|
2013
2152
|
if (!namespaceCheck.ok) {
|
|
2014
2153
|
throw new Error(`Kh\xF4ng th\u1EC3 t\u1EA1o repo d\u01B0\u1EDBi '${org}/': ${namespaceCheck.reason}`);
|
|
2015
2154
|
}
|
|
2016
|
-
const fullName = `${org}/${
|
|
2155
|
+
const fullName = `${org}/${input6.workspaceName}`;
|
|
2017
2156
|
if (repoExistsOnGitHub(fullName)) {
|
|
2018
2157
|
throw new CreateWorkspaceRemoteError(
|
|
2019
2158
|
"repo-exists",
|
|
@@ -2021,16 +2160,16 @@ async function createWorkspaceRemoteViaGh(input5) {
|
|
|
2021
2160
|
`Repo '${fullName}' \u0111\xE3 t\u1ED3n t\u1EA1i tr\xEAn GitHub. C\xF3 th\u1EC3 b\u1EA1n \u0111\xE3 init workspace n\xE0y tr\u01B0\u1EDBc \u0111\xF3.`
|
|
2022
2161
|
);
|
|
2023
2162
|
}
|
|
2024
|
-
log.info(`T\u1EA1o GitHub repo cho workspace: ${fullName} (${
|
|
2025
|
-
const r =
|
|
2163
|
+
log.info(`T\u1EA1o GitHub repo cho workspace: ${fullName} (${input6.visibility})...`);
|
|
2164
|
+
const r = spawnSync18(
|
|
2026
2165
|
"gh",
|
|
2027
2166
|
[
|
|
2028
2167
|
"repo",
|
|
2029
2168
|
"create",
|
|
2030
2169
|
fullName,
|
|
2031
|
-
`--${
|
|
2170
|
+
`--${input6.visibility}`,
|
|
2032
2171
|
"--source",
|
|
2033
|
-
|
|
2172
|
+
input6.workspacePath,
|
|
2034
2173
|
"--remote",
|
|
2035
2174
|
"origin",
|
|
2036
2175
|
"--push"
|
|
@@ -2063,7 +2202,7 @@ ${combined}
|
|
|
2063
2202
|
function linkExistingRemoteToWorkspace(args) {
|
|
2064
2203
|
const sshUrl = `git@github.com:${args.fullName}.git`;
|
|
2065
2204
|
const httpsUrl = `https://github.com/${args.fullName}.git`;
|
|
2066
|
-
const addResult =
|
|
2205
|
+
const addResult = spawnSync18(
|
|
2067
2206
|
"git",
|
|
2068
2207
|
["-C", args.workspacePath, "remote", "add", "origin", sshUrl],
|
|
2069
2208
|
{
|
|
@@ -2072,7 +2211,7 @@ function linkExistingRemoteToWorkspace(args) {
|
|
|
2072
2211
|
}
|
|
2073
2212
|
);
|
|
2074
2213
|
if (addResult.status !== 0) {
|
|
2075
|
-
|
|
2214
|
+
spawnSync18("git", ["-C", args.workspacePath, "remote", "set-url", "origin", sshUrl], {
|
|
2076
2215
|
stdio: "ignore"
|
|
2077
2216
|
});
|
|
2078
2217
|
}
|
|
@@ -2086,11 +2225,11 @@ import { select as select7 } from "@inquirer/prompts";
|
|
|
2086
2225
|
import { simpleGit as simpleGit3 } from "simple-git";
|
|
2087
2226
|
|
|
2088
2227
|
// src/lib/check-folder-has-git.ts
|
|
2089
|
-
import { existsSync as
|
|
2090
|
-
import { join as
|
|
2228
|
+
import { existsSync as existsSync3, statSync } from "fs";
|
|
2229
|
+
import { join as join12 } from "path";
|
|
2091
2230
|
function checkFolderHasGit(folderPath) {
|
|
2092
|
-
const gitPath =
|
|
2093
|
-
if (!
|
|
2231
|
+
const gitPath = join12(folderPath, ".git");
|
|
2232
|
+
if (!existsSync3(gitPath)) return false;
|
|
2094
2233
|
const stat = statSync(gitPath);
|
|
2095
2234
|
return stat.isDirectory() || stat.isFile();
|
|
2096
2235
|
}
|
|
@@ -2120,8 +2259,8 @@ async function createInitialGitCommit(folderPath) {
|
|
|
2120
2259
|
}
|
|
2121
2260
|
|
|
2122
2261
|
// src/lib/detect-folder-tech-stack.ts
|
|
2123
|
-
import { existsSync as
|
|
2124
|
-
import { join as
|
|
2262
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2263
|
+
import { join as join13 } from "path";
|
|
2125
2264
|
var SIGNATURES = {
|
|
2126
2265
|
node: ["package.json"],
|
|
2127
2266
|
python: ["pyproject.toml", "requirements.txt", "setup.py", "Pipfile"],
|
|
@@ -2133,7 +2272,7 @@ var SIGNATURES = {
|
|
|
2133
2272
|
function detectFolderTechStack(folderPath) {
|
|
2134
2273
|
const matched = [];
|
|
2135
2274
|
for (const [stack, files] of Object.entries(SIGNATURES)) {
|
|
2136
|
-
if (files.some((f) =>
|
|
2275
|
+
if (files.some((f) => existsSync4(join13(folderPath, f)))) {
|
|
2137
2276
|
matched.push(stack);
|
|
2138
2277
|
}
|
|
2139
2278
|
}
|
|
@@ -2142,25 +2281,25 @@ function detectFolderTechStack(folderPath) {
|
|
|
2142
2281
|
|
|
2143
2282
|
// src/lib/gitignore-template-loader.ts
|
|
2144
2283
|
import { readFileSync as readFileSync2 } from "fs";
|
|
2145
|
-
import { dirname as dirname3, join as
|
|
2284
|
+
import { dirname as dirname3, join as join14 } from "path";
|
|
2146
2285
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2147
2286
|
var __dirname = dirname3(fileURLToPath2(import.meta.url));
|
|
2148
2287
|
var CANDIDATE_DIRS = [
|
|
2149
2288
|
// Bundled production: dist/index.js → __dirname = .../dist/, sibling dist/templates
|
|
2150
|
-
|
|
2289
|
+
join14(__dirname, "templates", "gitignore"),
|
|
2151
2290
|
// Legacy bundled: nếu file là dist/lib/*.js (sub-bundle), templates ở dist/templates
|
|
2152
|
-
|
|
2291
|
+
join14(__dirname, "..", "templates", "gitignore"),
|
|
2153
2292
|
// Dev mode (vitest/tsx run src/ trực tiếp): __dirname = src/lib/
|
|
2154
|
-
|
|
2293
|
+
join14(__dirname, "..", "..", "src", "templates", "gitignore"),
|
|
2155
2294
|
// npm-installed alt: __dirname = .../dist/ → package_root/src/templates
|
|
2156
|
-
|
|
2295
|
+
join14(__dirname, "..", "src", "templates", "gitignore")
|
|
2157
2296
|
];
|
|
2158
2297
|
var AVATAR_MARKER_START = "# === avatar ===";
|
|
2159
2298
|
var AVATAR_MARKER_END = "# === /avatar ===";
|
|
2160
2299
|
function readTemplate(stack) {
|
|
2161
2300
|
for (const dir of CANDIDATE_DIRS) {
|
|
2162
2301
|
try {
|
|
2163
|
-
return readFileSync2(
|
|
2302
|
+
return readFileSync2(join14(dir, `${stack}.txt`), "utf8");
|
|
2164
2303
|
} catch {
|
|
2165
2304
|
}
|
|
2166
2305
|
}
|
|
@@ -2174,11 +2313,11 @@ ${readTemplate(s).trim()}`);
|
|
|
2174
2313
|
}
|
|
2175
2314
|
|
|
2176
2315
|
// src/lib/write-or-merge-gitignore.ts
|
|
2177
|
-
import { existsSync as
|
|
2178
|
-
import { join as
|
|
2316
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
2317
|
+
import { join as join15 } from "path";
|
|
2179
2318
|
function writeOrMergeGitignore(folderPath, avatarBlock) {
|
|
2180
|
-
const path =
|
|
2181
|
-
if (!
|
|
2319
|
+
const path = join15(folderPath, ".gitignore");
|
|
2320
|
+
if (!existsSync5(path)) {
|
|
2182
2321
|
writeFileSync(path, avatarBlock, "utf8");
|
|
2183
2322
|
return;
|
|
2184
2323
|
}
|
|
@@ -2354,7 +2493,7 @@ async function safeBootstrapGitInFolder(folderPath, opts = {}) {
|
|
|
2354
2493
|
|
|
2355
2494
|
// src/commands/init-conflict-detection-helpers.ts
|
|
2356
2495
|
import { readdir } from "fs/promises";
|
|
2357
|
-
import { join as
|
|
2496
|
+
import { join as join16 } from "path";
|
|
2358
2497
|
async function isEmptyOrMissing(path) {
|
|
2359
2498
|
if (!await pathExists(path)) return true;
|
|
2360
2499
|
try {
|
|
@@ -2367,7 +2506,7 @@ async function isEmptyOrMissing(path) {
|
|
|
2367
2506
|
}
|
|
2368
2507
|
async function findAlternativeWorkspaceName(parent, desiredName, maxAttempts = 10) {
|
|
2369
2508
|
for (let i = 2; i < maxAttempts; i++) {
|
|
2370
|
-
const candidate =
|
|
2509
|
+
const candidate = join16(parent, `${desiredName}-${i}`);
|
|
2371
2510
|
if (await isEmptyOrMissing(candidate)) return candidate;
|
|
2372
2511
|
}
|
|
2373
2512
|
return null;
|
|
@@ -2678,7 +2817,7 @@ async function promptProjectStatus() {
|
|
|
2678
2817
|
});
|
|
2679
2818
|
}
|
|
2680
2819
|
async function runInitFromExistingRemote(opts, ownerEmail) {
|
|
2681
|
-
const initialRemoteUrl = opts.clientRepo ?? await
|
|
2820
|
+
const initialRemoteUrl = opts.clientRepo ?? await input5({
|
|
2682
2821
|
message: "URL git c\u1EE7a repo:",
|
|
2683
2822
|
validate: (v) => v.length > 0 ? true : "URL b\u1EAFt bu\u1ED9c"
|
|
2684
2823
|
});
|
|
@@ -2686,7 +2825,7 @@ async function runInitFromExistingRemote(opts, ownerEmail) {
|
|
|
2686
2825
|
const remoteUrl = resolvedRemoteUrl ?? initialRemoteUrl;
|
|
2687
2826
|
const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
|
|
2688
2827
|
const inferredName = inferWorkspaceName(remoteUrl);
|
|
2689
|
-
const workspaceName = opts.workspaceName ?? await
|
|
2828
|
+
const workspaceName = opts.workspaceName ?? await input5({ message: "T\xEAn workspace:", default: inferredName });
|
|
2690
2829
|
const workspaceParent = resolve(opts.workspaceParent ?? ".");
|
|
2691
2830
|
const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);
|
|
2692
2831
|
await scaffoldWorkspaceWithSrcSubmodule({
|
|
@@ -2709,7 +2848,7 @@ async function runInitFromExistingRemote(opts, ownerEmail) {
|
|
|
2709
2848
|
}
|
|
2710
2849
|
async function runInitFromExistingFolder(opts, ownerEmail) {
|
|
2711
2850
|
const folderPath = resolve(
|
|
2712
|
-
opts.folderPath ?? await
|
|
2851
|
+
opts.folderPath ?? await input5({
|
|
2713
2852
|
message: "\u0110\u01B0\u1EDDng d\u1EABn folder hi\u1EC7n c\xF3:",
|
|
2714
2853
|
validate: (v) => v.length > 0 ? true : "Path b\u1EAFt bu\u1ED9c"
|
|
2715
2854
|
})
|
|
@@ -2721,7 +2860,7 @@ async function runInitFromExistingFolder(opts, ownerEmail) {
|
|
|
2721
2860
|
const remoteUrl = await getOrCreateOriginRemote(folderPath, opts);
|
|
2722
2861
|
const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
|
|
2723
2862
|
const inferredName = opts.workspaceName ?? `${basename(folderPath)}-avatar-workspace`;
|
|
2724
|
-
const workspaceName = opts.workspaceName ?? await
|
|
2863
|
+
const workspaceName = opts.workspaceName ?? await input5({ message: "T\xEAn workspace:", default: inferredName });
|
|
2725
2864
|
const workspaceParent = resolve(opts.workspaceParent ?? ".");
|
|
2726
2865
|
const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);
|
|
2727
2866
|
await scaffoldWorkspaceWithSrcSubmodule({
|
|
@@ -2745,7 +2884,7 @@ async function runInitFromExistingFolder(opts, ownerEmail) {
|
|
|
2745
2884
|
}
|
|
2746
2885
|
async function runInitFromScratch(opts, ownerEmail) {
|
|
2747
2886
|
await ensureGitHubReady();
|
|
2748
|
-
const projectName = opts.workspaceName ?? await
|
|
2887
|
+
const projectName = opts.workspaceName ?? await input5({
|
|
2749
2888
|
message: "T\xEAn d\u1EF1 \xE1n:",
|
|
2750
2889
|
validate: (v) => v.length > 0 ? true : "T\xEAn b\u1EAFt bu\u1ED9c"
|
|
2751
2890
|
});
|
|
@@ -2759,7 +2898,7 @@ async function runInitFromScratch(opts, ownerEmail) {
|
|
|
2759
2898
|
const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
|
|
2760
2899
|
const workspaceParent = resolve(opts.workspaceParent ?? ".");
|
|
2761
2900
|
const workspacePath = await resolveWorkspacePath(workspaceParent, projectName, opts.force);
|
|
2762
|
-
const srcPath =
|
|
2901
|
+
const srcPath = join17(workspacePath, "src");
|
|
2763
2902
|
await ensureDir(workspacePath);
|
|
2764
2903
|
await ensureDir(srcPath);
|
|
2765
2904
|
await safeBootstrapGitInFolder(srcPath, { autoYes: true });
|
|
@@ -2830,7 +2969,7 @@ async function getOrCreateOriginRemote(folderPath, opts) {
|
|
|
2830
2969
|
{ name: "public", value: "public" }
|
|
2831
2970
|
]
|
|
2832
2971
|
});
|
|
2833
|
-
const repoName = await
|
|
2972
|
+
const repoName = await input5({
|
|
2834
2973
|
message: "T\xEAn repo:",
|
|
2835
2974
|
default: basename(folderPath)
|
|
2836
2975
|
});
|
|
@@ -2895,10 +3034,10 @@ async function finalizeWorkspaceScaffold(args) {
|
|
|
2895
3034
|
await writeRootClaudeMd(args.workspacePath, vars);
|
|
2896
3035
|
await writeProjectSettings(args.workspacePath, vars);
|
|
2897
3036
|
await appendGitignoreEntries(args.workspacePath);
|
|
2898
|
-
await ensureDir(
|
|
2899
|
-
await ensureDir(
|
|
2900
|
-
await installGitHook(
|
|
2901
|
-
await installGitHook(
|
|
3037
|
+
await ensureDir(join17(args.workspacePath, "notes"));
|
|
3038
|
+
await ensureDir(join17(args.workspacePath, "scripts"));
|
|
3039
|
+
await installGitHook(join17(args.workspacePath, ".git"), "post-merge");
|
|
3040
|
+
await installGitHook(join17(args.workspacePath, ".git", "modules", "src"), "pre-push");
|
|
2902
3041
|
log.success("C\xE0i post-merge (workspace) + pre-push (src/)");
|
|
2903
3042
|
await appendAuditEntry("init", `flow=${args.flow},workspace=${args.workspaceName}`);
|
|
2904
3043
|
await maybeCommitWorkspace(args.workspacePath, args.skipCommit);
|
|
@@ -2973,7 +3112,7 @@ async function maybeCreateWorkspaceRemote(args) {
|
|
|
2973
3112
|
});
|
|
2974
3113
|
return;
|
|
2975
3114
|
}
|
|
2976
|
-
const newName = await
|
|
3115
|
+
const newName = await input5({
|
|
2977
3116
|
message: "T\xEAn workspace m\u1EDBi (s\u1EBD t\u1EA1o repo new):",
|
|
2978
3117
|
validate: (v) => v.trim().length > 0 ? true : "T\xEAn kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng"
|
|
2979
3118
|
});
|
|
@@ -2998,7 +3137,7 @@ async function maybeCreateWorkspaceRemote(args) {
|
|
|
2998
3137
|
}
|
|
2999
3138
|
}
|
|
3000
3139
|
async function resolveWorkspacePath(parent, desiredName, force) {
|
|
3001
|
-
const desired =
|
|
3140
|
+
const desired = join17(parent, desiredName);
|
|
3002
3141
|
if (await isEmptyOrMissing(desired)) return desired;
|
|
3003
3142
|
log.warn(`Workspace path "${desired}" \u0111\xE3 c\xF3 n\u1ED9i dung.`);
|
|
3004
3143
|
while (true) {
|
|
@@ -3025,17 +3164,17 @@ async function resolveWorkspacePath(parent, desiredName, force) {
|
|
|
3025
3164
|
if (action === "use-alt" && alternative) {
|
|
3026
3165
|
return alternative;
|
|
3027
3166
|
}
|
|
3028
|
-
const newName = await
|
|
3167
|
+
const newName = await input5({
|
|
3029
3168
|
message: "T\xEAn workspace m\u1EDBi:",
|
|
3030
3169
|
validate: (v) => v.trim().length > 0 ? true : "T\xEAn kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng"
|
|
3031
3170
|
});
|
|
3032
|
-
const newPath =
|
|
3171
|
+
const newPath = join17(parent, newName.trim());
|
|
3033
3172
|
if (await isEmptyOrMissing(newPath)) return newPath;
|
|
3034
3173
|
log.warn(`"${newPath}" c\u0169ng \u0111\xE3 c\xF3 n\u1ED9i dung. Th\u1EED t\xEAn kh\xE1c.`);
|
|
3035
3174
|
}
|
|
3036
3175
|
}
|
|
3037
3176
|
async function promptTeamOwner(currentUserEmail) {
|
|
3038
|
-
return await
|
|
3177
|
+
return await input5({ message: "Team owner email:", default: currentUserEmail });
|
|
3039
3178
|
}
|
|
3040
3179
|
async function maybeCommitWorkspace(workspacePath, skipCommit) {
|
|
3041
3180
|
if (skipCommit) {
|
|
@@ -3075,6 +3214,22 @@ function printInitSuccessBox(rootPath, flow, aiResult = null) {
|
|
|
3075
3214
|
`);
|
|
3076
3215
|
}
|
|
3077
3216
|
|
|
3217
|
+
// src/lib/not-implemented-stub.ts
|
|
3218
|
+
function notImplementedYet(commandName, milestone) {
|
|
3219
|
+
return () => {
|
|
3220
|
+
process.stdout.write(
|
|
3221
|
+
`${chalk.yellow("\u23F3")} ${chalk.bold(`avatar ${commandName}`)} \u2014 ch\u01B0a implement \u1EDF milestone hi\u1EC7n t\u1EA1i.
|
|
3222
|
+
`
|
|
3223
|
+
);
|
|
3224
|
+
if (milestone) {
|
|
3225
|
+
process.stdout.write(` D\u1EF1 ki\u1EBFn: ${chalk.cyan(milestone)}
|
|
3226
|
+
`);
|
|
3227
|
+
}
|
|
3228
|
+
process.stdout.write(" Spec \u0111\xE3 c\xF3 trong avatar-cli-implementation_4.html.\n");
|
|
3229
|
+
process.exit(0);
|
|
3230
|
+
};
|
|
3231
|
+
}
|
|
3232
|
+
|
|
3078
3233
|
// src/commands/mcp-run.ts
|
|
3079
3234
|
function registerMcpRunCommand(program2) {
|
|
3080
3235
|
program2.command("mcp-run <tool-id>", { hidden: true }).description("[internal] Spawn MCP v\u1EDBi secrets injected (M09)").action(notImplementedYet("mcp-run", "Milestone 09"));
|
|
@@ -3107,15 +3262,15 @@ function registerSecretsCommand(program2) {
|
|
|
3107
3262
|
|
|
3108
3263
|
// src/commands/status.ts
|
|
3109
3264
|
import { promises as fs8 } from "fs";
|
|
3110
|
-
import { join as
|
|
3265
|
+
import { join as join19 } from "path";
|
|
3111
3266
|
import boxen5 from "boxen";
|
|
3112
3267
|
|
|
3113
3268
|
// src/lib/pack-backup-manager.ts
|
|
3114
3269
|
import { promises as fs7 } from "fs";
|
|
3115
|
-
import { join as
|
|
3270
|
+
import { join as join18 } from "path";
|
|
3116
3271
|
var BACKUP_DIR_NAME = "_backup";
|
|
3117
3272
|
async function listBackups(projectRoot) {
|
|
3118
|
-
const dir =
|
|
3273
|
+
const dir = join18(projectRoot, ".claude", BACKUP_DIR_NAME);
|
|
3119
3274
|
if (!await pathExists(dir)) return [];
|
|
3120
3275
|
const entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
3121
3276
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort().reverse();
|
|
@@ -3141,7 +3296,7 @@ function registerStatusCommand(program2) {
|
|
|
3141
3296
|
}
|
|
3142
3297
|
async function gatherStatus(cwd) {
|
|
3143
3298
|
const projectName = cwd.split("/").filter(Boolean).pop() ?? "unknown";
|
|
3144
|
-
const claudeRoot =
|
|
3299
|
+
const claudeRoot = join19(cwd, ".claude");
|
|
3145
3300
|
const hasAvatar = await pathExists(claudeRoot);
|
|
3146
3301
|
if (!hasAvatar) {
|
|
3147
3302
|
return {
|
|
@@ -3154,8 +3309,8 @@ async function gatherStatus(cwd) {
|
|
|
3154
3309
|
hasAvatar: false
|
|
3155
3310
|
};
|
|
3156
3311
|
}
|
|
3157
|
-
const packVersion = await isGitRepo(
|
|
3158
|
-
const pendingDir =
|
|
3312
|
+
const packVersion = await isGitRepo(join19(claudeRoot, "pack")) ? await readPinnedPackVersion(cwd).catch(() => null) : null;
|
|
3313
|
+
const pendingDir = join19(claudeRoot, "_pending");
|
|
3159
3314
|
const pendingCount = await pathExists(pendingDir) ? (await fs8.readdir(pendingDir)).filter((n) => n.endsWith(".diff.md")).length : 0;
|
|
3160
3315
|
const backupCount = (await listBackups(cwd)).length;
|
|
3161
3316
|
const techStackSummary = await readTechStackFirstLine(claudeRoot);
|
|
@@ -3170,7 +3325,7 @@ async function gatherStatus(cwd) {
|
|
|
3170
3325
|
};
|
|
3171
3326
|
}
|
|
3172
3327
|
async function readTechStackFirstLine(claudeRoot) {
|
|
3173
|
-
const techStackPath =
|
|
3328
|
+
const techStackPath = join19(claudeRoot, "project", "tech-stack.md");
|
|
3174
3329
|
if (!await pathExists(techStackPath)) return "(no tech-stack.md)";
|
|
3175
3330
|
const content = await readText(techStackPath);
|
|
3176
3331
|
const firstNonHeaderLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#") && !l.startsWith(">"));
|
|
@@ -3211,27 +3366,27 @@ import boxen6 from "boxen";
|
|
|
3211
3366
|
// src/lib/create-uninstall-backup-snapshot.ts
|
|
3212
3367
|
import { cp, mkdir, writeFile } from "fs/promises";
|
|
3213
3368
|
import { homedir as homedir3 } from "os";
|
|
3214
|
-
import { basename as basename2, join as
|
|
3215
|
-
var UNINSTALL_BACKUPS_DIR =
|
|
3369
|
+
import { basename as basename2, join as join20 } from "path";
|
|
3370
|
+
var UNINSTALL_BACKUPS_DIR = join20(homedir3(), ".avatar", "uninstall-backups");
|
|
3216
3371
|
async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersion) {
|
|
3217
3372
|
const projectName = basename2(projectRoot);
|
|
3218
3373
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3219
|
-
const backupDir =
|
|
3374
|
+
const backupDir = join20(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp}`);
|
|
3220
3375
|
await mkdir(backupDir, { recursive: true, mode: 448 });
|
|
3221
3376
|
if (artifacts.claudeDir) {
|
|
3222
|
-
await cp(artifacts.claudeDir,
|
|
3377
|
+
await cp(artifacts.claudeDir, join20(backupDir, ".claude"), { recursive: true });
|
|
3223
3378
|
}
|
|
3224
3379
|
if (artifacts.claudeMd) {
|
|
3225
|
-
await cp(artifacts.claudeMd,
|
|
3380
|
+
await cp(artifacts.claudeMd, join20(backupDir, "CLAUDE.md"));
|
|
3226
3381
|
}
|
|
3227
3382
|
if (artifacts.postMergeHook || artifacts.prePushHook) {
|
|
3228
|
-
const hooksBackupDir =
|
|
3383
|
+
const hooksBackupDir = join20(backupDir, "hooks");
|
|
3229
3384
|
await mkdir(hooksBackupDir, { recursive: true });
|
|
3230
3385
|
if (artifacts.postMergeHook) {
|
|
3231
|
-
await cp(artifacts.postMergeHook,
|
|
3386
|
+
await cp(artifacts.postMergeHook, join20(hooksBackupDir, "post-merge"));
|
|
3232
3387
|
}
|
|
3233
3388
|
if (artifacts.prePushHook) {
|
|
3234
|
-
await cp(artifacts.prePushHook,
|
|
3389
|
+
await cp(artifacts.prePushHook, join20(hooksBackupDir, "pre-push"));
|
|
3235
3390
|
}
|
|
3236
3391
|
}
|
|
3237
3392
|
const manifest = {
|
|
@@ -3246,27 +3401,27 @@ async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersi
|
|
|
3246
3401
|
prePushHook: !!artifacts.prePushHook
|
|
3247
3402
|
}
|
|
3248
3403
|
};
|
|
3249
|
-
await writeFile(
|
|
3404
|
+
await writeFile(join20(backupDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
|
|
3250
3405
|
return backupDir;
|
|
3251
3406
|
}
|
|
3252
3407
|
|
|
3253
3408
|
// src/lib/detect-avatar-project-artifacts.ts
|
|
3254
|
-
import { existsSync as
|
|
3255
|
-
import { join as
|
|
3409
|
+
import { existsSync as existsSync6 } from "fs";
|
|
3410
|
+
import { join as join21 } from "path";
|
|
3256
3411
|
function existsOrNull(path) {
|
|
3257
|
-
return
|
|
3412
|
+
return existsSync6(path) ? path : null;
|
|
3258
3413
|
}
|
|
3259
3414
|
function detectAvatarProjectArtifacts(projectRoot) {
|
|
3260
|
-
const claudeDir = existsOrNull(
|
|
3261
|
-
const claudeMd = existsOrNull(
|
|
3262
|
-
const postMergeHook = existsOrNull(
|
|
3415
|
+
const claudeDir = existsOrNull(join21(projectRoot, ".claude"));
|
|
3416
|
+
const claudeMd = existsOrNull(join21(projectRoot, "CLAUDE.md"));
|
|
3417
|
+
const postMergeHook = existsOrNull(join21(projectRoot, ".git", "hooks", "post-merge"));
|
|
3263
3418
|
const prePushHook = existsOrNull(
|
|
3264
|
-
|
|
3419
|
+
join21(projectRoot, ".git", "modules", "src", "hooks", "pre-push")
|
|
3265
3420
|
);
|
|
3266
|
-
const gitignorePath = existsOrNull(
|
|
3267
|
-
const gitmodulesPath = existsOrNull(
|
|
3268
|
-
const notesDir = existsOrNull(
|
|
3269
|
-
const scriptsDir = existsOrNull(
|
|
3421
|
+
const gitignorePath = existsOrNull(join21(projectRoot, ".gitignore"));
|
|
3422
|
+
const gitmodulesPath = existsOrNull(join21(projectRoot, ".gitmodules"));
|
|
3423
|
+
const notesDir = existsOrNull(join21(projectRoot, "notes"));
|
|
3424
|
+
const scriptsDir = existsOrNull(join21(projectRoot, "scripts"));
|
|
3270
3425
|
const hasAnyArtifact = !!(claudeDir || claudeMd || postMergeHook || prePushHook);
|
|
3271
3426
|
return {
|
|
3272
3427
|
hasAnyArtifact,
|
|
@@ -3287,11 +3442,11 @@ async function executeUninstallDeletion(artifacts, flags) {
|
|
|
3287
3442
|
if (artifacts.claudeDir) {
|
|
3288
3443
|
if (flags.keepSubmodule) {
|
|
3289
3444
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
3290
|
-
const { join:
|
|
3445
|
+
const { join: join22 } = await import("path");
|
|
3291
3446
|
const entries = await readdir2(artifacts.claudeDir);
|
|
3292
3447
|
for (const entry of entries) {
|
|
3293
3448
|
if (entry === "pack") continue;
|
|
3294
|
-
await rm(
|
|
3449
|
+
await rm(join22(artifacts.claudeDir, entry), { recursive: true, force: true });
|
|
3295
3450
|
}
|
|
3296
3451
|
} else {
|
|
3297
3452
|
await rm(artifacts.claudeDir, { recursive: true, force: true });
|
|
@@ -3360,7 +3515,7 @@ async function removeSubmoduleEntry(gitmodulesPath, submodulePath) {
|
|
|
3360
3515
|
}
|
|
3361
3516
|
|
|
3362
3517
|
// src/commands/uninstall.ts
|
|
3363
|
-
var CLI_VERSION = "1.
|
|
3518
|
+
var CLI_VERSION = "1.3.0";
|
|
3364
3519
|
function registerUninstallCommand(program2) {
|
|
3365
3520
|
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) => {
|
|
3366
3521
|
try {
|
|
@@ -3442,7 +3597,7 @@ function printUninstallSuccessBox(backupPath) {
|
|
|
3442
3597
|
}
|
|
3443
3598
|
|
|
3444
3599
|
// src/index.ts
|
|
3445
|
-
var CLI_VERSION2 = "1.
|
|
3600
|
+
var CLI_VERSION2 = "1.3.0";
|
|
3446
3601
|
var program = new Command();
|
|
3447
3602
|
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(
|
|
3448
3603
|
"beforeAll",
|