@nalvietnam/avatar-cli 1.3.3 → 1.4.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
|
@@ -134,8 +134,8 @@ async function writeUserConfig(config) {
|
|
|
134
134
|
}
|
|
135
135
|
async function clearUserConfig() {
|
|
136
136
|
if (await pathExists(USER_CONFIG_PATH)) {
|
|
137
|
-
const { promises:
|
|
138
|
-
await
|
|
137
|
+
const { promises: fs11 } = await import("fs");
|
|
138
|
+
await fs11.unlink(USER_CONFIG_PATH);
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
function isTokenExpired(config) {
|
|
@@ -1306,14 +1306,196 @@ async function applyFixes(checks) {
|
|
|
1306
1306
|
if (count === 0) log.dim("Kh\xF4ng c\xF3 g\xEC \u0111\u1EC3 fix t\u1EF1 \u0111\u1ED9ng.");
|
|
1307
1307
|
}
|
|
1308
1308
|
|
|
1309
|
-
// src/commands/
|
|
1310
|
-
import {
|
|
1311
|
-
import {
|
|
1312
|
-
import
|
|
1309
|
+
// src/commands/gitnexus.ts
|
|
1310
|
+
import { spawnSync as spawnSync11 } from "child_process";
|
|
1311
|
+
import { promises as fs8 } from "fs";
|
|
1312
|
+
import { join as join15 } from "path";
|
|
1313
1313
|
|
|
1314
|
-
// src/lib/
|
|
1314
|
+
// src/lib/run-gitnexus-setup-and-analyze.ts
|
|
1315
|
+
import { spawnSync as spawnSync7 } from "child_process";
|
|
1316
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1317
|
+
import { join as join12 } from "path";
|
|
1318
|
+
var SETUP_TIMEOUT_MS = 2 * 60 * 1e3;
|
|
1319
|
+
var ANALYZE_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
1320
|
+
var GitnexusOperationError = class extends Error {
|
|
1321
|
+
operation;
|
|
1322
|
+
reason;
|
|
1323
|
+
exitCode;
|
|
1324
|
+
stderr;
|
|
1325
|
+
constructor(operation, reason, message, exitCode = null, stderr) {
|
|
1326
|
+
super(message);
|
|
1327
|
+
this.name = "GitnexusOperationError";
|
|
1328
|
+
this.operation = operation;
|
|
1329
|
+
this.reason = reason;
|
|
1330
|
+
this.exitCode = exitCode;
|
|
1331
|
+
this.stderr = stderr;
|
|
1332
|
+
}
|
|
1333
|
+
};
|
|
1334
|
+
function classifyOperationFailure(operation, exitCode, signal, stderrSample) {
|
|
1335
|
+
if (signal === "SIGTERM") {
|
|
1336
|
+
return new GitnexusOperationError(
|
|
1337
|
+
operation,
|
|
1338
|
+
"timeout",
|
|
1339
|
+
`gitnexus ${operation} timeout. Check m\u1EA1ng / repo size.`,
|
|
1340
|
+
null,
|
|
1341
|
+
stderrSample
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
const stderr = stderrSample.toLowerCase();
|
|
1345
|
+
if (stderr.includes("eacces") || stderr.includes("permission denied")) {
|
|
1346
|
+
return new GitnexusOperationError(
|
|
1347
|
+
operation,
|
|
1348
|
+
"permission",
|
|
1349
|
+
`gitnexus ${operation} fail (permission). Check write access ~/.claude ho\u1EB7c cwd.`,
|
|
1350
|
+
exitCode,
|
|
1351
|
+
stderrSample
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
return new GitnexusOperationError(
|
|
1355
|
+
operation,
|
|
1356
|
+
"non-zero-exit",
|
|
1357
|
+
`gitnexus ${operation} exit ${exitCode ?? "null"}. Xem log ph\xEDa tr\xEAn.`,
|
|
1358
|
+
exitCode,
|
|
1359
|
+
stderrSample
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
function runGitnexusSetup() {
|
|
1363
|
+
log.info("Setup GitNexus global skills (~/.claude/skills/gitnexus-*)...");
|
|
1364
|
+
const result = spawnSync7("gitnexus", ["setup"], {
|
|
1365
|
+
stdio: ["inherit", "inherit", "pipe"],
|
|
1366
|
+
timeout: SETUP_TIMEOUT_MS,
|
|
1367
|
+
encoding: "utf8"
|
|
1368
|
+
});
|
|
1369
|
+
if (result.status !== 0 || result.signal === "SIGTERM") {
|
|
1370
|
+
const stderr = (result.stderr || "").trim();
|
|
1371
|
+
if (stderr) process.stderr.write(`${stderr}
|
|
1372
|
+
`);
|
|
1373
|
+
throw classifyOperationFailure("setup", result.status, result.signal, stderr);
|
|
1374
|
+
}
|
|
1375
|
+
log.success("GitNexus setup OK (global skills installed)");
|
|
1376
|
+
}
|
|
1377
|
+
function runGitnexusAnalyze(workspacePath) {
|
|
1378
|
+
log.info(`Analyze workspace ${workspacePath} (c\xF3 th\u1EC3 1-3 ph\xFAt)...`);
|
|
1379
|
+
const result = spawnSync7("gitnexus", ["analyze", "."], {
|
|
1380
|
+
cwd: workspacePath,
|
|
1381
|
+
stdio: ["inherit", "inherit", "pipe"],
|
|
1382
|
+
timeout: ANALYZE_TIMEOUT_MS,
|
|
1383
|
+
encoding: "utf8"
|
|
1384
|
+
});
|
|
1385
|
+
if (result.status !== 0 || result.signal === "SIGTERM") {
|
|
1386
|
+
const stderr = (result.stderr || "").trim();
|
|
1387
|
+
if (stderr) process.stderr.write(`${stderr}
|
|
1388
|
+
`);
|
|
1389
|
+
throw classifyOperationFailure("analyze", result.status, result.signal, stderr);
|
|
1390
|
+
}
|
|
1391
|
+
const metaPath = join12(workspacePath, ".gitnexus", "meta.json");
|
|
1392
|
+
if (!existsSync4(metaPath)) {
|
|
1393
|
+
throw new GitnexusOperationError(
|
|
1394
|
+
"analyze",
|
|
1395
|
+
"missing-output",
|
|
1396
|
+
`gitnexus analyze xong nh\u01B0ng kh\xF4ng th\u1EA5y ${metaPath}. Repo c\xF3 th\u1EC3 empty ho\u1EB7c gitnexus fail silent.`
|
|
1397
|
+
);
|
|
1398
|
+
}
|
|
1399
|
+
log.success(`Analyze OK (index t\u1EA1i ${join12(workspacePath, ".gitnexus")})`);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// src/lib/run-gitnexus-setup-phase.ts
|
|
1403
|
+
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
1404
|
+
import boxen2 from "boxen";
|
|
1405
|
+
|
|
1406
|
+
// src/lib/detect-gitnexus-installation.ts
|
|
1315
1407
|
import { spawnSync as spawnSync8 } from "child_process";
|
|
1316
|
-
|
|
1408
|
+
var VERSION_PROBE_TIMEOUT_MS2 = 5e3;
|
|
1409
|
+
var SEMVER_REGEX2 = /(\d+\.\d+\.\d+)/;
|
|
1410
|
+
function probeGitnexusBinaryPath() {
|
|
1411
|
+
const isWindows = detectHostPlatform() === "win32";
|
|
1412
|
+
const probeCmd = isWindows ? "where" : "which";
|
|
1413
|
+
const result = spawnSync8(probeCmd, ["gitnexus"], { encoding: "utf8" });
|
|
1414
|
+
if (result.error || result.status !== 0) return null;
|
|
1415
|
+
const out = (result.stdout || "").trim();
|
|
1416
|
+
if (!out) return null;
|
|
1417
|
+
return out.split(/\r?\n/)[0].trim();
|
|
1418
|
+
}
|
|
1419
|
+
function probeGitnexusVersion() {
|
|
1420
|
+
const result = spawnSync8("gitnexus", ["--version"], {
|
|
1421
|
+
encoding: "utf8",
|
|
1422
|
+
timeout: VERSION_PROBE_TIMEOUT_MS2
|
|
1423
|
+
});
|
|
1424
|
+
if (result.error || result.status !== 0) return null;
|
|
1425
|
+
const out = (result.stdout || "").trim();
|
|
1426
|
+
const match = SEMVER_REGEX2.exec(out);
|
|
1427
|
+
return match ? match[1] : null;
|
|
1428
|
+
}
|
|
1429
|
+
function detectGitnexusInstallation() {
|
|
1430
|
+
const path = probeGitnexusBinaryPath();
|
|
1431
|
+
if (!path) {
|
|
1432
|
+
return { installed: false, version: null, path: null };
|
|
1433
|
+
}
|
|
1434
|
+
const version = probeGitnexusVersion();
|
|
1435
|
+
return { installed: true, version, path };
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
// src/lib/install-gitnexus-via-npm.ts
|
|
1439
|
+
import { spawnSync as spawnSync9 } from "child_process";
|
|
1440
|
+
var NPM_INSTALL_TIMEOUT_MS2 = 5 * 60 * 1e3;
|
|
1441
|
+
var GITNEXUS_PACKAGE = "gitnexus";
|
|
1442
|
+
var InstallGitnexusError = class extends Error {
|
|
1443
|
+
reason;
|
|
1444
|
+
exitCode;
|
|
1445
|
+
constructor(reason, message, exitCode = null) {
|
|
1446
|
+
super(message);
|
|
1447
|
+
this.name = "InstallGitnexusError";
|
|
1448
|
+
this.reason = reason;
|
|
1449
|
+
this.exitCode = exitCode;
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
function classifyNpmFailure2(exitCode, stderrSample) {
|
|
1453
|
+
const stderr = stderrSample.toLowerCase();
|
|
1454
|
+
if (stderr.includes("eacces") || stderr.includes("permission denied")) {
|
|
1455
|
+
return new InstallGitnexusError(
|
|
1456
|
+
"permission-denied",
|
|
1457
|
+
`npm install -g c\u1EA7n quy\u1EC1n. Th\u1EED: sudo npm install -g ${GITNEXUS_PACKAGE} ho\u1EB7c fix npm prefix (npm config set prefix ~/.npm-global).`,
|
|
1458
|
+
exitCode
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
if (stderr.includes("enospc") || stderr.includes("no space")) {
|
|
1462
|
+
return new InstallGitnexusError("disk-full", "\u0110\u0129a \u0111\u1EA7y. Free disk space r\u1ED3i th\u1EED l\u1EA1i.", exitCode);
|
|
1463
|
+
}
|
|
1464
|
+
return new InstallGitnexusError(
|
|
1465
|
+
"generic",
|
|
1466
|
+
`npm install th\u1EA5t b\u1EA1i (exit ${exitCode ?? "null"}). Xem log npm ph\xEDa tr\xEAn.`,
|
|
1467
|
+
exitCode
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
function installGitnexusViaNpm() {
|
|
1471
|
+
log.info("\u0110ang c\xE0i GitNexus qua npm (c\xF3 th\u1EC3 m\u1EA5t 1-2 ph\xFAt)...");
|
|
1472
|
+
const result = spawnSync9("npm", ["install", "-g", GITNEXUS_PACKAGE], {
|
|
1473
|
+
stdio: ["inherit", "inherit", "pipe"],
|
|
1474
|
+
timeout: NPM_INSTALL_TIMEOUT_MS2,
|
|
1475
|
+
encoding: "utf8"
|
|
1476
|
+
});
|
|
1477
|
+
if (result.signal === "SIGTERM") {
|
|
1478
|
+
throw new InstallGitnexusError(
|
|
1479
|
+
"timeout",
|
|
1480
|
+
`npm install timeout sau ${NPM_INSTALL_TIMEOUT_MS2 / 1e3}s. Check m\u1EA1ng r\u1ED3i th\u1EED l\u1EA1i.`,
|
|
1481
|
+
null
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
if (result.status !== 0) {
|
|
1485
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
1486
|
+
throw classifyNpmFailure2(result.status, result.stderr || "");
|
|
1487
|
+
}
|
|
1488
|
+
const probe = detectGitnexusInstallation();
|
|
1489
|
+
if (!probe.installed || !probe.path) {
|
|
1490
|
+
throw new InstallGitnexusError(
|
|
1491
|
+
"binary-not-in-path",
|
|
1492
|
+
"npm c\xE0i xong nh\u01B0ng `gitnexus` kh\xF4ng trong PATH. Reload shell (source ~/.zshrc) ho\u1EB7c th\xEAm npm global bin v\xE0o PATH.",
|
|
1493
|
+
null
|
|
1494
|
+
);
|
|
1495
|
+
}
|
|
1496
|
+
log.success(`\u0110\xE3 c\xE0i GitNexus${probe.version ? ` v${probe.version}` : ""} t\u1EA1i ${probe.path}`);
|
|
1497
|
+
return { version: probe.version, path: probe.path };
|
|
1498
|
+
}
|
|
1317
1499
|
|
|
1318
1500
|
// src/lib/prompt-recovery-action-on-failure.ts
|
|
1319
1501
|
import { input as input3, select as select3 } from "@inquirer/prompts";
|
|
@@ -1339,24 +1521,408 @@ async function promptRetryOrSkip(args) {
|
|
|
1339
1521
|
});
|
|
1340
1522
|
}
|
|
1341
1523
|
|
|
1524
|
+
// src/lib/register-gitnexus-mcp-server.ts
|
|
1525
|
+
import { promises as fs7 } from "fs";
|
|
1526
|
+
import { homedir as homedir3 } from "os";
|
|
1527
|
+
import { join as join13 } from "path";
|
|
1528
|
+
var MCP_FILE_MODE = 384;
|
|
1529
|
+
var EXPECTED_GITNEXUS_ENTRY = {
|
|
1530
|
+
command: "gitnexus",
|
|
1531
|
+
args: ["mcp"]
|
|
1532
|
+
};
|
|
1533
|
+
function getMcpServersPath() {
|
|
1534
|
+
return join13(homedir3(), ".claude", "mcp_servers.json");
|
|
1535
|
+
}
|
|
1536
|
+
function isEntryEqual(a, b) {
|
|
1537
|
+
if (a === b) return true;
|
|
1538
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
1539
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
1540
|
+
}
|
|
1541
|
+
async function backupExistingFile(path) {
|
|
1542
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1543
|
+
const backupPath = `${path}.avatar-backup-${ts}`;
|
|
1544
|
+
await fs7.copyFile(path, backupPath);
|
|
1545
|
+
return backupPath;
|
|
1546
|
+
}
|
|
1547
|
+
async function registerGitnexusMcpServer() {
|
|
1548
|
+
const path = getMcpServersPath();
|
|
1549
|
+
let existing = {};
|
|
1550
|
+
let fileExisted = false;
|
|
1551
|
+
if (await pathExists(path)) {
|
|
1552
|
+
fileExisted = true;
|
|
1553
|
+
try {
|
|
1554
|
+
existing = await readJson(path);
|
|
1555
|
+
} catch (err) {
|
|
1556
|
+
throw new Error(
|
|
1557
|
+
`MCP config corrupted (${path}): ${err.message}. Backup + x\xF3a file \u0111\u1EC3 Avatar t\u1EA1o l\u1EA1i.`
|
|
1558
|
+
);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
const existingEntry = existing.mcp_servers?.gitnexus;
|
|
1562
|
+
if (existingEntry && isEntryEqual(existingEntry, EXPECTED_GITNEXUS_ENTRY)) {
|
|
1563
|
+
log.dim(`MCP entry gitnexus \u0111\xE3 \u0111\xFAng t\u1EA1i ${path} (no-op)`);
|
|
1564
|
+
return { path, wasUpdated: false };
|
|
1565
|
+
}
|
|
1566
|
+
let backup;
|
|
1567
|
+
if (fileExisted) {
|
|
1568
|
+
backup = await backupExistingFile(path);
|
|
1569
|
+
log.dim(`Backup ${path} \u2192 ${backup}`);
|
|
1570
|
+
}
|
|
1571
|
+
const merged = {
|
|
1572
|
+
...existing,
|
|
1573
|
+
mcp_servers: {
|
|
1574
|
+
...existing.mcp_servers || {},
|
|
1575
|
+
gitnexus: EXPECTED_GITNEXUS_ENTRY
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
await writeJsonAtomic(path, merged, MCP_FILE_MODE);
|
|
1579
|
+
try {
|
|
1580
|
+
await fs7.chmod(path, MCP_FILE_MODE);
|
|
1581
|
+
} catch {
|
|
1582
|
+
}
|
|
1583
|
+
log.success(`Registered MCP server: gitnexus \u2192 ${path}`);
|
|
1584
|
+
return { path, wasUpdated: true, backup };
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
// src/lib/run-gitnexus-wiki-conditional.ts
|
|
1588
|
+
import { spawnSync as spawnSync10 } from "child_process";
|
|
1589
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1590
|
+
import { join as join14 } from "path";
|
|
1591
|
+
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
1592
|
+
var WIKI_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
1593
|
+
async function readSettingsForWikiCredentials(workspacePath) {
|
|
1594
|
+
const settingsPath = join14(workspacePath, ".claude", "settings.json");
|
|
1595
|
+
if (!await pathExists(settingsPath)) return null;
|
|
1596
|
+
try {
|
|
1597
|
+
const settings = await readJson(settingsPath);
|
|
1598
|
+
const env = settings.env || {};
|
|
1599
|
+
const apiKey = typeof env.ANTHROPIC_AUTH_TOKEN === "string" ? env.ANTHROPIC_AUTH_TOKEN : null;
|
|
1600
|
+
const baseUrl = typeof env.ANTHROPIC_BASE_URL === "string" ? env.ANTHROPIC_BASE_URL : null;
|
|
1601
|
+
if (!apiKey || !baseUrl) return null;
|
|
1602
|
+
return { apiKey, baseUrl };
|
|
1603
|
+
} catch {
|
|
1604
|
+
return null;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
async function confirmWikiGeneration(baseUrl) {
|
|
1608
|
+
return await confirm2({
|
|
1609
|
+
message: `Generate wiki cho workspace? (~$0.50 qua ${baseUrl}, 2-5 ph\xFAt). Continue?`,
|
|
1610
|
+
default: true
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
async function runGitnexusWikiConditional(workspacePath) {
|
|
1614
|
+
const creds = await readSettingsForWikiCredentials(workspacePath);
|
|
1615
|
+
if (!creds) {
|
|
1616
|
+
log.warn("Subscription mode (ho\u1EB7c settings.json kh\xF4ng c\xF3 LLMLite key) \u2192 skip wiki gen.");
|
|
1617
|
+
log.dim("\u0110\u1EC3 gen wiki sau, ch\u1EA1y manual: gitnexus wiki . --api-key <openai-key>");
|
|
1618
|
+
return { ran: false, skipped: true, reason: "subscription-mode" };
|
|
1619
|
+
}
|
|
1620
|
+
const proceed = await confirmWikiGeneration(creds.baseUrl);
|
|
1621
|
+
if (!proceed) {
|
|
1622
|
+
log.dim(
|
|
1623
|
+
"User decline wiki gen \u2014 workspace OK kh\xF4ng c\xF3 wiki. Ch\u1EA1y `gitnexus wiki` manual sau khi c\u1EA7n."
|
|
1624
|
+
);
|
|
1625
|
+
return { ran: false, skipped: true, reason: "user-declined" };
|
|
1626
|
+
}
|
|
1627
|
+
log.info(`Generating wiki via ${creds.baseUrl} (2-5 ph\xFAt)...`);
|
|
1628
|
+
const result = spawnSync10(
|
|
1629
|
+
"gitnexus",
|
|
1630
|
+
["wiki", ".", "--api-key", creds.apiKey, "--base-url", creds.baseUrl],
|
|
1631
|
+
{
|
|
1632
|
+
cwd: workspacePath,
|
|
1633
|
+
stdio: ["inherit", "inherit", "pipe"],
|
|
1634
|
+
timeout: WIKI_TIMEOUT_MS,
|
|
1635
|
+
encoding: "utf8"
|
|
1636
|
+
}
|
|
1637
|
+
);
|
|
1638
|
+
if (result.status !== 0 || result.signal === "SIGTERM") {
|
|
1639
|
+
const stderr = (result.stderr || "").trim();
|
|
1640
|
+
if (stderr) process.stderr.write(`${stderr}
|
|
1641
|
+
`);
|
|
1642
|
+
const reason = result.signal === "SIGTERM" ? "timeout" : "non-zero-exit";
|
|
1643
|
+
return {
|
|
1644
|
+
ran: false,
|
|
1645
|
+
skipped: true,
|
|
1646
|
+
reason: "fail",
|
|
1647
|
+
detail: `Wiki gen ${reason} (exit ${result.status ?? "null"})`
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
const wikiPath = join14(workspacePath, ".gitnexus", "wiki", "index.html");
|
|
1651
|
+
if (!existsSync5(wikiPath)) {
|
|
1652
|
+
return {
|
|
1653
|
+
ran: false,
|
|
1654
|
+
skipped: true,
|
|
1655
|
+
reason: "fail",
|
|
1656
|
+
detail: `Wiki exit 0 nh\u01B0ng kh\xF4ng th\u1EA5y ${wikiPath}`
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
log.success(`Wiki ready: ${wikiPath}`);
|
|
1660
|
+
return { ran: true, skipped: false, wikiPath };
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
// src/lib/run-gitnexus-setup-phase.ts
|
|
1664
|
+
async function promptInstallGitnexus() {
|
|
1665
|
+
const lines = [
|
|
1666
|
+
chalk.bold("\u{1F9E0} GitNexus ch\u01B0a c\xE0i"),
|
|
1667
|
+
"",
|
|
1668
|
+
"GitNexus = code intelligence layer cho Claude Code:",
|
|
1669
|
+
" \u2022 Architectural awareness (impact analysis)",
|
|
1670
|
+
" \u2022 Call chain debug + blast radius tr\u01B0\u1EDBc refactor",
|
|
1671
|
+
" \u2022 Wiki HTML t\u1EF1 gen m\xF4 t\u1EA3 codebase",
|
|
1672
|
+
"",
|
|
1673
|
+
`S\u1EBD c\xE0i: ${chalk.cyan("npm install -g gitnexus")} (global)`
|
|
1674
|
+
];
|
|
1675
|
+
process.stdout.write(
|
|
1676
|
+
`${boxen2(lines.join("\n"), { padding: 1, borderStyle: "round", borderColor: "cyan" })}
|
|
1677
|
+
`
|
|
1678
|
+
);
|
|
1679
|
+
return await confirm3({ message: "C\xE0i GitNexus global?", default: true });
|
|
1680
|
+
}
|
|
1681
|
+
async function installWithRecovery() {
|
|
1682
|
+
while (true) {
|
|
1683
|
+
try {
|
|
1684
|
+
installGitnexusViaNpm();
|
|
1685
|
+
return true;
|
|
1686
|
+
} catch (err) {
|
|
1687
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1688
|
+
const hint = err instanceof InstallGitnexusError && err.reason === "permission-denied" ? "Th\u1EED l\u1EA1i v\u1EDBi sudo, ho\u1EB7c fix npm prefix: npm config set prefix ~/.npm-global" : "Check log npm ph\xEDa tr\xEAn + th\u1EED l\u1EA1i.";
|
|
1689
|
+
const action = await promptRetryOrSkip({
|
|
1690
|
+
taskName: "C\xE0i GitNexus qua npm",
|
|
1691
|
+
reason: message,
|
|
1692
|
+
allowSkip: true,
|
|
1693
|
+
hint
|
|
1694
|
+
});
|
|
1695
|
+
if (action === "abort") {
|
|
1696
|
+
throw new UserAbortedRecoveryError("User abort t\u1EA1i b\u01B0\u1EDBc c\xE0i GitNexus.");
|
|
1697
|
+
}
|
|
1698
|
+
if (action === "skip") return false;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
async function analyzeWithRecovery(workspacePath) {
|
|
1703
|
+
while (true) {
|
|
1704
|
+
try {
|
|
1705
|
+
runGitnexusAnalyze(workspacePath);
|
|
1706
|
+
return true;
|
|
1707
|
+
} catch (err) {
|
|
1708
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1709
|
+
const hint = err instanceof GitnexusOperationError && err.reason === "missing-output" ? "Repo c\xF3 th\u1EC3 empty ho\u1EB7c gitnexus version mismatch. Check `gitnexus --version`." : "Network glitch? Retry th\u01B0\u1EDDng work.";
|
|
1710
|
+
const action = await promptRetryOrSkip({
|
|
1711
|
+
taskName: "GitNexus analyze workspace",
|
|
1712
|
+
reason: message,
|
|
1713
|
+
allowSkip: true,
|
|
1714
|
+
hint
|
|
1715
|
+
});
|
|
1716
|
+
if (action === "abort") {
|
|
1717
|
+
throw new UserAbortedRecoveryError("User abort t\u1EA1i b\u01B0\u1EDBc GitNexus analyze.");
|
|
1718
|
+
}
|
|
1719
|
+
if (action === "skip") return false;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
async function runGitnexusSetupPhase(args) {
|
|
1724
|
+
const result = {
|
|
1725
|
+
ok: false,
|
|
1726
|
+
installed: false,
|
|
1727
|
+
analyzed: false,
|
|
1728
|
+
wikiGenerated: false,
|
|
1729
|
+
mcpRegistered: false
|
|
1730
|
+
};
|
|
1731
|
+
try {
|
|
1732
|
+
log.info("=== Phase 10: GitNexus Setup ===");
|
|
1733
|
+
let info = detectGitnexusInstallation();
|
|
1734
|
+
if (!info.installed) {
|
|
1735
|
+
const shouldInstall = await promptInstallGitnexus();
|
|
1736
|
+
if (!shouldInstall) {
|
|
1737
|
+
await appendAuditEntry("gitnexus_setup", "result=skipped,reason=user-declined");
|
|
1738
|
+
log.dim("Skip GitNexus. C\xE0i sau qua `avatar gitnexus install`.");
|
|
1739
|
+
result.reason = "user-declined";
|
|
1740
|
+
return result;
|
|
1741
|
+
}
|
|
1742
|
+
const installed = await installWithRecovery();
|
|
1743
|
+
if (!installed) {
|
|
1744
|
+
await appendAuditEntry("gitnexus_setup", "result=skipped,reason=install-skipped");
|
|
1745
|
+
log.dim("Skip GitNexus install. Workspace OK kh\xF4ng c\xF3 codebase intelligence.");
|
|
1746
|
+
result.reason = "install-skipped";
|
|
1747
|
+
return result;
|
|
1748
|
+
}
|
|
1749
|
+
info = detectGitnexusInstallation();
|
|
1750
|
+
if (!info.installed) {
|
|
1751
|
+
throw new Error("C\xE0i xong nh\u01B0ng kh\xF4ng detect \u0111\u01B0\u1EE3c binary (PATH issue).");
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
result.installed = true;
|
|
1755
|
+
log.success(`GitNexus available${info.version ? ` v${info.version}` : ""}`);
|
|
1756
|
+
try {
|
|
1757
|
+
runGitnexusSetup();
|
|
1758
|
+
} catch (err) {
|
|
1759
|
+
log.warn(`gitnexus setup fail: ${err.message}`);
|
|
1760
|
+
log.dim("Skip global skills install. Workspace v\u1EABn d\xF9ng \u0111\u01B0\u1EE3c.");
|
|
1761
|
+
}
|
|
1762
|
+
const analyzed = await analyzeWithRecovery(args.workspacePath);
|
|
1763
|
+
if (!analyzed) {
|
|
1764
|
+
await appendAuditEntry("gitnexus_setup", "result=skipped,reason=analyze-skipped");
|
|
1765
|
+
log.dim("Skip analyze. GitNexus installed nh\u01B0ng ch\u01B0a index.");
|
|
1766
|
+
result.reason = "analyze-skipped";
|
|
1767
|
+
return result;
|
|
1768
|
+
}
|
|
1769
|
+
result.analyzed = true;
|
|
1770
|
+
const wikiResult = await runGitnexusWikiConditional(args.workspacePath);
|
|
1771
|
+
result.wikiGenerated = wikiResult.ran;
|
|
1772
|
+
if (wikiResult.skipped && wikiResult.reason === "fail") {
|
|
1773
|
+
log.warn(`Wiki gen fail (workspace v\u1EABn OK): ${wikiResult.detail ?? "unknown"}`);
|
|
1774
|
+
}
|
|
1775
|
+
try {
|
|
1776
|
+
const mcpResult = await registerGitnexusMcpServer();
|
|
1777
|
+
result.mcpRegistered = true;
|
|
1778
|
+
if (!mcpResult.wasUpdated) {
|
|
1779
|
+
log.dim("MCP server gitnexus \u0111\xE3 registered tr\u01B0\u1EDBc \u0111\xF3.");
|
|
1780
|
+
}
|
|
1781
|
+
} catch (err) {
|
|
1782
|
+
log.warn(`MCP server register fail: ${err.message}`);
|
|
1783
|
+
log.dim(
|
|
1784
|
+
"Workspace OK nh\u01B0ng Claude Code kh\xF4ng t\u1EF1 attach MCP server. Manual add v\xE0o ~/.claude/mcp_servers.json."
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1787
|
+
result.ok = true;
|
|
1788
|
+
await appendAuditEntry(
|
|
1789
|
+
"gitnexus_setup",
|
|
1790
|
+
`result=ok,analyzed=${result.analyzed},wiki=${result.wikiGenerated},mcp=${result.mcpRegistered}`
|
|
1791
|
+
);
|
|
1792
|
+
log.success("GitNexus ready");
|
|
1793
|
+
return result;
|
|
1794
|
+
} catch (err) {
|
|
1795
|
+
if (err instanceof UserAbortedRecoveryError) throw err;
|
|
1796
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1797
|
+
log.warn(`GitNexus setup th\u1EA5t b\u1EA1i: ${message}`);
|
|
1798
|
+
log.dim("Workspace v\u1EABn s\u1EB5n s\xE0ng. Setup sau qua `avatar gitnexus install`.");
|
|
1799
|
+
await appendAuditEntry("gitnexus_setup", `result=failed,error=${message.slice(0, 200)}`);
|
|
1800
|
+
result.reason = message;
|
|
1801
|
+
return result;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
// src/commands/gitnexus.ts
|
|
1806
|
+
function ensureWorkspaceCwd2() {
|
|
1807
|
+
const cwd = process.cwd();
|
|
1808
|
+
const workspaceRoot = resolveAvatarWorkspaceRootFromCwd(cwd);
|
|
1809
|
+
if (!workspaceRoot) {
|
|
1810
|
+
log.error(
|
|
1811
|
+
`Kh\xF4ng t\xECm th\u1EA5y Avatar workspace t\u1EEB th\u01B0 m\u1EE5c hi\u1EC7n t\u1EA1i.
|
|
1812
|
+
Avatar workspace c\u1EA7n c\xF3: .claude/ + CLAUDE.md + src/ (ho\u1EB7c .gitmodules).
|
|
1813
|
+
B\u1EA1n \u0111ang \u1EDF: ${cwd}
|
|
1814
|
+
Cd v\xE0o workspace dir r\u1ED3i ch\u1EA1y l\u1EA1i.`
|
|
1815
|
+
);
|
|
1816
|
+
process.exit(1);
|
|
1817
|
+
}
|
|
1818
|
+
if (workspaceRoot !== cwd) {
|
|
1819
|
+
log.dim(`Detected workspace root: ${workspaceRoot}`);
|
|
1820
|
+
}
|
|
1821
|
+
return workspaceRoot;
|
|
1822
|
+
}
|
|
1823
|
+
async function runGitnexusInstall() {
|
|
1824
|
+
const workspacePath = ensureWorkspaceCwd2();
|
|
1825
|
+
const result = await runGitnexusSetupPhase({ workspacePath });
|
|
1826
|
+
if (result.ok) {
|
|
1827
|
+
log.success("GitNexus setup complete");
|
|
1828
|
+
log.dim("Update CLAUDE.md \u0111\u1EC3 re-render section GitNexus: re-run avatar init ho\u1EB7c ch\u1EC9nh tay.");
|
|
1829
|
+
} else {
|
|
1830
|
+
log.warn(`Setup kh\xF4ng complete: ${result.reason ?? "unknown"}`);
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
async function runGitnexusStatus() {
|
|
1834
|
+
const workspacePath = ensureWorkspaceCwd2();
|
|
1835
|
+
const metaPath = join15(workspacePath, ".gitnexus", "meta.json");
|
|
1836
|
+
if (!await pathExists(metaPath)) {
|
|
1837
|
+
log.warn(`Ch\u01B0a c\xF3 ${metaPath}. Ch\u1EA1y: avatar gitnexus install`);
|
|
1838
|
+
return;
|
|
1839
|
+
}
|
|
1840
|
+
try {
|
|
1841
|
+
const meta = await readJson(metaPath);
|
|
1842
|
+
log.info(`Project: ${workspacePath}`);
|
|
1843
|
+
log.info(`Last commit: ${meta.lastCommit?.slice(0, 7) ?? "(unknown)"}`);
|
|
1844
|
+
log.info(`Indexed at: ${meta.indexedAt ?? "(unknown)"}`);
|
|
1845
|
+
if (meta.stats) {
|
|
1846
|
+
log.info(
|
|
1847
|
+
`Stats: ${meta.stats.files ?? "?"} files \xB7 ${meta.stats.nodes ?? "?"} nodes \xB7 ${meta.stats.edges ?? "?"} edges`
|
|
1848
|
+
);
|
|
1849
|
+
}
|
|
1850
|
+
if (meta.lastCommit) {
|
|
1851
|
+
const headResult = spawnSync11("git", ["rev-parse", "HEAD"], {
|
|
1852
|
+
cwd: workspacePath,
|
|
1853
|
+
encoding: "utf8"
|
|
1854
|
+
});
|
|
1855
|
+
if (headResult.status === 0) {
|
|
1856
|
+
const currentHead = headResult.stdout.trim();
|
|
1857
|
+
if (currentHead !== meta.lastCommit) {
|
|
1858
|
+
log.warn("\u26A0 Index stale \u2014 HEAD \u0111\xE3 ti\u1EBFn t\u1EEB lastCommit. Ch\u1EA1y: avatar gitnexus analyze");
|
|
1859
|
+
} else {
|
|
1860
|
+
log.dim("Index fresh (HEAD === lastCommit)");
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
const wikiPath = join15(workspacePath, ".gitnexus", "wiki", "index.html");
|
|
1865
|
+
if (await pathExists(wikiPath)) {
|
|
1866
|
+
const stat = await fs8.stat(wikiPath);
|
|
1867
|
+
log.info(`Wiki: ${stat.mtime.toISOString()}`);
|
|
1868
|
+
} else {
|
|
1869
|
+
log.dim("Wiki: ch\u01B0a generate (ch\u1EA1y gitnexus wiki manual)");
|
|
1870
|
+
}
|
|
1871
|
+
} catch (err) {
|
|
1872
|
+
log.error(`Read meta.json fail: ${err.message}`);
|
|
1873
|
+
process.exit(1);
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
async function runGitnexusAnalyzeCommand() {
|
|
1877
|
+
const workspacePath = ensureWorkspaceCwd2();
|
|
1878
|
+
try {
|
|
1879
|
+
runGitnexusAnalyze(workspacePath);
|
|
1880
|
+
log.success("Index refreshed. Ch\u1EA1y `avatar gitnexus status` xem chi ti\u1EBFt.");
|
|
1881
|
+
} catch (err) {
|
|
1882
|
+
log.error(`Analyze fail: ${err.message}`);
|
|
1883
|
+
process.exit(1);
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
function registerGitnexusCommand(program2) {
|
|
1887
|
+
const gx = program2.command("gitnexus").description("Qu\u1EA3n l\xFD GitNexus code intelligence (M10)");
|
|
1888
|
+
gx.command("install").description("C\xE0i + setup GitNexus cho workspace hi\u1EC7n t\u1EA1i").action(async () => {
|
|
1889
|
+
await runGitnexusInstall();
|
|
1890
|
+
});
|
|
1891
|
+
gx.command("status").description("Show index info + staleness warning").action(async () => {
|
|
1892
|
+
await runGitnexusStatus();
|
|
1893
|
+
});
|
|
1894
|
+
gx.command("analyze").description("Re-run analyze refresh index (no wiki)").action(async () => {
|
|
1895
|
+
await runGitnexusAnalyzeCommand();
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
// src/commands/init.ts
|
|
1900
|
+
import { basename, join as join22, relative as relative2, resolve } from "path";
|
|
1901
|
+
import { confirm as confirm5, input as input5, select as select8 } from "@inquirer/prompts";
|
|
1902
|
+
import boxen5 from "boxen";
|
|
1903
|
+
|
|
1904
|
+
// src/lib/add-team-pack-submodule-with-retry-on-network-fail.ts
|
|
1905
|
+
import { spawnSync as spawnSync13 } from "child_process";
|
|
1906
|
+
import { select as select5 } from "@inquirer/prompts";
|
|
1907
|
+
|
|
1342
1908
|
// src/lib/team-pack-submodule-manager.ts
|
|
1343
|
-
import { join as
|
|
1909
|
+
import { join as join16 } from "path";
|
|
1344
1910
|
|
|
1345
1911
|
// src/lib/check-team-pack-access-with-retry-loop.ts
|
|
1346
|
-
import { spawnSync as
|
|
1347
|
-
import { confirm as
|
|
1348
|
-
import
|
|
1912
|
+
import { spawnSync as spawnSync12 } from "child_process";
|
|
1913
|
+
import { confirm as confirm4, select as select4 } from "@inquirer/prompts";
|
|
1914
|
+
import boxen3 from "boxen";
|
|
1349
1915
|
function parseRepoSlugFromGitUrl(url) {
|
|
1350
1916
|
const httpsMatch = url.match(/github\.com[/:]([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
1351
1917
|
if (httpsMatch) return httpsMatch[1];
|
|
1352
1918
|
return null;
|
|
1353
1919
|
}
|
|
1354
1920
|
function checkRepoAccess(repoSlug) {
|
|
1355
|
-
const r =
|
|
1921
|
+
const r = spawnSync12("gh", ["api", `repos/${repoSlug}`], { stdio: "ignore" });
|
|
1356
1922
|
return r.status === 0;
|
|
1357
1923
|
}
|
|
1358
1924
|
function getCurrentGhUser() {
|
|
1359
|
-
const r =
|
|
1925
|
+
const r = spawnSync12("gh", ["api", "user", "--jq", ".login"], {
|
|
1360
1926
|
encoding: "utf8",
|
|
1361
1927
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1362
1928
|
});
|
|
@@ -1365,13 +1931,13 @@ function getCurrentGhUser() {
|
|
|
1365
1931
|
}
|
|
1366
1932
|
function triggerGhAuthLoginInteractive() {
|
|
1367
1933
|
log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
|
|
1368
|
-
const r =
|
|
1934
|
+
const r = spawnSync12("gh", ["auth", "login", "--web"], { stdio: "inherit" });
|
|
1369
1935
|
if (r.status !== 0) {
|
|
1370
1936
|
log.warn(`gh auth login exit ${r.status}. C\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
|
|
1371
1937
|
}
|
|
1372
1938
|
}
|
|
1373
1939
|
async function copyInfoToClipboardWithConsent(info) {
|
|
1374
|
-
const ok = await
|
|
1940
|
+
const ok = await confirm4({
|
|
1375
1941
|
message: "Copy th\xF4ng tin (GitHub username + email) v\xE0o clipboard \u0111\u1EC3 d\xE1n v\xE0o Slack/email?",
|
|
1376
1942
|
default: true
|
|
1377
1943
|
});
|
|
@@ -1400,7 +1966,7 @@ function printAccessWarningBox(repoSlug, ghUser, ssoEmail) {
|
|
|
1400
1966
|
`${chalk.dim("Li\xEAn h\u1EC7:")} luke@nal.vn (Slack #avatar-setup)`
|
|
1401
1967
|
];
|
|
1402
1968
|
process.stdout.write(
|
|
1403
|
-
`${
|
|
1969
|
+
`${boxen3(lines.join("\n"), { padding: 1, borderColor: "red", borderStyle: "round" })}
|
|
1404
1970
|
`
|
|
1405
1971
|
);
|
|
1406
1972
|
}
|
|
@@ -1505,7 +2071,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
|
|
|
1505
2071
|
}
|
|
1506
2072
|
let target = tag ?? null;
|
|
1507
2073
|
if (!target) {
|
|
1508
|
-
target = await latestTag(
|
|
2074
|
+
target = await latestTag(join16(projectRoot, TEAM_PACK_RELATIVE_PATH));
|
|
1509
2075
|
}
|
|
1510
2076
|
if (target) {
|
|
1511
2077
|
await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, target, projectRoot);
|
|
@@ -1513,7 +2079,7 @@ async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
|
|
|
1513
2079
|
return { pinnedTag: target };
|
|
1514
2080
|
}
|
|
1515
2081
|
async function readPinnedPackVersion(projectRoot) {
|
|
1516
|
-
const submoduleRoot =
|
|
2082
|
+
const submoduleRoot = join16(projectRoot, TEAM_PACK_RELATIVE_PATH);
|
|
1517
2083
|
const tag = await latestTag(submoduleRoot);
|
|
1518
2084
|
if (tag) return tag;
|
|
1519
2085
|
const sha = await currentCommitSha(submoduleRoot);
|
|
@@ -1527,14 +2093,14 @@ function isSshPermissionError(message) {
|
|
|
1527
2093
|
}
|
|
1528
2094
|
function triggerGhAuthLoginInteractive2() {
|
|
1529
2095
|
log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
|
|
1530
|
-
const r =
|
|
2096
|
+
const r = spawnSync13("gh", ["auth", "login", "--web"], { stdio: "inherit" });
|
|
1531
2097
|
if (r.status !== 0) {
|
|
1532
2098
|
log.warn(`gh auth login exit ${r.status}. C\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
|
|
1533
2099
|
}
|
|
1534
2100
|
}
|
|
1535
2101
|
function openGithubSshKeysPage() {
|
|
1536
2102
|
log.info("M\u1EDF trang GitHub Settings \u2192 SSH Keys...");
|
|
1537
|
-
const r =
|
|
2103
|
+
const r = spawnSync13("open", ["https://github.com/settings/keys"], { stdio: "ignore" });
|
|
1538
2104
|
if (r.status !== 0) {
|
|
1539
2105
|
log.info("URL: https://github.com/settings/keys");
|
|
1540
2106
|
}
|
|
@@ -1684,7 +2250,7 @@ ${renderAvatarBanner(opts)}
|
|
|
1684
2250
|
}
|
|
1685
2251
|
|
|
1686
2252
|
// src/lib/execute-gh-repo-create.ts
|
|
1687
|
-
import { spawnSync as
|
|
2253
|
+
import { spawnSync as spawnSync14 } from "child_process";
|
|
1688
2254
|
var RepoAlreadyExistsError = class extends Error {
|
|
1689
2255
|
constructor(fullName) {
|
|
1690
2256
|
super(`Repo "${fullName}" \u0111\xE3 t\u1ED3n t\u1EA1i tr\xEAn GitHub. \u0110\u1ED5i t\xEAn ho\u1EB7c x\xF3a repo c\u0169.`);
|
|
@@ -1704,7 +2270,7 @@ function executeGhRepoCreate(input6) {
|
|
|
1704
2270
|
"origin",
|
|
1705
2271
|
"--push"
|
|
1706
2272
|
];
|
|
1707
|
-
const r =
|
|
2273
|
+
const r = spawnSync14("gh", args, { stdio: "inherit" });
|
|
1708
2274
|
if (r.status !== 0) {
|
|
1709
2275
|
if (r.status === 1) {
|
|
1710
2276
|
throw new RepoAlreadyExistsError(fullName);
|
|
@@ -1718,9 +2284,9 @@ function executeGhRepoCreate(input6) {
|
|
|
1718
2284
|
}
|
|
1719
2285
|
|
|
1720
2286
|
// src/lib/resolve-github-username-default.ts
|
|
1721
|
-
import { spawnSync as
|
|
2287
|
+
import { spawnSync as spawnSync15 } from "child_process";
|
|
1722
2288
|
function resolveGithubUsernameDefault() {
|
|
1723
|
-
const r =
|
|
2289
|
+
const r = spawnSync15("gh", ["api", "user", "--jq", ".login"], {
|
|
1724
2290
|
encoding: "utf8",
|
|
1725
2291
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1726
2292
|
});
|
|
@@ -1768,12 +2334,12 @@ function createGithubRemoteFromFolder(input6) {
|
|
|
1768
2334
|
}
|
|
1769
2335
|
|
|
1770
2336
|
// src/lib/create-workspace-remote-via-gh.ts
|
|
1771
|
-
import { spawnSync as
|
|
2337
|
+
import { spawnSync as spawnSync23 } from "child_process";
|
|
1772
2338
|
|
|
1773
2339
|
// src/lib/check-gh-cli-auth-status.ts
|
|
1774
|
-
import { spawnSync as
|
|
2340
|
+
import { spawnSync as spawnSync16 } from "child_process";
|
|
1775
2341
|
function checkGhCliAuthStatus() {
|
|
1776
|
-
const r =
|
|
2342
|
+
const r = spawnSync16("gh", ["auth", "status"], { stdio: "ignore" });
|
|
1777
2343
|
if (r.error && r.error.code === "ENOENT") {
|
|
1778
2344
|
return "not-installed";
|
|
1779
2345
|
}
|
|
@@ -1781,12 +2347,12 @@ function checkGhCliAuthStatus() {
|
|
|
1781
2347
|
}
|
|
1782
2348
|
|
|
1783
2349
|
// src/lib/detect-package-manager.ts
|
|
1784
|
-
import { spawnSync as
|
|
2350
|
+
import { spawnSync as spawnSync17 } from "child_process";
|
|
1785
2351
|
function hasBinary(name) {
|
|
1786
2352
|
const platform2 = detectHostPlatform();
|
|
1787
2353
|
const probe = platform2 === "win32" ? "where" : "command";
|
|
1788
2354
|
const args = platform2 === "win32" ? [name] : ["-v", name];
|
|
1789
|
-
const r =
|
|
2355
|
+
const r = spawnSync17(probe, args, {
|
|
1790
2356
|
shell: platform2 !== "win32",
|
|
1791
2357
|
stdio: "ignore"
|
|
1792
2358
|
});
|
|
@@ -1802,11 +2368,11 @@ function detectPackageManager() {
|
|
|
1802
2368
|
}
|
|
1803
2369
|
|
|
1804
2370
|
// src/lib/handle-remote-access-failure-with-account-switch.ts
|
|
1805
|
-
import { spawnSync as
|
|
2371
|
+
import { spawnSync as spawnSync19 } from "child_process";
|
|
1806
2372
|
import { input as input4, select as select6 } from "@inquirer/prompts";
|
|
1807
2373
|
|
|
1808
2374
|
// src/lib/verify-git-remote-accessible.ts
|
|
1809
|
-
import { spawnSync as
|
|
2375
|
+
import { spawnSync as spawnSync18 } from "child_process";
|
|
1810
2376
|
var TIMEOUT_MS = 5e3;
|
|
1811
2377
|
function classifyRemoteError(stderr) {
|
|
1812
2378
|
const text = stderr.toLowerCase();
|
|
@@ -1822,7 +2388,7 @@ function classifyRemoteError(stderr) {
|
|
|
1822
2388
|
return "unknown";
|
|
1823
2389
|
}
|
|
1824
2390
|
function tryVerifyGitRemoteAccessible(url) {
|
|
1825
|
-
const r =
|
|
2391
|
+
const r = spawnSync18("git", ["ls-remote", "--exit-code", url, "HEAD"], {
|
|
1826
2392
|
encoding: "utf8",
|
|
1827
2393
|
timeout: TIMEOUT_MS,
|
|
1828
2394
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -1844,7 +2410,7 @@ var RemoteAccessAbortedError = class extends Error {
|
|
|
1844
2410
|
}
|
|
1845
2411
|
};
|
|
1846
2412
|
function getCurrentGhUser2() {
|
|
1847
|
-
const r =
|
|
2413
|
+
const r = spawnSync19("gh", ["api", "user", "--jq", ".login"], {
|
|
1848
2414
|
encoding: "utf8",
|
|
1849
2415
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1850
2416
|
});
|
|
@@ -1853,7 +2419,7 @@ function getCurrentGhUser2() {
|
|
|
1853
2419
|
}
|
|
1854
2420
|
function triggerGhAuthLoginInteractive3() {
|
|
1855
2421
|
log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
|
|
1856
|
-
const r =
|
|
2422
|
+
const r = spawnSync19("gh", ["auth", "login", "--web"], { stdio: "inherit" });
|
|
1857
2423
|
if (r.status !== 0) {
|
|
1858
2424
|
log.warn(`gh auth login exit ${r.status}. B\u1EA1n c\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
|
|
1859
2425
|
}
|
|
@@ -1936,7 +2502,7 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
|
|
|
1936
2502
|
}
|
|
1937
2503
|
|
|
1938
2504
|
// src/lib/install-gh-cli-via-package-manager.ts
|
|
1939
|
-
import { spawnSync as
|
|
2505
|
+
import { spawnSync as spawnSync20 } from "child_process";
|
|
1940
2506
|
var INSTALL_COMMANDS = {
|
|
1941
2507
|
brew: { cmd: "brew", args: ["install", "gh"] },
|
|
1942
2508
|
apt: { cmd: "sudo", args: ["apt-get", "install", "-y", "gh"] },
|
|
@@ -1947,7 +2513,7 @@ var INSTALL_COMMANDS = {
|
|
|
1947
2513
|
function installGhCliViaPackageManager(pm) {
|
|
1948
2514
|
const spec = INSTALL_COMMANDS[pm];
|
|
1949
2515
|
log.info(`\u0110ang c\xE0i gh CLI qua ${pm}...`);
|
|
1950
|
-
const r =
|
|
2516
|
+
const r = spawnSync20(spec.cmd, spec.args, { stdio: "inherit" });
|
|
1951
2517
|
if (r.status !== 0) {
|
|
1952
2518
|
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.`);
|
|
1953
2519
|
}
|
|
@@ -1955,9 +2521,9 @@ function installGhCliViaPackageManager(pm) {
|
|
|
1955
2521
|
}
|
|
1956
2522
|
|
|
1957
2523
|
// src/lib/setup-git-credential-via-gh.ts
|
|
1958
|
-
import { spawnSync as
|
|
2524
|
+
import { spawnSync as spawnSync21 } from "child_process";
|
|
1959
2525
|
function setupGitCredentialViaGh() {
|
|
1960
|
-
const r =
|
|
2526
|
+
const r = spawnSync21("gh", ["auth", "setup-git"], { stdio: "ignore" });
|
|
1961
2527
|
if (r.status !== 0) {
|
|
1962
2528
|
log.warn("gh auth setup-git fail (non-fatal). N\u1EBFu git clone l\u1ED7i 128 \u2192 ch\u1EA1y th\u1EE7 c\xF4ng.");
|
|
1963
2529
|
return;
|
|
@@ -1966,10 +2532,10 @@ function setupGitCredentialViaGh() {
|
|
|
1966
2532
|
}
|
|
1967
2533
|
|
|
1968
2534
|
// src/lib/trigger-gh-cli-auth-login.ts
|
|
1969
|
-
import { spawnSync as
|
|
2535
|
+
import { spawnSync as spawnSync22 } from "child_process";
|
|
1970
2536
|
function triggerGhCliAuthLogin() {
|
|
1971
2537
|
log.info("Kh\u1EDFi \u0111\u1ED9ng \u0111\u0103ng nh\u1EADp GitHub qua gh CLI (browser s\u1EBD m\u1EDF)...");
|
|
1972
|
-
const r =
|
|
2538
|
+
const r = spawnSync22(
|
|
1973
2539
|
"gh",
|
|
1974
2540
|
["auth", "login", "--hostname", "github.com", "--web", "--git-protocol", "ssh"],
|
|
1975
2541
|
{ stdio: "inherit" }
|
|
@@ -2087,20 +2653,20 @@ function classifyGhCreateError(stderr) {
|
|
|
2087
2653
|
return "unknown";
|
|
2088
2654
|
}
|
|
2089
2655
|
function repoExistsOnGitHub(fullName) {
|
|
2090
|
-
const r =
|
|
2656
|
+
const r = spawnSync23("gh", ["repo", "view", fullName, "--json", "name"], {
|
|
2091
2657
|
stdio: "ignore"
|
|
2092
2658
|
});
|
|
2093
2659
|
return r.status === 0;
|
|
2094
2660
|
}
|
|
2095
2661
|
function canCreateInNamespace(org, ghUser) {
|
|
2096
2662
|
if (org.toLowerCase() === ghUser.toLowerCase()) return { ok: true };
|
|
2097
|
-
const r =
|
|
2663
|
+
const r = spawnSync23("gh", ["api", `orgs/${org}/members/${ghUser}`, "--silent"], {
|
|
2098
2664
|
stdio: "ignore"
|
|
2099
2665
|
});
|
|
2100
2666
|
if (r.status === 0) return { ok: true };
|
|
2101
|
-
const orgCheck =
|
|
2667
|
+
const orgCheck = spawnSync23("gh", ["api", `orgs/${org}`, "--silent"], { stdio: "ignore" });
|
|
2102
2668
|
if (orgCheck.status !== 0) {
|
|
2103
|
-
const userCheck =
|
|
2669
|
+
const userCheck = spawnSync23("gh", ["api", `users/${org}`, "--silent"], { stdio: "ignore" });
|
|
2104
2670
|
if (userCheck.status === 0) {
|
|
2105
2671
|
return {
|
|
2106
2672
|
ok: false,
|
|
@@ -2136,7 +2702,7 @@ async function createWorkspaceRemoteViaGh(input6) {
|
|
|
2136
2702
|
);
|
|
2137
2703
|
}
|
|
2138
2704
|
log.info(`T\u1EA1o GitHub repo cho workspace: ${fullName} (${input6.visibility})...`);
|
|
2139
|
-
const r =
|
|
2705
|
+
const r = spawnSync23(
|
|
2140
2706
|
"gh",
|
|
2141
2707
|
[
|
|
2142
2708
|
"repo",
|
|
@@ -2177,7 +2743,7 @@ ${combined}
|
|
|
2177
2743
|
function linkExistingRemoteToWorkspace(args) {
|
|
2178
2744
|
const sshUrl = `git@github.com:${args.fullName}.git`;
|
|
2179
2745
|
const httpsUrl = `https://github.com/${args.fullName}.git`;
|
|
2180
|
-
const addResult =
|
|
2746
|
+
const addResult = spawnSync23(
|
|
2181
2747
|
"git",
|
|
2182
2748
|
["-C", args.workspacePath, "remote", "add", "origin", sshUrl],
|
|
2183
2749
|
{
|
|
@@ -2186,7 +2752,7 @@ function linkExistingRemoteToWorkspace(args) {
|
|
|
2186
2752
|
}
|
|
2187
2753
|
);
|
|
2188
2754
|
if (addResult.status !== 0) {
|
|
2189
|
-
|
|
2755
|
+
spawnSync23("git", ["-C", args.workspacePath, "remote", "set-url", "origin", sshUrl], {
|
|
2190
2756
|
stdio: "ignore"
|
|
2191
2757
|
});
|
|
2192
2758
|
}
|
|
@@ -2200,11 +2766,11 @@ import { select as select7 } from "@inquirer/prompts";
|
|
|
2200
2766
|
import { simpleGit as simpleGit3 } from "simple-git";
|
|
2201
2767
|
|
|
2202
2768
|
// src/lib/check-folder-has-git.ts
|
|
2203
|
-
import { existsSync as
|
|
2204
|
-
import { join as
|
|
2769
|
+
import { existsSync as existsSync6, statSync } from "fs";
|
|
2770
|
+
import { join as join17 } from "path";
|
|
2205
2771
|
function checkFolderHasGit(folderPath) {
|
|
2206
|
-
const gitPath =
|
|
2207
|
-
if (!
|
|
2772
|
+
const gitPath = join17(folderPath, ".git");
|
|
2773
|
+
if (!existsSync6(gitPath)) return false;
|
|
2208
2774
|
const stat = statSync(gitPath);
|
|
2209
2775
|
return stat.isDirectory() || stat.isFile();
|
|
2210
2776
|
}
|
|
@@ -2234,8 +2800,8 @@ async function createInitialGitCommit(folderPath) {
|
|
|
2234
2800
|
}
|
|
2235
2801
|
|
|
2236
2802
|
// src/lib/detect-folder-tech-stack.ts
|
|
2237
|
-
import { existsSync as
|
|
2238
|
-
import { join as
|
|
2803
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2804
|
+
import { join as join18 } from "path";
|
|
2239
2805
|
var SIGNATURES = {
|
|
2240
2806
|
node: ["package.json"],
|
|
2241
2807
|
python: ["pyproject.toml", "requirements.txt", "setup.py", "Pipfile"],
|
|
@@ -2247,7 +2813,7 @@ var SIGNATURES = {
|
|
|
2247
2813
|
function detectFolderTechStack(folderPath) {
|
|
2248
2814
|
const matched = [];
|
|
2249
2815
|
for (const [stack, files] of Object.entries(SIGNATURES)) {
|
|
2250
|
-
if (files.some((f) =>
|
|
2816
|
+
if (files.some((f) => existsSync7(join18(folderPath, f)))) {
|
|
2251
2817
|
matched.push(stack);
|
|
2252
2818
|
}
|
|
2253
2819
|
}
|
|
@@ -2256,25 +2822,25 @@ function detectFolderTechStack(folderPath) {
|
|
|
2256
2822
|
|
|
2257
2823
|
// src/lib/gitignore-template-loader.ts
|
|
2258
2824
|
import { readFileSync as readFileSync3 } from "fs";
|
|
2259
|
-
import { dirname as dirname4, join as
|
|
2825
|
+
import { dirname as dirname4, join as join19 } from "path";
|
|
2260
2826
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2261
2827
|
var __dirname = dirname4(fileURLToPath2(import.meta.url));
|
|
2262
2828
|
var CANDIDATE_DIRS = [
|
|
2263
2829
|
// Bundled production: dist/index.js → __dirname = .../dist/, sibling dist/templates
|
|
2264
|
-
|
|
2830
|
+
join19(__dirname, "templates", "gitignore"),
|
|
2265
2831
|
// Legacy bundled: nếu file là dist/lib/*.js (sub-bundle), templates ở dist/templates
|
|
2266
|
-
|
|
2832
|
+
join19(__dirname, "..", "templates", "gitignore"),
|
|
2267
2833
|
// Dev mode (vitest/tsx run src/ trực tiếp): __dirname = src/lib/
|
|
2268
|
-
|
|
2834
|
+
join19(__dirname, "..", "..", "src", "templates", "gitignore"),
|
|
2269
2835
|
// npm-installed alt: __dirname = .../dist/ → package_root/src/templates
|
|
2270
|
-
|
|
2836
|
+
join19(__dirname, "..", "src", "templates", "gitignore")
|
|
2271
2837
|
];
|
|
2272
2838
|
var AVATAR_MARKER_START = "# === avatar ===";
|
|
2273
2839
|
var AVATAR_MARKER_END = "# === /avatar ===";
|
|
2274
2840
|
function readTemplate(stack) {
|
|
2275
2841
|
for (const dir of CANDIDATE_DIRS) {
|
|
2276
2842
|
try {
|
|
2277
|
-
return readFileSync3(
|
|
2843
|
+
return readFileSync3(join19(dir, `${stack}.txt`), "utf8");
|
|
2278
2844
|
} catch {
|
|
2279
2845
|
}
|
|
2280
2846
|
}
|
|
@@ -2288,11 +2854,11 @@ ${readTemplate(s).trim()}`);
|
|
|
2288
2854
|
}
|
|
2289
2855
|
|
|
2290
2856
|
// src/lib/write-or-merge-gitignore.ts
|
|
2291
|
-
import { existsSync as
|
|
2292
|
-
import { join as
|
|
2857
|
+
import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
2858
|
+
import { join as join20 } from "path";
|
|
2293
2859
|
function writeOrMergeGitignore(folderPath, avatarBlock) {
|
|
2294
|
-
const path =
|
|
2295
|
-
if (!
|
|
2860
|
+
const path = join20(folderPath, ".gitignore");
|
|
2861
|
+
if (!existsSync8(path)) {
|
|
2296
2862
|
writeFileSync(path, avatarBlock, "utf8");
|
|
2297
2863
|
return;
|
|
2298
2864
|
}
|
|
@@ -2468,7 +3034,7 @@ async function safeBootstrapGitInFolder(folderPath, opts = {}) {
|
|
|
2468
3034
|
|
|
2469
3035
|
// src/commands/init-conflict-detection-helpers.ts
|
|
2470
3036
|
import { readdir } from "fs/promises";
|
|
2471
|
-
import { join as
|
|
3037
|
+
import { join as join21 } from "path";
|
|
2472
3038
|
async function isEmptyOrMissing(path) {
|
|
2473
3039
|
if (!await pathExists(path)) return true;
|
|
2474
3040
|
try {
|
|
@@ -2481,7 +3047,7 @@ async function isEmptyOrMissing(path) {
|
|
|
2481
3047
|
}
|
|
2482
3048
|
async function findAlternativeWorkspaceName(parent, desiredName, maxAttempts = 10) {
|
|
2483
3049
|
for (let i = 2; i < maxAttempts; i++) {
|
|
2484
|
-
const candidate =
|
|
3050
|
+
const candidate = join21(parent, `${desiredName}-${i}`);
|
|
2485
3051
|
if (await isEmptyOrMissing(candidate)) return candidate;
|
|
2486
3052
|
}
|
|
2487
3053
|
return null;
|
|
@@ -2497,6 +3063,38 @@ function inferWorkspaceName(repoUrl) {
|
|
|
2497
3063
|
const withoutPrefix = baseName.replace(/^avatar-/, "");
|
|
2498
3064
|
return `avatar-${withoutPrefix}-workspace`;
|
|
2499
3065
|
}
|
|
3066
|
+
function buildGitnexusSection(gitnexusReady) {
|
|
3067
|
+
if (!gitnexusReady) return "";
|
|
3068
|
+
return `
|
|
3069
|
+
### \u{1F9E0} CODEBASE INTELLIGENCE \u2014 GitNexus
|
|
3070
|
+
|
|
3071
|
+
Workspace c\xF3 GitNexus index t\u1EA1i \`.gitnexus/\` cung c\u1EA5p architectural awareness
|
|
3072
|
+
cho Claude Code (impact analysis, call chains, blast radius).
|
|
3073
|
+
|
|
3074
|
+
**H\u01B0\u1EDBng d\u1EABn cho Claude:**
|
|
3075
|
+
|
|
3076
|
+
- Khi c\u1EA7n hi\u1EC3u codebase architecture (vd: tr\u01B0\u1EDBc refactor, debug call chain) \u2192
|
|
3077
|
+
query GitNexus MCP resource \`gitnexus://repo/<name>/context\`
|
|
3078
|
+
- Khi resource tr\u1EA3 staleness hint "\u26A0\uFE0F Index is N commit(s) behind HEAD" \u2192
|
|
3079
|
+
T\u1EF0 ch\u1EA1y \`gitnexus analyze .\` (trong workspace root) tr\u01B0\u1EDBc khi ti\u1EBFp t\u1EE5c task
|
|
3080
|
+
- Sau khi \`gitnexus analyze\` xong: **KH\xD4NG t\u1EF1 ch\u1EA1y \`gitnexus wiki\`**
|
|
3081
|
+
(cost cao, c\xF3 th\u1EC3 block session 2-5 ph\xFAt) \u2014 user s\u1EBD ch\u1EA1y manual khi c\u1EA7n
|
|
3082
|
+
|
|
3083
|
+
**Folders Claude scan auto cho skills:**
|
|
3084
|
+
|
|
3085
|
+
- \`~/.claude/skills/gitnexus-*/\` \u2014 GitNexus global skills (exploring, debugging, ...)
|
|
3086
|
+
- \`.claude/pack/skills/\` \u2014 NAL team-shared skills (qua team-ai-pack submodule)
|
|
3087
|
+
- C\u1EA3 2 \u0111\u1EC1u \u0111\u01B0\u1EE3c scan, kh\xF4ng xung \u0111\u1ED9t (different naming prefix)
|
|
3088
|
+
|
|
3089
|
+
**Manual wiki update:**
|
|
3090
|
+
|
|
3091
|
+
Khi user c\u1EA7n regenerate wiki sau refactor l\u1EDBn \u2014 ch\u1EA1y:
|
|
3092
|
+
|
|
3093
|
+
\`\`\`bash
|
|
3094
|
+
gitnexus wiki . --api-key <key> --base-url <url>
|
|
3095
|
+
\`\`\`
|
|
3096
|
+
`;
|
|
3097
|
+
}
|
|
2500
3098
|
function buildScaffoldVariables(args) {
|
|
2501
3099
|
return {
|
|
2502
3100
|
projectName: args.projectName,
|
|
@@ -2505,12 +3103,13 @@ function buildScaffoldVariables(args) {
|
|
|
2505
3103
|
avatarVersion: AVATAR_CLI_VERSION,
|
|
2506
3104
|
packVersion: args.packVersion,
|
|
2507
3105
|
lastScan: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2508
|
-
mode: args.mode
|
|
3106
|
+
mode: args.mode,
|
|
3107
|
+
gitnexusSection: buildGitnexusSection(args.gitnexusReady ?? false)
|
|
2509
3108
|
};
|
|
2510
3109
|
}
|
|
2511
3110
|
|
|
2512
3111
|
// src/commands/login.ts
|
|
2513
|
-
import
|
|
3112
|
+
import boxen4 from "boxen";
|
|
2514
3113
|
import open from "open";
|
|
2515
3114
|
|
|
2516
3115
|
// src/lib/google-oauth-device-flow.ts
|
|
@@ -2657,7 +3256,7 @@ async function runLogin(opts) {
|
|
|
2657
3256
|
"",
|
|
2658
3257
|
`Ho\u1EB7c Avatar t\u1EF1 m\u1EDF browser, click ${chalk.green("Allow")}...`
|
|
2659
3258
|
].join("\n");
|
|
2660
|
-
process.stdout.write(`${
|
|
3259
|
+
process.stdout.write(`${boxen4(instructions, { padding: 1, borderStyle: "round" })}
|
|
2661
3260
|
`);
|
|
2662
3261
|
void open(verificationUrl).catch(() => {
|
|
2663
3262
|
log.dim("(Kh\xF4ng m\u1EDF \u0111\u01B0\u1EE3c browser t\u1EF1 \u0111\u1ED9ng \u2014 copy URL \u1EDF tr\xEAn)");
|
|
@@ -2713,6 +3312,9 @@ function parseBootstrapStrategyOpts(opts) {
|
|
|
2713
3312
|
}
|
|
2714
3313
|
function registerInitCommand(program2) {
|
|
2715
3314
|
program2.command("init").description("Kh\u1EDFi t\u1EA1o Avatar \u2014 3 flow t\u1EF1 nh\u1EADn di\u1EC7n (repo / folder / new)").option("--project-status <val>", "existing-remote | existing-folder | new-project").option("--folder-path <path>", "\u0110\u01B0\u1EDDng d\u1EABn folder hi\u1EC7n c\xF3 (flow existing-folder)").option("--create-remote", "Force t\u1EA1o remote qua gh (flow existing-folder ho\u1EB7c new-project)").option("--repo-visibility <val>", "private (m\u1EB7c \u0111\u1ECBnh) | public").option("--repo-org <name>", "GitHub org/owner cho repo m\u1EDBi").option("--client-repo <url>", "URL git remote (flow existing-remote)").option("--workspace-name <name>", "T\xEAn workspace").option("--workspace-parent <path>", "Th\u01B0 m\u1EE5c cha t\u1EA1o workspace (m\u1EB7c \u0111\u1ECBnh . \u2014 CWD)").option("--pack-version <tag>", "Pin team-ai-pack v\xE0o tag c\u1EE5 th\u1EC3").option("--team-owner <email>", "Email team owner (b\u1ECF qua prompt)").option("--description <text>", "M\xF4 t\u1EA3 1 d\xF2ng c\u1EE7a d\u1EF1 \xE1n").option("--skip-scan", "B\u1ECF qua project-scanner sau scaffold").option("--skip-team-pack", "B\u1ECF qua submodule team-ai-pack (test mode)").option("--force", "B\u1ECF qua prompt khi workspace path \u0111\xE3 t\u1ED3n t\u1EA1i").option("--yes", "Auto-confirm t\u1EA5t c\u1EA3 prompt").option("--no-commit", "Skip commit workspace initial state (m\u1EB7c \u0111\u1ECBnh LU\xD4N commit)").option("--workspace-remote", "T\u1EA1o GitHub remote cho workspace root (default: prompt)").option("--ai-skip", "B\u1ECF qua phase AI setup (CI/test mode \u2014 ch\u1EA1y `avatar ai setup` sau)").option(
|
|
3315
|
+
"--gitnexus-skip",
|
|
3316
|
+
"B\u1ECF qua phase GitNexus setup (M10 \u2014 ch\u1EA1y `avatar gitnexus install` sau)"
|
|
3317
|
+
).option(
|
|
2716
3318
|
"--bootstrap-strategy <s>",
|
|
2717
3319
|
"X\u1EED l\xFD folder dirty: stash | commit-all | skip | branch (default: prompt)"
|
|
2718
3320
|
).option("--preserve-uncommitted", "Alias cho --bootstrap-strategy=stash (gi\u1EEF changes user)").option("--mode <mode>", "[DEPRECATED] D\xF9ng --project-status thay th\u1EBF").action(async (opts) => {
|
|
@@ -2818,6 +3420,7 @@ async function runInitFromExistingRemote(opts, ownerEmail) {
|
|
|
2818
3420
|
repoOrg: opts.repoOrg,
|
|
2819
3421
|
flow: "existing-remote",
|
|
2820
3422
|
aiSkip: opts.aiSkip,
|
|
3423
|
+
gitnexusSkip: opts.gitnexusSkip,
|
|
2821
3424
|
ssoEmail: ownerEmail
|
|
2822
3425
|
});
|
|
2823
3426
|
}
|
|
@@ -2854,6 +3457,7 @@ async function runInitFromExistingFolder(opts, ownerEmail) {
|
|
|
2854
3457
|
repoOrg: opts.repoOrg,
|
|
2855
3458
|
flow: "existing-folder",
|
|
2856
3459
|
aiSkip: opts.aiSkip,
|
|
3460
|
+
gitnexusSkip: opts.gitnexusSkip,
|
|
2857
3461
|
ssoEmail: ownerEmail
|
|
2858
3462
|
});
|
|
2859
3463
|
}
|
|
@@ -2873,7 +3477,7 @@ async function runInitFromScratch(opts, ownerEmail) {
|
|
|
2873
3477
|
const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
|
|
2874
3478
|
const workspaceParent = resolve(opts.workspaceParent ?? ".");
|
|
2875
3479
|
const workspacePath = await resolveWorkspacePath(workspaceParent, projectName, opts.force);
|
|
2876
|
-
const srcPath =
|
|
3480
|
+
const srcPath = join22(workspacePath, "src");
|
|
2877
3481
|
await ensureDir(workspacePath);
|
|
2878
3482
|
await ensureDir(srcPath);
|
|
2879
3483
|
await safeBootstrapGitInFolder(srcPath, { autoYes: true });
|
|
@@ -2914,7 +3518,8 @@ async function runInitFromScratch(opts, ownerEmail) {
|
|
|
2914
3518
|
repoVisibility: opts.repoVisibility,
|
|
2915
3519
|
repoOrg: opts.repoOrg,
|
|
2916
3520
|
flow: "new-project",
|
|
2917
|
-
aiSkip: opts.aiSkip
|
|
3521
|
+
aiSkip: opts.aiSkip,
|
|
3522
|
+
gitnexusSkip: opts.gitnexusSkip
|
|
2918
3523
|
});
|
|
2919
3524
|
} catch (err) {
|
|
2920
3525
|
sp.fail("Init workspace th\u1EA5t b\u1EA1i");
|
|
@@ -2928,7 +3533,7 @@ async function getOrCreateOriginRemote(folderPath, opts) {
|
|
|
2928
3533
|
log.success(`Folder \u0111\xE3 c\xF3 remote origin: ${origin.refs.push}`);
|
|
2929
3534
|
return origin.refs.push;
|
|
2930
3535
|
}
|
|
2931
|
-
const shouldCreate = opts.createRemote ?? await
|
|
3536
|
+
const shouldCreate = opts.createRemote ?? await confirm5({
|
|
2932
3537
|
message: "Folder ch\u01B0a c\xF3 remote. T\u1EA1o GitHub repo ngay \u0111\u1EC3 share team?",
|
|
2933
3538
|
default: true
|
|
2934
3539
|
});
|
|
@@ -2989,7 +3594,8 @@ async function scaffoldWorkspaceWithSrcSubmodule(args) {
|
|
|
2989
3594
|
repoVisibility: args.repoVisibility,
|
|
2990
3595
|
repoOrg: args.repoOrg,
|
|
2991
3596
|
flow: args.flow,
|
|
2992
|
-
aiSkip: args.aiSkip
|
|
3597
|
+
aiSkip: args.aiSkip,
|
|
3598
|
+
gitnexusSkip: args.gitnexusSkip
|
|
2993
3599
|
});
|
|
2994
3600
|
} catch (err) {
|
|
2995
3601
|
sp.fail("Init workspace th\u1EA5t b\u1EA1i");
|
|
@@ -3009,10 +3615,10 @@ async function finalizeWorkspaceScaffold(args) {
|
|
|
3009
3615
|
await writeRootClaudeMd(args.workspacePath, vars);
|
|
3010
3616
|
await writeProjectSettings(args.workspacePath, vars);
|
|
3011
3617
|
await appendGitignoreEntries(args.workspacePath);
|
|
3012
|
-
await ensureDir(
|
|
3013
|
-
await ensureDir(
|
|
3014
|
-
await installGitHook(
|
|
3015
|
-
await installGitHook(
|
|
3618
|
+
await ensureDir(join22(args.workspacePath, "notes"));
|
|
3619
|
+
await ensureDir(join22(args.workspacePath, "scripts"));
|
|
3620
|
+
await installGitHook(join22(args.workspacePath, ".git"), "post-merge");
|
|
3621
|
+
await installGitHook(join22(args.workspacePath, ".git", "modules", "src"), "pre-push");
|
|
3016
3622
|
log.success("C\xE0i post-merge (workspace) + pre-push (src/)");
|
|
3017
3623
|
await appendAuditEntry("init", `flow=${args.flow},workspace=${args.workspaceName}`);
|
|
3018
3624
|
await maybeCommitWorkspace(args.workspacePath, args.skipCommit);
|
|
@@ -3023,7 +3629,30 @@ async function finalizeWorkspaceScaffold(args) {
|
|
|
3023
3629
|
} else {
|
|
3024
3630
|
aiResult = await runAiSetupPhase({ workspacePath: args.workspacePath });
|
|
3025
3631
|
}
|
|
3026
|
-
|
|
3632
|
+
let gitnexusResult = null;
|
|
3633
|
+
const skipGitnexus = args.aiSkip || args.gitnexusSkip;
|
|
3634
|
+
if (skipGitnexus) {
|
|
3635
|
+
if (args.gitnexusSkip) {
|
|
3636
|
+
log.dim("B\u1ECF qua GitNexus setup (--gitnexus-skip). Setup sau: avatar gitnexus install");
|
|
3637
|
+
} else {
|
|
3638
|
+
log.dim("B\u1ECF qua GitNexus setup (auto-skip do --ai-skip).");
|
|
3639
|
+
}
|
|
3640
|
+
} else {
|
|
3641
|
+
gitnexusResult = await runGitnexusSetupPhase({ workspacePath: args.workspacePath });
|
|
3642
|
+
}
|
|
3643
|
+
if (gitnexusResult?.ok) {
|
|
3644
|
+
const updatedVars = buildScaffoldVariables({
|
|
3645
|
+
projectName: args.workspaceName,
|
|
3646
|
+
projectDescription: args.description,
|
|
3647
|
+
teamOwner: args.teamOwner,
|
|
3648
|
+
packVersion: args.packVersion,
|
|
3649
|
+
mode: "client",
|
|
3650
|
+
gitnexusReady: true
|
|
3651
|
+
});
|
|
3652
|
+
await writeRootClaudeMd(args.workspacePath, updatedVars);
|
|
3653
|
+
log.dim("Updated CLAUDE.md v\u1EDBi GitNexus section");
|
|
3654
|
+
}
|
|
3655
|
+
printInitSuccessBox(args.workspacePath, args.flow, aiResult, gitnexusResult);
|
|
3027
3656
|
}
|
|
3028
3657
|
async function maybeCreateWorkspaceRemote(args) {
|
|
3029
3658
|
if (args.skipCommit) {
|
|
@@ -3033,7 +3662,7 @@ async function maybeCreateWorkspaceRemote(args) {
|
|
|
3033
3662
|
let shouldCreate = args.createWorkspaceRemote;
|
|
3034
3663
|
if (shouldCreate === void 0) {
|
|
3035
3664
|
if (args.autoYes) return;
|
|
3036
|
-
shouldCreate = await
|
|
3665
|
+
shouldCreate = await confirm5({
|
|
3037
3666
|
message: "T\u1EA1o remote GitHub cho workspace \u0111\u1EC3 share team? (Avatar state)",
|
|
3038
3667
|
default: false
|
|
3039
3668
|
});
|
|
@@ -3112,7 +3741,7 @@ async function maybeCreateWorkspaceRemote(args) {
|
|
|
3112
3741
|
}
|
|
3113
3742
|
}
|
|
3114
3743
|
async function resolveWorkspacePath(parent, desiredName, force) {
|
|
3115
|
-
const desired =
|
|
3744
|
+
const desired = join22(parent, desiredName);
|
|
3116
3745
|
if (await isEmptyOrMissing(desired)) return desired;
|
|
3117
3746
|
log.warn(`Workspace path "${desired}" \u0111\xE3 c\xF3 n\u1ED9i dung.`);
|
|
3118
3747
|
while (true) {
|
|
@@ -3143,7 +3772,7 @@ async function resolveWorkspacePath(parent, desiredName, force) {
|
|
|
3143
3772
|
message: "T\xEAn workspace m\u1EDBi:",
|
|
3144
3773
|
validate: (v) => v.trim().length > 0 ? true : "T\xEAn kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng"
|
|
3145
3774
|
});
|
|
3146
|
-
const newPath =
|
|
3775
|
+
const newPath = join22(parent, newName.trim());
|
|
3147
3776
|
if (await isEmptyOrMissing(newPath)) return newPath;
|
|
3148
3777
|
log.warn(`"${newPath}" c\u0169ng \u0111\xE3 c\xF3 n\u1ED9i dung. Th\u1EED t\xEAn kh\xE1c.`);
|
|
3149
3778
|
}
|
|
@@ -3171,21 +3800,34 @@ function formatAiStatusLine(aiResult) {
|
|
|
3171
3800
|
}
|
|
3172
3801
|
return ` ${chalk.yellow("AI:")} failed (${aiResult.reason.slice(0, 60)}) \xB7 th\u1EED ${chalk.cyan("avatar ai setup")}`;
|
|
3173
3802
|
}
|
|
3174
|
-
function
|
|
3803
|
+
function formatGitnexusStatusLine(result) {
|
|
3804
|
+
if (result === null) {
|
|
3805
|
+
return ` ${chalk.yellow("GitNexus:")} skipped \xB7 ${chalk.cyan("avatar gitnexus install")} \u0111\u1EC3 setup sau`;
|
|
3806
|
+
}
|
|
3807
|
+
if (result.ok) {
|
|
3808
|
+
const parts = ["ready"];
|
|
3809
|
+
if (result.analyzed) parts.push("indexed");
|
|
3810
|
+
if (result.wikiGenerated) parts.push("wiki");
|
|
3811
|
+
if (result.mcpRegistered) parts.push("mcp");
|
|
3812
|
+
return ` ${chalk.green("GitNexus:")} ${parts.join(" \xB7 ")}`;
|
|
3813
|
+
}
|
|
3814
|
+
return ` ${chalk.yellow("GitNexus:")} skipped (${(result.reason ?? "unknown").slice(0, 40)}) \xB7 th\u1EED ${chalk.cyan("avatar gitnexus install")}`;
|
|
3815
|
+
}
|
|
3816
|
+
function printInitSuccessBox(rootPath, flow, aiResult = null, gitnexusResult = null) {
|
|
3175
3817
|
const lines = [
|
|
3176
3818
|
`${chalk.green("\u2713")} Workspace s\u1EB5n s\xE0ng: ${relative2(process.cwd(), rootPath) || rootPath}`,
|
|
3177
3819
|
` ${chalk.dim(`(flow: ${flow})`)}`,
|
|
3178
3820
|
formatAiStatusLine(aiResult),
|
|
3821
|
+
formatGitnexusStatusLine(gitnexusResult),
|
|
3179
3822
|
"",
|
|
3180
3823
|
` ${chalk.cyan(`cd ${rootPath}`)}`,
|
|
3181
3824
|
` ${chalk.cyan("claude")} M\u1EDF Claude Code \u1EDF workspace root`,
|
|
3182
3825
|
"",
|
|
3183
|
-
` ${chalk.cyan("avatar commit
|
|
3184
|
-
` ${chalk.cyan("avatar commit --avatar")} Commit Avatar state`,
|
|
3826
|
+
` ${chalk.cyan("avatar commit src")} Commit code l\xEAn client remote`,
|
|
3185
3827
|
` ${chalk.cyan("avatar sync")} Pull team-ai-pack m\u1EDBi`,
|
|
3186
3828
|
` ${chalk.cyan("avatar uninstall")} G\u1EE1 Avatar (gi\u1EEF code)`
|
|
3187
3829
|
];
|
|
3188
|
-
process.stdout.write(`${
|
|
3830
|
+
process.stdout.write(`${boxen5(lines.join("\n"), { padding: 1, borderStyle: "round" })}
|
|
3189
3831
|
`);
|
|
3190
3832
|
}
|
|
3191
3833
|
|
|
@@ -3236,18 +3878,18 @@ function registerSecretsCommand(program2) {
|
|
|
3236
3878
|
}
|
|
3237
3879
|
|
|
3238
3880
|
// src/commands/status.ts
|
|
3239
|
-
import { promises as
|
|
3240
|
-
import { join as
|
|
3241
|
-
import
|
|
3881
|
+
import { promises as fs10 } from "fs";
|
|
3882
|
+
import { join as join24 } from "path";
|
|
3883
|
+
import boxen6 from "boxen";
|
|
3242
3884
|
|
|
3243
3885
|
// src/lib/pack-backup-manager.ts
|
|
3244
|
-
import { promises as
|
|
3245
|
-
import { join as
|
|
3886
|
+
import { promises as fs9 } from "fs";
|
|
3887
|
+
import { join as join23 } from "path";
|
|
3246
3888
|
var BACKUP_DIR_NAME = "_backup";
|
|
3247
3889
|
async function listBackups(projectRoot) {
|
|
3248
|
-
const dir =
|
|
3890
|
+
const dir = join23(projectRoot, ".claude", BACKUP_DIR_NAME);
|
|
3249
3891
|
if (!await pathExists(dir)) return [];
|
|
3250
|
-
const entries = await
|
|
3892
|
+
const entries = await fs9.readdir(dir, { withFileTypes: true });
|
|
3251
3893
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort().reverse();
|
|
3252
3894
|
}
|
|
3253
3895
|
|
|
@@ -3271,7 +3913,7 @@ function registerStatusCommand(program2) {
|
|
|
3271
3913
|
}
|
|
3272
3914
|
async function gatherStatus(cwd) {
|
|
3273
3915
|
const projectName = cwd.split("/").filter(Boolean).pop() ?? "unknown";
|
|
3274
|
-
const claudeRoot =
|
|
3916
|
+
const claudeRoot = join24(cwd, ".claude");
|
|
3275
3917
|
const hasAvatar = await pathExists(claudeRoot);
|
|
3276
3918
|
if (!hasAvatar) {
|
|
3277
3919
|
return {
|
|
@@ -3284,9 +3926,9 @@ async function gatherStatus(cwd) {
|
|
|
3284
3926
|
hasAvatar: false
|
|
3285
3927
|
};
|
|
3286
3928
|
}
|
|
3287
|
-
const packVersion = await isGitRepo(
|
|
3288
|
-
const pendingDir =
|
|
3289
|
-
const pendingCount = await pathExists(pendingDir) ? (await
|
|
3929
|
+
const packVersion = await isGitRepo(join24(claudeRoot, "pack")) ? await readPinnedPackVersion(cwd).catch(() => null) : null;
|
|
3930
|
+
const pendingDir = join24(claudeRoot, "_pending");
|
|
3931
|
+
const pendingCount = await pathExists(pendingDir) ? (await fs10.readdir(pendingDir)).filter((n) => n.endsWith(".diff.md")).length : 0;
|
|
3290
3932
|
const backupCount = (await listBackups(cwd)).length;
|
|
3291
3933
|
const techStackSummary = await readTechStackFirstLine(claudeRoot);
|
|
3292
3934
|
return {
|
|
@@ -3300,7 +3942,7 @@ async function gatherStatus(cwd) {
|
|
|
3300
3942
|
};
|
|
3301
3943
|
}
|
|
3302
3944
|
async function readTechStackFirstLine(claudeRoot) {
|
|
3303
|
-
const techStackPath =
|
|
3945
|
+
const techStackPath = join24(claudeRoot, "project", "tech-stack.md");
|
|
3304
3946
|
if (!await pathExists(techStackPath)) return "(no tech-stack.md)";
|
|
3305
3947
|
const content = await readText(techStackPath);
|
|
3306
3948
|
const firstNonHeaderLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#") && !l.startsWith(">"));
|
|
@@ -3316,7 +3958,7 @@ function renderStatusBox(s) {
|
|
|
3316
3958
|
`${chalk.dim("Backups:")} ${s.backupCount}`,
|
|
3317
3959
|
`${chalk.dim("Tech stack:")} ${s.techStackSummary}`
|
|
3318
3960
|
];
|
|
3319
|
-
process.stdout.write(`${
|
|
3961
|
+
process.stdout.write(`${boxen6(lines.join("\n"), { padding: 1, borderStyle: "round" })}
|
|
3320
3962
|
`);
|
|
3321
3963
|
}
|
|
3322
3964
|
|
|
@@ -3335,33 +3977,33 @@ function registerToolsCommand(program2) {
|
|
|
3335
3977
|
|
|
3336
3978
|
// src/commands/uninstall.ts
|
|
3337
3979
|
import { relative as relative3 } from "path";
|
|
3338
|
-
import { confirm as
|
|
3339
|
-
import
|
|
3980
|
+
import { confirm as confirm6 } from "@inquirer/prompts";
|
|
3981
|
+
import boxen7 from "boxen";
|
|
3340
3982
|
|
|
3341
3983
|
// src/lib/create-uninstall-backup-snapshot.ts
|
|
3342
3984
|
import { cp, mkdir, writeFile } from "fs/promises";
|
|
3343
|
-
import { homedir as
|
|
3344
|
-
import { basename as basename2, join as
|
|
3345
|
-
var UNINSTALL_BACKUPS_DIR =
|
|
3985
|
+
import { homedir as homedir4 } from "os";
|
|
3986
|
+
import { basename as basename2, join as join25 } from "path";
|
|
3987
|
+
var UNINSTALL_BACKUPS_DIR = join25(homedir4(), ".avatar", "uninstall-backups");
|
|
3346
3988
|
async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersion) {
|
|
3347
3989
|
const projectName = basename2(projectRoot);
|
|
3348
3990
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
3349
|
-
const backupDir =
|
|
3991
|
+
const backupDir = join25(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp}`);
|
|
3350
3992
|
await mkdir(backupDir, { recursive: true, mode: 448 });
|
|
3351
3993
|
if (artifacts.claudeDir) {
|
|
3352
|
-
await cp(artifacts.claudeDir,
|
|
3994
|
+
await cp(artifacts.claudeDir, join25(backupDir, ".claude"), { recursive: true });
|
|
3353
3995
|
}
|
|
3354
3996
|
if (artifacts.claudeMd) {
|
|
3355
|
-
await cp(artifacts.claudeMd,
|
|
3997
|
+
await cp(artifacts.claudeMd, join25(backupDir, "CLAUDE.md"));
|
|
3356
3998
|
}
|
|
3357
3999
|
if (artifacts.postMergeHook || artifacts.prePushHook) {
|
|
3358
|
-
const hooksBackupDir =
|
|
4000
|
+
const hooksBackupDir = join25(backupDir, "hooks");
|
|
3359
4001
|
await mkdir(hooksBackupDir, { recursive: true });
|
|
3360
4002
|
if (artifacts.postMergeHook) {
|
|
3361
|
-
await cp(artifacts.postMergeHook,
|
|
4003
|
+
await cp(artifacts.postMergeHook, join25(hooksBackupDir, "post-merge"));
|
|
3362
4004
|
}
|
|
3363
4005
|
if (artifacts.prePushHook) {
|
|
3364
|
-
await cp(artifacts.prePushHook,
|
|
4006
|
+
await cp(artifacts.prePushHook, join25(hooksBackupDir, "pre-push"));
|
|
3365
4007
|
}
|
|
3366
4008
|
}
|
|
3367
4009
|
const manifest = {
|
|
@@ -3376,27 +4018,27 @@ async function createUninstallBackupSnapshot(projectRoot, artifacts, avatarVersi
|
|
|
3376
4018
|
prePushHook: !!artifacts.prePushHook
|
|
3377
4019
|
}
|
|
3378
4020
|
};
|
|
3379
|
-
await writeFile(
|
|
4021
|
+
await writeFile(join25(backupDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
|
|
3380
4022
|
return backupDir;
|
|
3381
4023
|
}
|
|
3382
4024
|
|
|
3383
4025
|
// src/lib/detect-avatar-project-artifacts.ts
|
|
3384
|
-
import { existsSync as
|
|
3385
|
-
import { join as
|
|
4026
|
+
import { existsSync as existsSync9 } from "fs";
|
|
4027
|
+
import { join as join26 } from "path";
|
|
3386
4028
|
function existsOrNull(path) {
|
|
3387
|
-
return
|
|
4029
|
+
return existsSync9(path) ? path : null;
|
|
3388
4030
|
}
|
|
3389
4031
|
function detectAvatarProjectArtifacts(projectRoot) {
|
|
3390
|
-
const claudeDir = existsOrNull(
|
|
3391
|
-
const claudeMd = existsOrNull(
|
|
3392
|
-
const postMergeHook = existsOrNull(
|
|
4032
|
+
const claudeDir = existsOrNull(join26(projectRoot, ".claude"));
|
|
4033
|
+
const claudeMd = existsOrNull(join26(projectRoot, "CLAUDE.md"));
|
|
4034
|
+
const postMergeHook = existsOrNull(join26(projectRoot, ".git", "hooks", "post-merge"));
|
|
3393
4035
|
const prePushHook = existsOrNull(
|
|
3394
|
-
|
|
4036
|
+
join26(projectRoot, ".git", "modules", "src", "hooks", "pre-push")
|
|
3395
4037
|
);
|
|
3396
|
-
const gitignorePath = existsOrNull(
|
|
3397
|
-
const gitmodulesPath = existsOrNull(
|
|
3398
|
-
const notesDir = existsOrNull(
|
|
3399
|
-
const scriptsDir = existsOrNull(
|
|
4038
|
+
const gitignorePath = existsOrNull(join26(projectRoot, ".gitignore"));
|
|
4039
|
+
const gitmodulesPath = existsOrNull(join26(projectRoot, ".gitmodules"));
|
|
4040
|
+
const notesDir = existsOrNull(join26(projectRoot, "notes"));
|
|
4041
|
+
const scriptsDir = existsOrNull(join26(projectRoot, "scripts"));
|
|
3400
4042
|
const hasAnyArtifact = !!(claudeDir || claudeMd || postMergeHook || prePushHook);
|
|
3401
4043
|
return {
|
|
3402
4044
|
hasAnyArtifact,
|
|
@@ -3417,11 +4059,11 @@ async function executeUninstallDeletion(artifacts, flags) {
|
|
|
3417
4059
|
if (artifacts.claudeDir) {
|
|
3418
4060
|
if (flags.keepSubmodule) {
|
|
3419
4061
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
3420
|
-
const { join:
|
|
4062
|
+
const { join: join27 } = await import("path");
|
|
3421
4063
|
const entries = await readdir2(artifacts.claudeDir);
|
|
3422
4064
|
for (const entry of entries) {
|
|
3423
4065
|
if (entry === "pack") continue;
|
|
3424
|
-
await rm(
|
|
4066
|
+
await rm(join27(artifacts.claudeDir, entry), { recursive: true, force: true });
|
|
3425
4067
|
}
|
|
3426
4068
|
} else {
|
|
3427
4069
|
await rm(artifacts.claudeDir, { recursive: true, force: true });
|
|
@@ -3490,7 +4132,7 @@ async function removeSubmoduleEntry(gitmodulesPath, submodulePath) {
|
|
|
3490
4132
|
}
|
|
3491
4133
|
|
|
3492
4134
|
// src/commands/uninstall.ts
|
|
3493
|
-
var CLI_VERSION = "1.
|
|
4135
|
+
var CLI_VERSION = "1.4.0";
|
|
3494
4136
|
function registerUninstallCommand(program2) {
|
|
3495
4137
|
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) => {
|
|
3496
4138
|
try {
|
|
@@ -3514,7 +4156,7 @@ async function runUninstall(opts) {
|
|
|
3514
4156
|
return;
|
|
3515
4157
|
}
|
|
3516
4158
|
if (!opts.yes) {
|
|
3517
|
-
const ok = await
|
|
4159
|
+
const ok = await confirm6({
|
|
3518
4160
|
message: "Ti\u1EBFp t\u1EE5c g\u1EE1 Avatar?",
|
|
3519
4161
|
default: false
|
|
3520
4162
|
});
|
|
@@ -3567,12 +4209,12 @@ function printUninstallSuccessBox(backupPath) {
|
|
|
3567
4209
|
lines.push(` ${chalk.dim("Backup:")} ${backupPath}`);
|
|
3568
4210
|
lines.push(` ${chalk.dim("Restore:")} ${chalk.cyan(`cp -r "${backupPath}"/* .`)}`);
|
|
3569
4211
|
}
|
|
3570
|
-
process.stdout.write(`${
|
|
4212
|
+
process.stdout.write(`${boxen7(lines.join("\n"), { padding: 1, borderStyle: "round" })}
|
|
3571
4213
|
`);
|
|
3572
4214
|
}
|
|
3573
4215
|
|
|
3574
4216
|
// src/index.ts
|
|
3575
|
-
var CLI_VERSION2 = "1.
|
|
4217
|
+
var CLI_VERSION2 = "1.4.0";
|
|
3576
4218
|
var program = new Command();
|
|
3577
4219
|
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(
|
|
3578
4220
|
"beforeAll",
|
|
@@ -3599,6 +4241,7 @@ registerToolsCommand(program);
|
|
|
3599
4241
|
registerSecretsCommand(program);
|
|
3600
4242
|
registerMcpRunCommand(program);
|
|
3601
4243
|
registerAiCommand(program);
|
|
4244
|
+
registerGitnexusCommand(program);
|
|
3602
4245
|
registerUninstallCommand(program);
|
|
3603
4246
|
program.parseAsync(process.argv).catch((err) => {
|
|
3604
4247
|
const msg = err instanceof Error ? err.message : String(err);
|