@nalvietnam/avatar-cli 1.6.4 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -761,6 +761,14 @@ async function writeClaudeSettings(workspacePath, input6) {
|
|
|
761
761
|
|
|
762
762
|
// src/lib/run-ai-setup-phase.ts
|
|
763
763
|
var SUBSCRIPTION_DEFAULT_MODEL = "sonnet";
|
|
764
|
+
function warnAboutPlaintextSecret(providerLabel) {
|
|
765
|
+
log.warn(
|
|
766
|
+
`\u{1F512} ${providerLabel} key \u0111\xE3 l\u01B0u PLAINTEXT v\xE0o .claude/settings.json.
|
|
767
|
+
File n\xE0y \u0111\u01B0\u1EE3c gitignore t\u1EEB Avatar v1.7.0, nh\u01B0ng workspace c\u0169 c\xF3 th\u1EC3 CH\u01AFA.
|
|
768
|
+
Ki\u1EC3m tra: grep '.claude/settings.json' .gitignore
|
|
769
|
+
N\u1EBFu ch\u01B0a c\xF3 \u2192 TH\xCAM NGAY, tr\xE1nh leak key khi commit/push.`
|
|
770
|
+
);
|
|
771
|
+
}
|
|
764
772
|
async function runAiSetupPhase(args) {
|
|
765
773
|
try {
|
|
766
774
|
log.info("Setup AI provider cho workspace...");
|
|
@@ -819,6 +827,7 @@ async function runAiSetupPhase(args) {
|
|
|
819
827
|
`provider=llmlite,result=ok,model=${llmConfig.model},base=${llmConfig.baseUrl}`
|
|
820
828
|
);
|
|
821
829
|
log.success(`AI ready \xB7 LLMLite \xB7 model=${llmConfig.model} \xB7 ${llmConfig.baseUrl}`);
|
|
830
|
+
warnAboutPlaintextSecret("LLMLite");
|
|
822
831
|
return { ok: true, provider: "llmlite", model: llmConfig.model };
|
|
823
832
|
}
|
|
824
833
|
case "anthropic": {
|
|
@@ -836,6 +845,7 @@ async function runAiSetupPhase(args) {
|
|
|
836
845
|
log.success(
|
|
837
846
|
`AI ready \xB7 Anthropic Direct \xB7 model=${anthropicConfig.model} \xB7 ${anthropicConfig.baseUrl}`
|
|
838
847
|
);
|
|
848
|
+
warnAboutPlaintextSecret("Anthropic Direct API");
|
|
839
849
|
return { ok: true, provider: "anthropic", model: anthropicConfig.model };
|
|
840
850
|
}
|
|
841
851
|
case "use-global": {
|
|
@@ -1291,9 +1301,13 @@ async function listTags(cwd = process.cwd()) {
|
|
|
1291
1301
|
const result = await git(cwd).tags();
|
|
1292
1302
|
return result.all;
|
|
1293
1303
|
}
|
|
1294
|
-
async function
|
|
1295
|
-
|
|
1296
|
-
|
|
1304
|
+
async function tagAtHead(cwd = process.cwd()) {
|
|
1305
|
+
try {
|
|
1306
|
+
const result = await git(cwd).raw(["describe", "--tags", "--exact-match", "HEAD"]);
|
|
1307
|
+
return result.trim() || null;
|
|
1308
|
+
} catch {
|
|
1309
|
+
return null;
|
|
1310
|
+
}
|
|
1297
1311
|
}
|
|
1298
1312
|
async function currentCommitSha(cwd = process.cwd()) {
|
|
1299
1313
|
const result = await git(cwd).revparse(["HEAD"]);
|
|
@@ -1494,11 +1508,12 @@ async function runChecks(cwd) {
|
|
|
1494
1508
|
});
|
|
1495
1509
|
}
|
|
1496
1510
|
const gitignorePath = join11(cwd, ".gitignore");
|
|
1511
|
+
let gitignoreContent = "";
|
|
1497
1512
|
if (gitRepo) {
|
|
1498
1513
|
let gitignoreOk = false;
|
|
1499
1514
|
if (await pathExists(gitignorePath)) {
|
|
1500
|
-
|
|
1501
|
-
gitignoreOk =
|
|
1515
|
+
gitignoreContent = await fs6.readFile(gitignorePath, "utf8");
|
|
1516
|
+
gitignoreOk = gitignoreContent.includes(".claude/_pending/");
|
|
1502
1517
|
}
|
|
1503
1518
|
checks.push({
|
|
1504
1519
|
name: ".gitignore Avatar entries",
|
|
@@ -1506,6 +1521,66 @@ async function runChecks(cwd) {
|
|
|
1506
1521
|
detail: gitignoreOk ? "c\xF3 .claude/_pending/, .claude/_backup/" : "thi\u1EBFu entries",
|
|
1507
1522
|
fixable: false
|
|
1508
1523
|
});
|
|
1524
|
+
const settingsGitignored = gitignoreContent.includes(".claude/settings.json") || gitignoreContent.includes("/.claude/settings.json") || gitignoreContent.includes(".claude/*.json");
|
|
1525
|
+
checks.push({
|
|
1526
|
+
name: "\u{1F512} settings.json gitignored",
|
|
1527
|
+
status: settingsGitignored ? "ok" : "fail",
|
|
1528
|
+
detail: settingsGitignored ? "an to\xE0n \u2014 settings.json kh\xF4ng commit nh\u1EA7m" : "CRITICAL: settings.json ch\u1EE9a API key \u2014 ch\u1EA1y 'avatar doctor --fix' \u0111\u1EC3 add gitignore",
|
|
1529
|
+
fixable: !settingsGitignored,
|
|
1530
|
+
fix: settingsGitignored ? void 0 : async () => {
|
|
1531
|
+
const addition = "\n# Avatar v1.7.0 \u2014 Security: settings.json ch\u1EE9a raw API key, KH\xD4NG commit.\n.claude/settings.json\n.claude/settings.json.backup-*\n";
|
|
1532
|
+
await fs6.appendFile(gitignorePath, addition);
|
|
1533
|
+
}
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
const pythonCheck = spawnSync6("which", ["python"]);
|
|
1537
|
+
const python3Check = spawnSync6("which", ["python3"]);
|
|
1538
|
+
const hasPython = pythonCheck.status === 0;
|
|
1539
|
+
const hasPython3 = python3Check.status === 0;
|
|
1540
|
+
if (hasPython3 && !hasPython) {
|
|
1541
|
+
checks.push({
|
|
1542
|
+
name: "Python binary alias",
|
|
1543
|
+
status: "warn",
|
|
1544
|
+
detail: `Ch\u1EC9 c\xF3 python3 (modern macOS). Pack scripts th\u01B0\u1EDDng ref 'python' \u2192 suggest: ln -s ${python3Check.stdout.toString().trim()} ~/.local/bin/python`,
|
|
1545
|
+
fixable: false
|
|
1546
|
+
});
|
|
1547
|
+
} else if (hasPython) {
|
|
1548
|
+
checks.push({
|
|
1549
|
+
name: "Python binary",
|
|
1550
|
+
status: "ok",
|
|
1551
|
+
detail: `python: ${pythonCheck.stdout.toString().trim()}`,
|
|
1552
|
+
fixable: false
|
|
1553
|
+
});
|
|
1554
|
+
} else if (hasPython3) {
|
|
1555
|
+
checks.push({
|
|
1556
|
+
name: "Python binary",
|
|
1557
|
+
status: "ok",
|
|
1558
|
+
detail: `python3: ${python3Check.stdout.toString().trim()}`,
|
|
1559
|
+
fixable: false
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
const settingsPath = join11(cwd, ".claude", "settings.json");
|
|
1563
|
+
if (await pathExists(settingsPath)) {
|
|
1564
|
+
try {
|
|
1565
|
+
const settingsRaw = await fs6.readFile(settingsPath, "utf8");
|
|
1566
|
+
const settings = JSON.parse(settingsRaw);
|
|
1567
|
+
if (settings.statusLine?.command) {
|
|
1568
|
+
const cmd = settings.statusLine.command.trim();
|
|
1569
|
+
const match = cmd.match(/^(node|python|python3|bash|sh)\s+([^\s]+)/);
|
|
1570
|
+
if (match) {
|
|
1571
|
+
const refFile = match[2];
|
|
1572
|
+
const fullPath = refFile.startsWith("/") ? refFile : join11(cwd, refFile);
|
|
1573
|
+
const fileExists = await pathExists(fullPath);
|
|
1574
|
+
checks.push({
|
|
1575
|
+
name: "statusLine command",
|
|
1576
|
+
status: fileExists ? "ok" : "fail",
|
|
1577
|
+
detail: fileExists ? `ref OK: ${refFile}` : `BROKEN: settings.json ref '${refFile}' nh\u01B0ng file kh\xF4ng t\u1ED3n t\u1EA1i. Strip field statusLine ho\u1EB7c fix path.`,
|
|
1578
|
+
fixable: false
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
} catch {
|
|
1583
|
+
}
|
|
1509
1584
|
}
|
|
1510
1585
|
const which = spawnSync6("which", ["claude"]);
|
|
1511
1586
|
const hasClaudeCli = which.status === 0;
|
|
@@ -2359,6 +2434,31 @@ async function ensureTeamPackAccessWithRetry(args) {
|
|
|
2359
2434
|
}
|
|
2360
2435
|
}
|
|
2361
2436
|
|
|
2437
|
+
// src/lib/pick-latest-stable-semver-tag.ts
|
|
2438
|
+
var SEMVER_REGEX3 = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/;
|
|
2439
|
+
function parseSemVerTag(tag) {
|
|
2440
|
+
const match = tag.match(SEMVER_REGEX3);
|
|
2441
|
+
if (!match) return null;
|
|
2442
|
+
const [, major, minor, patch, prerelease] = match;
|
|
2443
|
+
return {
|
|
2444
|
+
raw: tag,
|
|
2445
|
+
major: Number.parseInt(major ?? "0", 10),
|
|
2446
|
+
minor: Number.parseInt(minor ?? "0", 10),
|
|
2447
|
+
patch: Number.parseInt(patch ?? "0", 10),
|
|
2448
|
+
prerelease: prerelease ?? null
|
|
2449
|
+
};
|
|
2450
|
+
}
|
|
2451
|
+
function pickLatestStableSemVerTag(tags, includePrerelease = false) {
|
|
2452
|
+
const parsed = tags.map(parseSemVerTag).filter((t) => t !== null).filter((t) => includePrerelease || t.prerelease === null);
|
|
2453
|
+
if (parsed.length === 0) return null;
|
|
2454
|
+
parsed.sort((a, b) => {
|
|
2455
|
+
if (a.major !== b.major) return a.major - b.major;
|
|
2456
|
+
if (a.minor !== b.minor) return a.minor - b.minor;
|
|
2457
|
+
return a.patch - b.patch;
|
|
2458
|
+
});
|
|
2459
|
+
return parsed[parsed.length - 1]?.raw ?? null;
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2362
2462
|
// src/lib/resolve-team-pack-repo-url.ts
|
|
2363
2463
|
var ORG_DEFAULT = "git@github.com:nalvn/team-ai-pack.git";
|
|
2364
2464
|
function resolveTeamPackRepoUrl() {
|
|
@@ -2405,7 +2505,9 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
|
|
|
2405
2505
|
}
|
|
2406
2506
|
let target = tag ?? null;
|
|
2407
2507
|
if (!target) {
|
|
2408
|
-
|
|
2508
|
+
const submoduleDir = join16(projectRoot, TEAM_PACK_RELATIVE_PATH);
|
|
2509
|
+
const allTags = await listTags(submoduleDir);
|
|
2510
|
+
target = pickLatestStableSemVerTag(allTags);
|
|
2409
2511
|
}
|
|
2410
2512
|
if (target) {
|
|
2411
2513
|
await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, target, projectRoot);
|
|
@@ -2414,7 +2516,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
|
|
|
2414
2516
|
}
|
|
2415
2517
|
async function readPinnedPackVersion(projectRoot) {
|
|
2416
2518
|
const submoduleRoot = join16(projectRoot, TEAM_PACK_RELATIVE_PATH);
|
|
2417
|
-
const tag = await
|
|
2519
|
+
const tag = await tagAtHead(submoduleRoot);
|
|
2418
2520
|
if (tag) return tag;
|
|
2419
2521
|
const sha = await currentCommitSha(submoduleRoot);
|
|
2420
2522
|
return sha.slice(0, 7);
|
|
@@ -3097,6 +3199,16 @@ function linkExistingRemoteToWorkspace(args) {
|
|
|
3097
3199
|
// src/lib/merge-pack-settings-into-project-settings.ts
|
|
3098
3200
|
import { promises as fs9 } from "fs";
|
|
3099
3201
|
import { join as join17 } from "path";
|
|
3202
|
+
async function isStatusLineCommandResolvable(workspacePath, command) {
|
|
3203
|
+
const trimmed = command.trim();
|
|
3204
|
+
const match = trimmed.match(/^(node|python|python3|bash|sh)\s+([^\s]+)/);
|
|
3205
|
+
if (!match) {
|
|
3206
|
+
return true;
|
|
3207
|
+
}
|
|
3208
|
+
const filePath = match[2];
|
|
3209
|
+
const fullPath = filePath.startsWith("/") ? filePath : join17(workspacePath, filePath);
|
|
3210
|
+
return await pathExists(fullPath);
|
|
3211
|
+
}
|
|
3100
3212
|
function backupFilename(originalPath) {
|
|
3101
3213
|
const d = /* @__PURE__ */ new Date();
|
|
3102
3214
|
const stamp = `${d.getFullYear().toString().slice(-2) + String(d.getMonth() + 1).padStart(2, "0") + String(d.getDate()).padStart(2, "0")}-${String(d.getHours()).padStart(2, "0")}${String(d.getMinutes()).padStart(2, "0")}`;
|
|
@@ -3157,8 +3269,18 @@ async function mergePackSettingsIntoProjectSettings(workspacePath) {
|
|
|
3157
3269
|
const changes = [];
|
|
3158
3270
|
const merged = { ...userSettings };
|
|
3159
3271
|
if (packTemplate.statusLine && !userSettings.statusLine) {
|
|
3160
|
-
|
|
3161
|
-
|
|
3272
|
+
const statusLineOk = await isStatusLineCommandResolvable(
|
|
3273
|
+
workspacePath,
|
|
3274
|
+
packTemplate.statusLine.command
|
|
3275
|
+
);
|
|
3276
|
+
if (statusLineOk) {
|
|
3277
|
+
merged.statusLine = packTemplate.statusLine;
|
|
3278
|
+
changes.push("statusLine added");
|
|
3279
|
+
} else {
|
|
3280
|
+
changes.push(
|
|
3281
|
+
`statusLine SKIPPED (file ref '${packTemplate.statusLine.command}' kh\xF4ng t\u1ED3n t\u1EA1i)`
|
|
3282
|
+
);
|
|
3283
|
+
}
|
|
3162
3284
|
}
|
|
3163
3285
|
if (typeof packTemplate.includeCoAuthoredBy === "boolean" && typeof userSettings.includeCoAuthoredBy !== "boolean") {
|
|
3164
3286
|
merged.includeCoAuthoredBy = packTemplate.includeCoAuthoredBy;
|
|
@@ -4545,9 +4667,11 @@ async function listCommitsBetween(packDir, fromSha, toRef) {
|
|
|
4545
4667
|
}
|
|
4546
4668
|
}
|
|
4547
4669
|
async function buildSyncPreview(packDir, claudeDir, targetVersion) {
|
|
4670
|
+
const currentTagOrNull = await tagAtHead(packDir);
|
|
4548
4671
|
const currentSha = await currentCommitSha(packDir);
|
|
4549
|
-
const currentVersion = currentSha.slice(0, 7);
|
|
4550
|
-
const
|
|
4672
|
+
const currentVersion = currentTagOrNull ?? currentSha.slice(0, 7);
|
|
4673
|
+
const allTags = await listTags(packDir);
|
|
4674
|
+
const target = targetVersion ?? pickLatestStableSemVerTag(allTags) ?? "HEAD";
|
|
4551
4675
|
const commits = await listCommitsBetween(packDir, currentSha, target);
|
|
4552
4676
|
const mountStatuses = [];
|
|
4553
4677
|
for (const dir of TEAM_PACK_MOUNT_DIRS) {
|
|
@@ -4584,10 +4708,13 @@ async function syncAction(opts) {
|
|
|
4584
4708
|
`Kh\xF4ng fetch \u0111\u01B0\u1EE3c tags t\u1EEB origin (${err instanceof Error ? err.message : err}). S\u1EBD d\xF9ng tag local hi\u1EC7n c\xF3.`
|
|
4585
4709
|
);
|
|
4586
4710
|
}
|
|
4587
|
-
const
|
|
4711
|
+
const allTags = await listTags(packDir);
|
|
4712
|
+
const targetVersion = opts.version ?? pickLatestStableSemVerTag(allTags);
|
|
4588
4713
|
if (!targetVersion) {
|
|
4589
4714
|
log.error(
|
|
4590
|
-
|
|
4715
|
+
`Kh\xF4ng t\xECm th\u1EA5y stable SemVer tag (vMAJOR.MINOR.PATCH) trong team-ai-pack submodule.
|
|
4716
|
+
Tags hi\u1EC7n c\xF3: ${allTags.length > 0 ? allTags.join(", ") : "(none)"}
|
|
4717
|
+
Pass --version <tag> r\xF5 r\xE0ng, ho\u1EB7c tag pack theo SemVer convention.`
|
|
4591
4718
|
);
|
|
4592
4719
|
process.exit(1);
|
|
4593
4720
|
return;
|
|
@@ -4840,7 +4967,7 @@ async function removeSubmoduleEntry(gitmodulesPath, submodulePath) {
|
|
|
4840
4967
|
}
|
|
4841
4968
|
|
|
4842
4969
|
// src/commands/uninstall.ts
|
|
4843
|
-
var CLI_VERSION = "1.
|
|
4970
|
+
var CLI_VERSION = "1.7.1";
|
|
4844
4971
|
function registerUninstallCommand(program2) {
|
|
4845
4972
|
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) => {
|
|
4846
4973
|
try {
|
|
@@ -4922,7 +5049,7 @@ function printUninstallSuccessBox(backupPath) {
|
|
|
4922
5049
|
}
|
|
4923
5050
|
|
|
4924
5051
|
// src/index.ts
|
|
4925
|
-
var CLI_VERSION2 = "1.
|
|
5052
|
+
var CLI_VERSION2 = "1.7.1";
|
|
4926
5053
|
var program = new Command();
|
|
4927
5054
|
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(
|
|
4928
5055
|
"beforeAll",
|