@nalvietnam/avatar-cli 1.2.9 → 1.2.11
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
|
@@ -1194,9 +1194,13 @@ async function applyFixes(checks) {
|
|
|
1194
1194
|
|
|
1195
1195
|
// src/commands/init.ts
|
|
1196
1196
|
import { basename, join as join16, relative as relative2, resolve } from "path";
|
|
1197
|
-
import { confirm as confirm3, input as input4, select as
|
|
1197
|
+
import { confirm as confirm3, input as input4, select as select8 } from "@inquirer/prompts";
|
|
1198
1198
|
import boxen4 from "boxen";
|
|
1199
1199
|
|
|
1200
|
+
// src/lib/add-team-pack-submodule-with-retry-on-network-fail.ts
|
|
1201
|
+
import { spawnSync as spawnSync7 } from "child_process";
|
|
1202
|
+
import { select as select5 } from "@inquirer/prompts";
|
|
1203
|
+
|
|
1200
1204
|
// src/lib/prompt-recovery-action-on-failure.ts
|
|
1201
1205
|
import { input as input2, select as select3 } from "@inquirer/prompts";
|
|
1202
1206
|
var UserAbortedRecoveryError = class extends Error {
|
|
@@ -1403,6 +1407,51 @@ async function readPinnedPackVersion(projectRoot) {
|
|
|
1403
1407
|
}
|
|
1404
1408
|
|
|
1405
1409
|
// src/lib/add-team-pack-submodule-with-retry-on-network-fail.ts
|
|
1410
|
+
function isSshPermissionError(message) {
|
|
1411
|
+
const text = message.toLowerCase();
|
|
1412
|
+
return text.includes("permission denied (publickey)") || text.includes("publickey)") || text.includes("ssh: could not resolve") || text.includes("host key verification failed");
|
|
1413
|
+
}
|
|
1414
|
+
function triggerGhAuthLoginInteractive2() {
|
|
1415
|
+
log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
|
|
1416
|
+
const r = spawnSync7("gh", ["auth", "login", "--web"], { stdio: "inherit" });
|
|
1417
|
+
if (r.status !== 0) {
|
|
1418
|
+
log.warn(`gh auth login exit ${r.status}. C\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
function openGithubSshKeysPage() {
|
|
1422
|
+
log.info("M\u1EDF trang GitHub Settings \u2192 SSH Keys...");
|
|
1423
|
+
const r = spawnSync7("open", ["https://github.com/settings/keys"], { stdio: "ignore" });
|
|
1424
|
+
if (r.status !== 0) {
|
|
1425
|
+
log.info("URL: https://github.com/settings/keys");
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
async function handleSshPermissionError() {
|
|
1429
|
+
return await select5({
|
|
1430
|
+
message: "SSH permission denied. C\xE1ch x\u1EED l\xFD?",
|
|
1431
|
+
choices: [
|
|
1432
|
+
{
|
|
1433
|
+
name: "Switch GitHub account (gh auth login \u2014 m\u1EDF browser)",
|
|
1434
|
+
value: "switch"
|
|
1435
|
+
},
|
|
1436
|
+
{
|
|
1437
|
+
name: "D\xF9ng HTTPS thay SSH (override URL b\u1EB1ng env AVATAR_TEAM_PACK_REPO_URL)",
|
|
1438
|
+
value: "https"
|
|
1439
|
+
},
|
|
1440
|
+
{
|
|
1441
|
+
name: "T\xF4i v\u1EEBa add SSH key l\xEAn GitHub \u2014 retry",
|
|
1442
|
+
value: "retry"
|
|
1443
|
+
},
|
|
1444
|
+
{
|
|
1445
|
+
name: "B\u1ECF qua team-ai-pack (d\xF9ng avatar sync sau)",
|
|
1446
|
+
value: "skip"
|
|
1447
|
+
},
|
|
1448
|
+
{
|
|
1449
|
+
name: "T\u1EA1m ng\u01B0ng init \u2014 fix SSH key tay r\u1ED3i ch\u1EA1y l\u1EA1i",
|
|
1450
|
+
value: "abort"
|
|
1451
|
+
}
|
|
1452
|
+
]
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1406
1455
|
async function addTeamPackSubmoduleWithRetryOnNetworkFail(projectRoot, tag, ssoEmail) {
|
|
1407
1456
|
while (true) {
|
|
1408
1457
|
try {
|
|
@@ -1410,9 +1459,39 @@ async function addTeamPackSubmoduleWithRetryOnNetworkFail(projectRoot, tag, ssoE
|
|
|
1410
1459
|
return { pinnedTag: result.pinnedTag, skipped: false };
|
|
1411
1460
|
} catch (err) {
|
|
1412
1461
|
if (err instanceof TeamPackAccessAbortedError) throw err;
|
|
1462
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1463
|
+
if (isSshPermissionError(message)) {
|
|
1464
|
+
log.warn("Pull team-ai-pack th\u1EA5t b\u1EA1i: SSH permission denied (publickey).");
|
|
1465
|
+
log.dim(
|
|
1466
|
+
" \u2192 M\xE1y n\xE0y ch\u01B0a c\xF3 SSH key \u0111\u01B0\u1EE3c register tr\xEAn GitHub, ho\u1EB7c key thu\u1ED9c account kh\xE1c."
|
|
1467
|
+
);
|
|
1468
|
+
const action2 = await handleSshPermissionError();
|
|
1469
|
+
if (action2 === "abort") {
|
|
1470
|
+
throw new UserAbortedRecoveryError(
|
|
1471
|
+
"User abort t\u1EA1i b\u01B0\u1EDBc pull team-ai-pack. Fix SSH key (https://github.com/settings/keys) r\u1ED3i ch\u1EA1y l\u1EA1i 'avatar init'."
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
if (action2 === "skip") {
|
|
1475
|
+
log.warn(
|
|
1476
|
+
"Skip team-ai-pack. Workspace d\xF9ng \u0111\u01B0\u1EE3c nh\u01B0ng kh\xF4ng c\xF3 shared knowledge. Pull sau qua `avatar sync`."
|
|
1477
|
+
);
|
|
1478
|
+
return { pinnedTag: null, skipped: true };
|
|
1479
|
+
}
|
|
1480
|
+
if (action2 === "switch") {
|
|
1481
|
+
triggerGhAuthLoginInteractive2();
|
|
1482
|
+
continue;
|
|
1483
|
+
}
|
|
1484
|
+
if (action2 === "https") {
|
|
1485
|
+
process.env.AVATAR_TEAM_PACK_REPO_URL = "https://github.com/nalvn/team-ai-pack.git";
|
|
1486
|
+
log.info("Override URL sang HTTPS. L\u01B0u \xFD: HTTPS c\xF3 th\u1EC3 fail 403 n\u1EBFu read-only role.");
|
|
1487
|
+
openGithubSshKeysPage();
|
|
1488
|
+
continue;
|
|
1489
|
+
}
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1413
1492
|
const action = await promptRetryOrSkip({
|
|
1414
1493
|
taskName: "Pull team-ai-pack submodule",
|
|
1415
|
-
reason:
|
|
1494
|
+
reason: message,
|
|
1416
1495
|
allowSkip: true,
|
|
1417
1496
|
hint: "Network glitch? Retry th\u01B0\u1EDDng work. N\u1EBFu skip, d\xF9ng `avatar sync` sau \u0111\u1EC3 pull pack."
|
|
1418
1497
|
});
|
|
@@ -1491,7 +1570,7 @@ ${renderAvatarBanner(opts)}
|
|
|
1491
1570
|
}
|
|
1492
1571
|
|
|
1493
1572
|
// src/lib/execute-gh-repo-create.ts
|
|
1494
|
-
import { spawnSync as
|
|
1573
|
+
import { spawnSync as spawnSync8 } from "child_process";
|
|
1495
1574
|
var RepoAlreadyExistsError = class extends Error {
|
|
1496
1575
|
constructor(fullName) {
|
|
1497
1576
|
super(`Repo "${fullName}" \u0111\xE3 t\u1ED3n t\u1EA1i tr\xEAn GitHub. \u0110\u1ED5i t\xEAn ho\u1EB7c x\xF3a repo c\u0169.`);
|
|
@@ -1511,7 +1590,7 @@ function executeGhRepoCreate(input5) {
|
|
|
1511
1590
|
"origin",
|
|
1512
1591
|
"--push"
|
|
1513
1592
|
];
|
|
1514
|
-
const r =
|
|
1593
|
+
const r = spawnSync8("gh", args, { stdio: "inherit" });
|
|
1515
1594
|
if (r.status !== 0) {
|
|
1516
1595
|
if (r.status === 1) {
|
|
1517
1596
|
throw new RepoAlreadyExistsError(fullName);
|
|
@@ -1525,9 +1604,9 @@ function executeGhRepoCreate(input5) {
|
|
|
1525
1604
|
}
|
|
1526
1605
|
|
|
1527
1606
|
// src/lib/resolve-github-username-default.ts
|
|
1528
|
-
import { spawnSync as
|
|
1607
|
+
import { spawnSync as spawnSync9 } from "child_process";
|
|
1529
1608
|
function resolveGithubUsernameDefault() {
|
|
1530
|
-
const r =
|
|
1609
|
+
const r = spawnSync9("gh", ["api", "user", "--jq", ".login"], {
|
|
1531
1610
|
encoding: "utf8",
|
|
1532
1611
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1533
1612
|
});
|
|
@@ -1575,12 +1654,12 @@ function createGithubRemoteFromFolder(input5) {
|
|
|
1575
1654
|
}
|
|
1576
1655
|
|
|
1577
1656
|
// src/lib/create-workspace-remote-via-gh.ts
|
|
1578
|
-
import { spawnSync as
|
|
1657
|
+
import { spawnSync as spawnSync17 } from "child_process";
|
|
1579
1658
|
|
|
1580
1659
|
// src/lib/check-gh-cli-auth-status.ts
|
|
1581
|
-
import { spawnSync as
|
|
1660
|
+
import { spawnSync as spawnSync10 } from "child_process";
|
|
1582
1661
|
function checkGhCliAuthStatus() {
|
|
1583
|
-
const r =
|
|
1662
|
+
const r = spawnSync10("gh", ["auth", "status"], { stdio: "ignore" });
|
|
1584
1663
|
if (r.error && r.error.code === "ENOENT") {
|
|
1585
1664
|
return "not-installed";
|
|
1586
1665
|
}
|
|
@@ -1588,12 +1667,12 @@ function checkGhCliAuthStatus() {
|
|
|
1588
1667
|
}
|
|
1589
1668
|
|
|
1590
1669
|
// src/lib/detect-package-manager.ts
|
|
1591
|
-
import { spawnSync as
|
|
1670
|
+
import { spawnSync as spawnSync11 } from "child_process";
|
|
1592
1671
|
function hasBinary(name) {
|
|
1593
1672
|
const platform2 = detectHostPlatform();
|
|
1594
1673
|
const probe = platform2 === "win32" ? "where" : "command";
|
|
1595
1674
|
const args = platform2 === "win32" ? [name] : ["-v", name];
|
|
1596
|
-
const r =
|
|
1675
|
+
const r = spawnSync11(probe, args, {
|
|
1597
1676
|
shell: platform2 !== "win32",
|
|
1598
1677
|
stdio: "ignore"
|
|
1599
1678
|
});
|
|
@@ -1609,11 +1688,11 @@ function detectPackageManager() {
|
|
|
1609
1688
|
}
|
|
1610
1689
|
|
|
1611
1690
|
// src/lib/handle-remote-access-failure-with-account-switch.ts
|
|
1612
|
-
import { spawnSync as
|
|
1613
|
-
import { input as input3, select as
|
|
1691
|
+
import { spawnSync as spawnSync13 } from "child_process";
|
|
1692
|
+
import { input as input3, select as select6 } from "@inquirer/prompts";
|
|
1614
1693
|
|
|
1615
1694
|
// src/lib/verify-git-remote-accessible.ts
|
|
1616
|
-
import { spawnSync as
|
|
1695
|
+
import { spawnSync as spawnSync12 } from "child_process";
|
|
1617
1696
|
var TIMEOUT_MS = 5e3;
|
|
1618
1697
|
function classifyRemoteError(stderr) {
|
|
1619
1698
|
const text = stderr.toLowerCase();
|
|
@@ -1629,7 +1708,7 @@ function classifyRemoteError(stderr) {
|
|
|
1629
1708
|
return "unknown";
|
|
1630
1709
|
}
|
|
1631
1710
|
function tryVerifyGitRemoteAccessible(url) {
|
|
1632
|
-
const r =
|
|
1711
|
+
const r = spawnSync12("git", ["ls-remote", "--exit-code", url, "HEAD"], {
|
|
1633
1712
|
encoding: "utf8",
|
|
1634
1713
|
timeout: TIMEOUT_MS,
|
|
1635
1714
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -1651,16 +1730,16 @@ var RemoteAccessAbortedError = class extends Error {
|
|
|
1651
1730
|
}
|
|
1652
1731
|
};
|
|
1653
1732
|
function getCurrentGhUser2() {
|
|
1654
|
-
const r =
|
|
1733
|
+
const r = spawnSync13("gh", ["api", "user", "--jq", ".login"], {
|
|
1655
1734
|
encoding: "utf8",
|
|
1656
1735
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1657
1736
|
});
|
|
1658
1737
|
if (r.status !== 0) return null;
|
|
1659
1738
|
return r.stdout.trim() || null;
|
|
1660
1739
|
}
|
|
1661
|
-
function
|
|
1740
|
+
function triggerGhAuthLoginInteractive3() {
|
|
1662
1741
|
log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
|
|
1663
|
-
const r =
|
|
1742
|
+
const r = spawnSync13("gh", ["auth", "login", "--web"], { stdio: "inherit" });
|
|
1664
1743
|
if (r.status !== 0) {
|
|
1665
1744
|
log.warn(`gh auth login exit ${r.status}. B\u1EA1n c\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
|
|
1666
1745
|
}
|
|
@@ -1694,7 +1773,7 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
|
|
|
1694
1773
|
log.dim(` L\xFD do: ${reason}${detail ? ` \u2014 ${detail.slice(0, 150)}` : ""}`);
|
|
1695
1774
|
log.info(getReasonHint(reason, currentUrl, ghUser));
|
|
1696
1775
|
if (ghUser) log.dim(` gh CLI hi\u1EC7n \u0111ang login: ${ghUser}`);
|
|
1697
|
-
const action = await
|
|
1776
|
+
const action = await select6({
|
|
1698
1777
|
message: "C\xE1ch x\u1EED l\xFD?",
|
|
1699
1778
|
choices: [
|
|
1700
1779
|
{
|
|
@@ -1729,7 +1808,7 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
|
|
|
1729
1808
|
currentUrl = newUrl.trim();
|
|
1730
1809
|
}
|
|
1731
1810
|
if (action === "switch") {
|
|
1732
|
-
|
|
1811
|
+
triggerGhAuthLoginInteractive3();
|
|
1733
1812
|
}
|
|
1734
1813
|
log.info(`Verify remote l\u1EA1i: ${currentUrl}...`);
|
|
1735
1814
|
const result = tryVerifyGitRemoteAccessible(currentUrl);
|
|
@@ -1743,7 +1822,7 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
|
|
|
1743
1822
|
}
|
|
1744
1823
|
|
|
1745
1824
|
// src/lib/install-gh-cli-via-package-manager.ts
|
|
1746
|
-
import { spawnSync as
|
|
1825
|
+
import { spawnSync as spawnSync14 } from "child_process";
|
|
1747
1826
|
var INSTALL_COMMANDS = {
|
|
1748
1827
|
brew: { cmd: "brew", args: ["install", "gh"] },
|
|
1749
1828
|
apt: { cmd: "sudo", args: ["apt-get", "install", "-y", "gh"] },
|
|
@@ -1754,7 +1833,7 @@ var INSTALL_COMMANDS = {
|
|
|
1754
1833
|
function installGhCliViaPackageManager(pm) {
|
|
1755
1834
|
const spec = INSTALL_COMMANDS[pm];
|
|
1756
1835
|
log.info(`\u0110ang c\xE0i gh CLI qua ${pm}...`);
|
|
1757
|
-
const r =
|
|
1836
|
+
const r = spawnSync14(spec.cmd, spec.args, { stdio: "inherit" });
|
|
1758
1837
|
if (r.status !== 0) {
|
|
1759
1838
|
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.`);
|
|
1760
1839
|
}
|
|
@@ -1762,9 +1841,9 @@ function installGhCliViaPackageManager(pm) {
|
|
|
1762
1841
|
}
|
|
1763
1842
|
|
|
1764
1843
|
// src/lib/setup-git-credential-via-gh.ts
|
|
1765
|
-
import { spawnSync as
|
|
1844
|
+
import { spawnSync as spawnSync15 } from "child_process";
|
|
1766
1845
|
function setupGitCredentialViaGh() {
|
|
1767
|
-
const r =
|
|
1846
|
+
const r = spawnSync15("gh", ["auth", "setup-git"], { stdio: "ignore" });
|
|
1768
1847
|
if (r.status !== 0) {
|
|
1769
1848
|
log.warn("gh auth setup-git fail (non-fatal). N\u1EBFu git clone l\u1ED7i 128 \u2192 ch\u1EA1y th\u1EE7 c\xF4ng.");
|
|
1770
1849
|
return;
|
|
@@ -1773,10 +1852,10 @@ function setupGitCredentialViaGh() {
|
|
|
1773
1852
|
}
|
|
1774
1853
|
|
|
1775
1854
|
// src/lib/trigger-gh-cli-auth-login.ts
|
|
1776
|
-
import { spawnSync as
|
|
1855
|
+
import { spawnSync as spawnSync16 } from "child_process";
|
|
1777
1856
|
function triggerGhCliAuthLogin() {
|
|
1778
1857
|
log.info("Kh\u1EDFi \u0111\u1ED9ng \u0111\u0103ng nh\u1EADp GitHub qua gh CLI (browser s\u1EBD m\u1EDF)...");
|
|
1779
|
-
const r =
|
|
1858
|
+
const r = spawnSync16(
|
|
1780
1859
|
"gh",
|
|
1781
1860
|
["auth", "login", "--hostname", "github.com", "--web", "--git-protocol", "ssh"],
|
|
1782
1861
|
{ stdio: "inherit" }
|
|
@@ -1865,15 +1944,49 @@ async function ensureGitHubReady(remoteUrl) {
|
|
|
1865
1944
|
}
|
|
1866
1945
|
|
|
1867
1946
|
// src/lib/create-workspace-remote-via-gh.ts
|
|
1947
|
+
var CreateWorkspaceRemoteError = class extends Error {
|
|
1948
|
+
reason;
|
|
1949
|
+
fullName;
|
|
1950
|
+
stderr;
|
|
1951
|
+
constructor(reason, fullName, message, stderr) {
|
|
1952
|
+
super(message);
|
|
1953
|
+
this.name = "CreateWorkspaceRemoteError";
|
|
1954
|
+
this.reason = reason;
|
|
1955
|
+
this.fullName = fullName;
|
|
1956
|
+
this.stderr = stderr;
|
|
1957
|
+
}
|
|
1958
|
+
};
|
|
1959
|
+
function classifyGhCreateError(stderr) {
|
|
1960
|
+
const text = stderr.toLowerCase();
|
|
1961
|
+
if (text.includes("name already exists") || text.includes("already exists on this account") || text.includes("repository already exists")) {
|
|
1962
|
+
return "repo-exists";
|
|
1963
|
+
}
|
|
1964
|
+
if (text.includes("403") || text.includes("permission") || text.includes("not authorized") || text.includes("forbidden")) {
|
|
1965
|
+
return "no-permission";
|
|
1966
|
+
}
|
|
1967
|
+
if (text.includes("invalid") && text.includes("name")) {
|
|
1968
|
+
return "name-invalid";
|
|
1969
|
+
}
|
|
1970
|
+
if (text.includes("could not resolve") || text.includes("network") || text.includes("connection refused")) {
|
|
1971
|
+
return "network";
|
|
1972
|
+
}
|
|
1973
|
+
return "unknown";
|
|
1974
|
+
}
|
|
1975
|
+
function repoExistsOnGitHub(fullName) {
|
|
1976
|
+
const r = spawnSync17("gh", ["repo", "view", fullName, "--json", "name"], {
|
|
1977
|
+
stdio: "ignore"
|
|
1978
|
+
});
|
|
1979
|
+
return r.status === 0;
|
|
1980
|
+
}
|
|
1868
1981
|
function canCreateInNamespace(org, ghUser) {
|
|
1869
1982
|
if (org.toLowerCase() === ghUser.toLowerCase()) return { ok: true };
|
|
1870
|
-
const r =
|
|
1983
|
+
const r = spawnSync17("gh", ["api", `orgs/${org}/members/${ghUser}`, "--silent"], {
|
|
1871
1984
|
stdio: "ignore"
|
|
1872
1985
|
});
|
|
1873
1986
|
if (r.status === 0) return { ok: true };
|
|
1874
|
-
const orgCheck =
|
|
1987
|
+
const orgCheck = spawnSync17("gh", ["api", `orgs/${org}`, "--silent"], { stdio: "ignore" });
|
|
1875
1988
|
if (orgCheck.status !== 0) {
|
|
1876
|
-
const userCheck =
|
|
1989
|
+
const userCheck = spawnSync17("gh", ["api", `users/${org}`, "--silent"], { stdio: "ignore" });
|
|
1877
1990
|
if (userCheck.status === 0) {
|
|
1878
1991
|
return {
|
|
1879
1992
|
ok: false,
|
|
@@ -1901,8 +2014,15 @@ async function createWorkspaceRemoteViaGh(input5) {
|
|
|
1901
2014
|
throw new Error(`Kh\xF4ng th\u1EC3 t\u1EA1o repo d\u01B0\u1EDBi '${org}/': ${namespaceCheck.reason}`);
|
|
1902
2015
|
}
|
|
1903
2016
|
const fullName = `${org}/${input5.workspaceName}`;
|
|
2017
|
+
if (repoExistsOnGitHub(fullName)) {
|
|
2018
|
+
throw new CreateWorkspaceRemoteError(
|
|
2019
|
+
"repo-exists",
|
|
2020
|
+
fullName,
|
|
2021
|
+
`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
|
+
);
|
|
2023
|
+
}
|
|
1904
2024
|
log.info(`T\u1EA1o GitHub repo cho workspace: ${fullName} (${input5.visibility})...`);
|
|
1905
|
-
const r =
|
|
2025
|
+
const r = spawnSync17(
|
|
1906
2026
|
"gh",
|
|
1907
2027
|
[
|
|
1908
2028
|
"repo",
|
|
@@ -1915,14 +2035,24 @@ async function createWorkspaceRemoteViaGh(input5) {
|
|
|
1915
2035
|
"origin",
|
|
1916
2036
|
"--push"
|
|
1917
2037
|
],
|
|
1918
|
-
{ stdio: ["
|
|
2038
|
+
{ stdio: ["ignore", "pipe", "pipe"], encoding: "utf8" }
|
|
1919
2039
|
);
|
|
1920
2040
|
if (r.status !== 0) {
|
|
1921
2041
|
const stderr = (r.stderr || "").trim();
|
|
1922
|
-
|
|
2042
|
+
const stdout = (r.stdout || "").trim();
|
|
2043
|
+
const combined = [stderr, stdout].filter(Boolean).join("\n");
|
|
2044
|
+
const reason = classifyGhCreateError(combined);
|
|
2045
|
+
if (combined) {
|
|
2046
|
+
process.stderr.write(`
|
|
2047
|
+
${combined}
|
|
2048
|
+
|
|
1923
2049
|
`);
|
|
1924
|
-
|
|
1925
|
-
|
|
2050
|
+
}
|
|
2051
|
+
throw new CreateWorkspaceRemoteError(
|
|
2052
|
+
reason,
|
|
2053
|
+
fullName,
|
|
2054
|
+
`T\u1EA1o workspace remote th\u1EA5t b\u1EA1i (exit ${r.status}): ${reason}.`,
|
|
2055
|
+
combined
|
|
1926
2056
|
);
|
|
1927
2057
|
}
|
|
1928
2058
|
const sshUrl = `git@github.com:${fullName}.git`;
|
|
@@ -1930,10 +2060,29 @@ async function createWorkspaceRemoteViaGh(input5) {
|
|
|
1930
2060
|
log.success(`Workspace remote: ${sshUrl}`);
|
|
1931
2061
|
return { sshUrl, httpsUrl };
|
|
1932
2062
|
}
|
|
2063
|
+
function linkExistingRemoteToWorkspace(args) {
|
|
2064
|
+
const sshUrl = `git@github.com:${args.fullName}.git`;
|
|
2065
|
+
const httpsUrl = `https://github.com/${args.fullName}.git`;
|
|
2066
|
+
const addResult = spawnSync17(
|
|
2067
|
+
"git",
|
|
2068
|
+
["-C", args.workspacePath, "remote", "add", "origin", sshUrl],
|
|
2069
|
+
{
|
|
2070
|
+
encoding: "utf8",
|
|
2071
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2072
|
+
}
|
|
2073
|
+
);
|
|
2074
|
+
if (addResult.status !== 0) {
|
|
2075
|
+
spawnSync17("git", ["-C", args.workspacePath, "remote", "set-url", "origin", sshUrl], {
|
|
2076
|
+
stdio: "ignore"
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2079
|
+
log.success(`Linked existing remote: ${sshUrl}`);
|
|
2080
|
+
return { sshUrl, httpsUrl };
|
|
2081
|
+
}
|
|
1933
2082
|
|
|
1934
2083
|
// src/lib/safe-bootstrap-for-dirty-folder.ts
|
|
1935
2084
|
import { readdirSync } from "fs";
|
|
1936
|
-
import { select as
|
|
2085
|
+
import { select as select7 } from "@inquirer/prompts";
|
|
1937
2086
|
import { simpleGit as simpleGit3 } from "simple-git";
|
|
1938
2087
|
|
|
1939
2088
|
// src/lib/check-folder-has-git.ts
|
|
@@ -2070,7 +2219,7 @@ async function promptBootstrapStrategy(state, opts) {
|
|
|
2070
2219
|
if (opts.presetStrategy) return opts.presetStrategy;
|
|
2071
2220
|
if (opts.autoYes) return "stash";
|
|
2072
2221
|
if (state === "empty" || state === "clean") return "commit-all";
|
|
2073
|
-
return await
|
|
2222
|
+
return await select7({
|
|
2074
2223
|
message: state === "dirty" ? "Folder c\xF3 changes ch\u01B0a commit. C\xE1ch x\u1EED l\xFD:" : "Folder c\xF3 file ch\u01B0a version. C\xE1ch x\u1EED l\xFD:",
|
|
2075
2224
|
choices: [
|
|
2076
2225
|
{
|
|
@@ -2519,7 +2668,7 @@ async function runInit(opts) {
|
|
|
2519
2668
|
}
|
|
2520
2669
|
}
|
|
2521
2670
|
async function promptProjectStatus() {
|
|
2522
|
-
return await
|
|
2671
|
+
return await select8({
|
|
2523
2672
|
message: "T\xECnh tr\u1EA1ng d\u1EF1 \xE1n c\u1EE7a b\u1EA1n?",
|
|
2524
2673
|
choices: [
|
|
2525
2674
|
{ name: "1. \u0110\xE3 c\xF3 repo git remote (URL c\xF3 s\u1EB5n)", value: "existing-remote" },
|
|
@@ -2600,7 +2749,7 @@ async function runInitFromScratch(opts, ownerEmail) {
|
|
|
2600
2749
|
message: "T\xEAn d\u1EF1 \xE1n:",
|
|
2601
2750
|
validate: (v) => v.length > 0 ? true : "T\xEAn b\u1EAFt bu\u1ED9c"
|
|
2602
2751
|
});
|
|
2603
|
-
const visibility = opts.repoVisibility ?? await
|
|
2752
|
+
const visibility = opts.repoVisibility ?? await select8({
|
|
2604
2753
|
message: "Visibility?",
|
|
2605
2754
|
choices: [
|
|
2606
2755
|
{ name: "private (m\u1EB7c \u0111\u1ECBnh)", value: "private" },
|
|
@@ -2674,7 +2823,7 @@ async function getOrCreateOriginRemote(folderPath, opts) {
|
|
|
2674
2823
|
return void 0;
|
|
2675
2824
|
}
|
|
2676
2825
|
await ensureGitHubReady();
|
|
2677
|
-
const visibility = opts.repoVisibility ?? await
|
|
2826
|
+
const visibility = opts.repoVisibility ?? await select8({
|
|
2678
2827
|
message: "Visibility?",
|
|
2679
2828
|
choices: [
|
|
2680
2829
|
{ name: "private (m\u1EB7c \u0111\u1ECBnh)", value: "private" },
|
|
@@ -2776,7 +2925,7 @@ async function maybeCreateWorkspaceRemote(args) {
|
|
|
2776
2925
|
});
|
|
2777
2926
|
}
|
|
2778
2927
|
if (!shouldCreate) return;
|
|
2779
|
-
const visibility = args.repoVisibility ?? (args.autoYes ? "private" : await
|
|
2928
|
+
const visibility = args.repoVisibility ?? (args.autoYes ? "private" : await select8({
|
|
2780
2929
|
message: "Workspace visibility?",
|
|
2781
2930
|
choices: [
|
|
2782
2931
|
{ name: "private (m\u1EB7c \u0111\u1ECBnh, an to\xE0n)", value: "private" },
|
|
@@ -2793,6 +2942,44 @@ async function maybeCreateWorkspaceRemote(args) {
|
|
|
2793
2942
|
});
|
|
2794
2943
|
return;
|
|
2795
2944
|
} catch (err) {
|
|
2945
|
+
if (err instanceof CreateWorkspaceRemoteError && err.reason === "repo-exists") {
|
|
2946
|
+
const fullName = err.fullName;
|
|
2947
|
+
const reuseAction = await select8({
|
|
2948
|
+
message: `Repo '${fullName}' \u0111\xE3 t\u1ED3n t\u1EA1i tr\xEAn GitHub. C\xE1ch x\u1EED l\xFD?`,
|
|
2949
|
+
choices: [
|
|
2950
|
+
{
|
|
2951
|
+
name: "D\xF9ng remote \u0111\xE3 c\xF3 (link workspace local v\xE0o repo n\xE0y)",
|
|
2952
|
+
value: "reuse"
|
|
2953
|
+
},
|
|
2954
|
+
{
|
|
2955
|
+
name: "Nh\u1EADp t\xEAn workspace kh\xE1c (t\u1EA1o repo m\u1EDBi)",
|
|
2956
|
+
value: "rename"
|
|
2957
|
+
},
|
|
2958
|
+
{ name: "B\u1ECF qua (workspace local-only)", value: "skip" },
|
|
2959
|
+
{ name: "T\u1EA1m ng\u01B0ng init", value: "abort" }
|
|
2960
|
+
]
|
|
2961
|
+
});
|
|
2962
|
+
if (reuseAction === "abort") {
|
|
2963
|
+
throw new UserAbortedRecoveryError("User abort t\u1EA1i b\u01B0\u1EDBc t\u1EA1o workspace remote.");
|
|
2964
|
+
}
|
|
2965
|
+
if (reuseAction === "skip") {
|
|
2966
|
+
log.warn("Workspace v\u1EABn s\u1EB5n s\xE0ng local-only. Setup remote sau khi c\u1EA7n.");
|
|
2967
|
+
return;
|
|
2968
|
+
}
|
|
2969
|
+
if (reuseAction === "reuse") {
|
|
2970
|
+
linkExistingRemoteToWorkspace({
|
|
2971
|
+
workspacePath: args.workspacePath,
|
|
2972
|
+
fullName
|
|
2973
|
+
});
|
|
2974
|
+
return;
|
|
2975
|
+
}
|
|
2976
|
+
const newName = await input4({
|
|
2977
|
+
message: "T\xEAn workspace m\u1EDBi (s\u1EBD t\u1EA1o repo new):",
|
|
2978
|
+
validate: (v) => v.trim().length > 0 ? true : "T\xEAn kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng"
|
|
2979
|
+
});
|
|
2980
|
+
args.workspaceName = newName.trim();
|
|
2981
|
+
continue;
|
|
2982
|
+
}
|
|
2796
2983
|
const action = await promptRetryOrSkip({
|
|
2797
2984
|
taskName: "T\u1EA1o workspace remote tr\xEAn GitHub",
|
|
2798
2985
|
reason: err instanceof Error ? err.message : String(err),
|
|
@@ -2826,7 +3013,7 @@ async function resolveWorkspacePath(parent, desiredName, force) {
|
|
|
2826
3013
|
}
|
|
2827
3014
|
choices.push({ name: "Nh\u1EADp t\xEAn workspace kh\xE1c (manual)", value: "manual" });
|
|
2828
3015
|
choices.push({ name: "T\u1EA1m ng\u01B0ng init", value: "abort" });
|
|
2829
|
-
const action = await
|
|
3016
|
+
const action = await select8({
|
|
2830
3017
|
message: "C\xE1ch x\u1EED l\xFD workspace path conflict?",
|
|
2831
3018
|
choices
|
|
2832
3019
|
});
|
|
@@ -3173,7 +3360,7 @@ async function removeSubmoduleEntry(gitmodulesPath, submodulePath) {
|
|
|
3173
3360
|
}
|
|
3174
3361
|
|
|
3175
3362
|
// src/commands/uninstall.ts
|
|
3176
|
-
var CLI_VERSION = "1.2.
|
|
3363
|
+
var CLI_VERSION = "1.2.11";
|
|
3177
3364
|
function registerUninstallCommand(program2) {
|
|
3178
3365
|
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) => {
|
|
3179
3366
|
try {
|
|
@@ -3255,7 +3442,7 @@ function printUninstallSuccessBox(backupPath) {
|
|
|
3255
3442
|
}
|
|
3256
3443
|
|
|
3257
3444
|
// src/index.ts
|
|
3258
|
-
var CLI_VERSION2 = "1.2.
|
|
3445
|
+
var CLI_VERSION2 = "1.2.11";
|
|
3259
3446
|
var program = new Command();
|
|
3260
3447
|
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(
|
|
3261
3448
|
"beforeAll",
|