@nalvietnam/avatar-cli 1.2.3 → 1.2.4

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,7 +1194,7 @@ 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 input2, select as select5 } from "@inquirer/prompts";
1197
+ import { confirm as confirm3, input as input2, select as select6 } from "@inquirer/prompts";
1198
1198
  import boxen3 from "boxen";
1199
1199
 
1200
1200
  // src/lib/avatar-ascii-banner.ts
@@ -1343,7 +1343,7 @@ function createGithubRemoteFromFolder(input3) {
1343
1343
  }
1344
1344
 
1345
1345
  // src/lib/create-workspace-remote-via-gh.ts
1346
- import { spawnSync as spawnSync14 } from "child_process";
1346
+ import { spawnSync as spawnSync15 } from "child_process";
1347
1347
 
1348
1348
  // src/lib/check-gh-cli-auth-status.ts
1349
1349
  import { spawnSync as spawnSync8 } from "child_process";
@@ -1376,8 +1376,124 @@ function detectPackageManager() {
1376
1376
  return null;
1377
1377
  }
1378
1378
 
1379
- // src/lib/install-gh-cli-via-package-manager.ts
1379
+ // src/lib/handle-remote-access-failure-with-account-switch.ts
1380
+ import { spawnSync as spawnSync11 } from "child_process";
1381
+ import { select as select3 } from "@inquirer/prompts";
1382
+
1383
+ // src/lib/verify-git-remote-accessible.ts
1380
1384
  import { spawnSync as spawnSync10 } from "child_process";
1385
+ var TIMEOUT_MS = 5e3;
1386
+ function classifyRemoteError(stderr) {
1387
+ const text = stderr.toLowerCase();
1388
+ if (text.includes("authentication") || text.includes("could not read username") || text.includes("permission denied") || text.includes("403") || text.includes("access denied") || text.includes("repository not found")) {
1389
+ return "no-access";
1390
+ }
1391
+ if (text.includes("404") || text.includes("does not exist")) {
1392
+ return "not-found";
1393
+ }
1394
+ if (text.includes("could not resolve host") || text.includes("network") || text.includes("connection refused") || text.includes("connection timed out")) {
1395
+ return "network";
1396
+ }
1397
+ return "unknown";
1398
+ }
1399
+ function tryVerifyGitRemoteAccessible(url) {
1400
+ const r = spawnSync10("git", ["ls-remote", "--exit-code", url, "HEAD"], {
1401
+ encoding: "utf8",
1402
+ timeout: TIMEOUT_MS,
1403
+ stdio: ["ignore", "pipe", "pipe"]
1404
+ });
1405
+ if (r.status === 0) return { ok: true };
1406
+ if (r.signal === "SIGTERM") {
1407
+ return { ok: false, reason: "timeout", detail: "git ls-remote > 5s" };
1408
+ }
1409
+ const stderr = (r.stderr || "").trim();
1410
+ const reason = classifyRemoteError(stderr);
1411
+ return { ok: false, reason, detail: stderr.slice(0, 300) };
1412
+ }
1413
+
1414
+ // src/lib/handle-remote-access-failure-with-account-switch.ts
1415
+ var RemoteAccessAbortedError = class extends Error {
1416
+ constructor(message) {
1417
+ super(message);
1418
+ this.name = "RemoteAccessAbortedError";
1419
+ }
1420
+ };
1421
+ function getCurrentGhUser() {
1422
+ const r = spawnSync11("gh", ["api", "user", "--jq", ".login"], {
1423
+ encoding: "utf8",
1424
+ stdio: ["ignore", "pipe", "pipe"]
1425
+ });
1426
+ if (r.status !== 0) return null;
1427
+ return r.stdout.trim() || null;
1428
+ }
1429
+ function triggerGhAuthLoginInteractive() {
1430
+ log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
1431
+ const r = spawnSync11("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1432
+ if (r.status !== 0) {
1433
+ log.warn(`gh auth login exit ${r.status}. B\u1EA1n c\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
1434
+ }
1435
+ }
1436
+ function getReasonHint(reason, url, ghUser) {
1437
+ switch (reason) {
1438
+ case "no-access":
1439
+ return ghUser ? `Repo c\xF3 th\u1EC3 private v\xE0 account '${ghUser}' kh\xF4ng c\xF3 quy\u1EC1n access. Switch sang account c\xF3 quy\u1EC1n, ho\u1EB7c xin invite t\u1EEB owner repo.` : "Repo c\xF3 th\u1EC3 private v\xE0 gh CLI ch\u01B0a login. Login tr\u01B0\u1EDBc r\u1ED3i retry.";
1440
+ case "not-found":
1441
+ return `URL c\xF3 th\u1EC3 sai ch\xEDnh t\u1EA3, ho\u1EB7c repo \u0111\xE3 b\u1ECB x\xF3a/rename. Check: ${url}`;
1442
+ case "network":
1443
+ return "Kh\xF4ng k\u1EBFt n\u1ED1i \u0111\u01B0\u1EE3c GitHub. Check m\u1EA1ng / VPN / firewall.";
1444
+ case "timeout":
1445
+ return "Network ch\u1EADm > 5s. Check m\u1EA1ng r\u1ED3i retry.";
1446
+ default:
1447
+ return "L\u1ED7i kh\xF4ng x\xE1c \u0111\u1ECBnh. Check URL + gh auth status.";
1448
+ }
1449
+ }
1450
+ async function handleRemoteAccessFailureWithAccountSwitch(args) {
1451
+ let reason = args.initialReason;
1452
+ let detail = args.initialDetail;
1453
+ while (true) {
1454
+ const ghUser = getCurrentGhUser();
1455
+ log.warn(`Kh\xF4ng truy c\u1EADp \u0111\u01B0\u1EE3c ${args.url}`);
1456
+ log.dim(` L\xFD do: ${reason}${detail ? ` \u2014 ${detail.slice(0, 150)}` : ""}`);
1457
+ log.info(getReasonHint(reason, args.url, ghUser));
1458
+ if (ghUser) log.dim(` gh CLI hi\u1EC7n \u0111ang login: ${ghUser}`);
1459
+ const action = await select3({
1460
+ message: "C\xE1ch x\u1EED l\xFD?",
1461
+ choices: [
1462
+ {
1463
+ name: "Switch GitHub account (gh auth login \u2014 m\u1EDF browser)",
1464
+ value: "switch"
1465
+ },
1466
+ {
1467
+ name: "T\xF4i v\u1EEBa fix (accept invite / s\u1EEDa URL) \u2014 retry verify",
1468
+ value: "retry"
1469
+ },
1470
+ {
1471
+ name: "T\u1EA1m ng\u01B0ng init \u2014 ch\u1EA1y l\u1EA1i 'avatar init' sau",
1472
+ value: "abort"
1473
+ }
1474
+ ]
1475
+ });
1476
+ if (action === "abort") {
1477
+ throw new RemoteAccessAbortedError(
1478
+ `User ch\u1ECDn t\u1EA1m ng\u01B0ng. Fix access ${args.url} r\u1ED3i ch\u1EA1y l\u1EA1i 'avatar init'.`
1479
+ );
1480
+ }
1481
+ if (action === "switch") {
1482
+ triggerGhAuthLoginInteractive();
1483
+ }
1484
+ log.info("Verify remote l\u1EA1i...");
1485
+ const result = tryVerifyGitRemoteAccessible(args.url);
1486
+ if (result.ok) {
1487
+ log.success(`Remote accessible: ${args.url}`);
1488
+ return;
1489
+ }
1490
+ reason = result.reason ?? "unknown";
1491
+ detail = result.detail;
1492
+ }
1493
+ }
1494
+
1495
+ // src/lib/install-gh-cli-via-package-manager.ts
1496
+ import { spawnSync as spawnSync12 } from "child_process";
1381
1497
  var INSTALL_COMMANDS = {
1382
1498
  brew: { cmd: "brew", args: ["install", "gh"] },
1383
1499
  apt: { cmd: "sudo", args: ["apt-get", "install", "-y", "gh"] },
@@ -1388,7 +1504,7 @@ var INSTALL_COMMANDS = {
1388
1504
  function installGhCliViaPackageManager(pm) {
1389
1505
  const spec = INSTALL_COMMANDS[pm];
1390
1506
  log.info(`\u0110ang c\xE0i gh CLI qua ${pm}...`);
1391
- const r = spawnSync10(spec.cmd, spec.args, { stdio: "inherit" });
1507
+ const r = spawnSync12(spec.cmd, spec.args, { stdio: "inherit" });
1392
1508
  if (r.status !== 0) {
1393
1509
  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.`);
1394
1510
  }
@@ -1396,9 +1512,9 @@ function installGhCliViaPackageManager(pm) {
1396
1512
  }
1397
1513
 
1398
1514
  // src/lib/setup-git-credential-via-gh.ts
1399
- import { spawnSync as spawnSync11 } from "child_process";
1515
+ import { spawnSync as spawnSync13 } from "child_process";
1400
1516
  function setupGitCredentialViaGh() {
1401
- const r = spawnSync11("gh", ["auth", "setup-git"], { stdio: "ignore" });
1517
+ const r = spawnSync13("gh", ["auth", "setup-git"], { stdio: "ignore" });
1402
1518
  if (r.status !== 0) {
1403
1519
  log.warn("gh auth setup-git fail (non-fatal). N\u1EBFu git clone l\u1ED7i 128 \u2192 ch\u1EA1y th\u1EE7 c\xF4ng.");
1404
1520
  return;
@@ -1407,10 +1523,10 @@ function setupGitCredentialViaGh() {
1407
1523
  }
1408
1524
 
1409
1525
  // src/lib/trigger-gh-cli-auth-login.ts
1410
- import { spawnSync as spawnSync12 } from "child_process";
1526
+ import { spawnSync as spawnSync14 } from "child_process";
1411
1527
  function triggerGhCliAuthLogin() {
1412
1528
  log.info("Kh\u1EDFi \u0111\u1ED9ng \u0111\u0103ng nh\u1EADp GitHub qua gh CLI (browser s\u1EBD m\u1EDF)...");
1413
- const r = spawnSync12(
1529
+ const r = spawnSync14(
1414
1530
  "gh",
1415
1531
  ["auth", "login", "--hostname", "github.com", "--web", "--git-protocol", "ssh"],
1416
1532
  { stdio: "inherit" }
@@ -1421,25 +1537,6 @@ function triggerGhCliAuthLogin() {
1421
1537
  log.success("\u0110\xE3 \u0111\u0103ng nh\u1EADp GitHub");
1422
1538
  }
1423
1539
 
1424
- // src/lib/verify-git-remote-accessible.ts
1425
- import { spawnSync as spawnSync13 } from "child_process";
1426
- var TIMEOUT_MS = 5e3;
1427
- var RemoteNotAccessibleError = class extends Error {
1428
- constructor(url, reason) {
1429
- super(`Kh\xF4ng truy c\u1EADp \u0111\u01B0\u1EE3c remote ${url}: ${reason}`);
1430
- this.name = "RemoteNotAccessibleError";
1431
- }
1432
- };
1433
- function verifyGitRemoteAccessible(url) {
1434
- const r = spawnSync13("git", ["ls-remote", "--exit-code", url, "HEAD"], {
1435
- stdio: "ignore",
1436
- timeout: TIMEOUT_MS
1437
- });
1438
- if (r.status === 0) return;
1439
- if (r.signal === "SIGTERM") throw new RemoteNotAccessibleError(url, "timeout 5s");
1440
- throw new RemoteNotAccessibleError(url, `git ls-remote exit ${r.status}`);
1441
- }
1442
-
1443
1540
  // src/lib/git-auth-and-install-orchestrator.ts
1444
1541
  async function ensureGitHubReady(remoteUrl) {
1445
1542
  let state = checkGhCliAuthStatus();
@@ -1465,21 +1562,29 @@ async function ensureGitHubReady(remoteUrl) {
1465
1562
  log.success("gh CLI s\u1EB5n s\xE0ng");
1466
1563
  setupGitCredentialViaGh();
1467
1564
  if (remoteUrl) {
1468
- verifyGitRemoteAccessible(remoteUrl);
1469
- log.success(`Remote accessible: ${remoteUrl}`);
1565
+ const result = tryVerifyGitRemoteAccessible(remoteUrl);
1566
+ if (result.ok) {
1567
+ log.success(`Remote accessible: ${remoteUrl}`);
1568
+ } else {
1569
+ await handleRemoteAccessFailureWithAccountSwitch({
1570
+ url: remoteUrl,
1571
+ initialReason: result.reason ?? "unknown",
1572
+ initialDetail: result.detail
1573
+ });
1574
+ }
1470
1575
  }
1471
1576
  }
1472
1577
 
1473
1578
  // src/lib/create-workspace-remote-via-gh.ts
1474
1579
  function canCreateInNamespace(org, ghUser) {
1475
1580
  if (org.toLowerCase() === ghUser.toLowerCase()) return { ok: true };
1476
- const r = spawnSync14("gh", ["api", `orgs/${org}/members/${ghUser}`, "--silent"], {
1581
+ const r = spawnSync15("gh", ["api", `orgs/${org}/members/${ghUser}`, "--silent"], {
1477
1582
  stdio: "ignore"
1478
1583
  });
1479
1584
  if (r.status === 0) return { ok: true };
1480
- const orgCheck = spawnSync14("gh", ["api", `orgs/${org}`, "--silent"], { stdio: "ignore" });
1585
+ const orgCheck = spawnSync15("gh", ["api", `orgs/${org}`, "--silent"], { stdio: "ignore" });
1481
1586
  if (orgCheck.status !== 0) {
1482
- const userCheck = spawnSync14("gh", ["api", `users/${org}`, "--silent"], { stdio: "ignore" });
1587
+ const userCheck = spawnSync15("gh", ["api", `users/${org}`, "--silent"], { stdio: "ignore" });
1483
1588
  if (userCheck.status === 0) {
1484
1589
  return {
1485
1590
  ok: false,
@@ -1508,7 +1613,7 @@ async function createWorkspaceRemoteViaGh(input3) {
1508
1613
  }
1509
1614
  const fullName = `${org}/${input3.workspaceName}`;
1510
1615
  log.info(`T\u1EA1o GitHub repo cho workspace: ${fullName} (${input3.visibility})...`);
1511
- const r = spawnSync14(
1616
+ const r = spawnSync15(
1512
1617
  "gh",
1513
1618
  [
1514
1619
  "repo",
@@ -1539,7 +1644,7 @@ async function createWorkspaceRemoteViaGh(input3) {
1539
1644
 
1540
1645
  // src/lib/safe-bootstrap-for-dirty-folder.ts
1541
1646
  import { readdirSync } from "fs";
1542
- import { select as select3 } from "@inquirer/prompts";
1647
+ import { select as select4 } from "@inquirer/prompts";
1543
1648
  import { simpleGit as simpleGit3 } from "simple-git";
1544
1649
 
1545
1650
  // src/lib/check-folder-has-git.ts
@@ -1670,7 +1775,7 @@ async function promptBootstrapStrategy(state, opts) {
1670
1775
  if (opts.presetStrategy) return opts.presetStrategy;
1671
1776
  if (opts.autoYes) return "stash";
1672
1777
  if (state === "empty" || state === "clean") return "commit-all";
1673
- return await select3({
1778
+ return await select4({
1674
1779
  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:",
1675
1780
  choices: [
1676
1781
  {
@@ -1807,19 +1912,19 @@ async function safeBootstrapGitInFolder(folderPath, opts = {}) {
1807
1912
  import { join as join14 } from "path";
1808
1913
 
1809
1914
  // src/lib/check-team-pack-access-with-retry-loop.ts
1810
- import { spawnSync as spawnSync15 } from "child_process";
1811
- import { confirm as confirm2, select as select4 } from "@inquirer/prompts";
1915
+ import { spawnSync as spawnSync16 } from "child_process";
1916
+ import { confirm as confirm2, select as select5 } from "@inquirer/prompts";
1812
1917
  function parseRepoSlugFromGitUrl(url) {
1813
1918
  const httpsMatch = url.match(/github\.com[/:]([^/]+\/[^/]+?)(?:\.git)?$/);
1814
1919
  if (httpsMatch) return httpsMatch[1];
1815
1920
  return null;
1816
1921
  }
1817
1922
  function checkRepoAccess(repoSlug) {
1818
- const r = spawnSync15("gh", ["api", `repos/${repoSlug}`], { stdio: "ignore" });
1923
+ const r = spawnSync16("gh", ["api", `repos/${repoSlug}`], { stdio: "ignore" });
1819
1924
  return r.status === 0;
1820
1925
  }
1821
- function getCurrentGhUser() {
1822
- const r = spawnSync15("gh", ["api", "user", "--jq", ".login"], {
1926
+ function getCurrentGhUser2() {
1927
+ const r = spawnSync16("gh", ["api", "user", "--jq", ".login"], {
1823
1928
  encoding: "utf8",
1824
1929
  stdio: ["ignore", "pipe", "pipe"]
1825
1930
  });
@@ -1852,7 +1957,7 @@ function buildAccessRequestInfo(ghUser, ssoEmail) {
1852
1957
  }
1853
1958
  async function ensureTeamPackAccessWithRetry(args) {
1854
1959
  if (checkRepoAccess(args.repoSlug)) return true;
1855
- const ghUser = getCurrentGhUser();
1960
+ const ghUser = getCurrentGhUser2();
1856
1961
  const info = buildAccessRequestInfo(ghUser, args.ssoEmail ?? null);
1857
1962
  log.warn(`B\u1EA1n ch\u01B0a c\xF3 quy\u1EC1n access v\xE0o b\u1ED9 package ${args.repoSlug}.`);
1858
1963
  log.info("Li\xEAn h\u1EC7 admin (Luke @nal.vn) \u0111\u1EC3 \u0111\u01B0\u1EE3c add v\xE0o org nalvn.");
@@ -1861,7 +1966,7 @@ async function ensureTeamPackAccessWithRetry(args) {
1861
1966
  log.plain("");
1862
1967
  await copyInfoToClipboardWithConsent(info);
1863
1968
  while (true) {
1864
- const action = await select4({
1969
+ const action = await select5({
1865
1970
  message: "Ti\u1EBFp t\u1EE5c?",
1866
1971
  choices: [
1867
1972
  { name: "\u0110\xE3 \u0111\u01B0\u1EE3c add \u2014 ki\u1EC3m tra l\u1EA1i v\xE0 ti\u1EBFp t\u1EE5c", value: "retry" },
@@ -2200,6 +2305,10 @@ function registerInitCommand(program2) {
2200
2305
  log.dim(err.message);
2201
2306
  process.exit(0);
2202
2307
  }
2308
+ if (err instanceof RemoteAccessAbortedError) {
2309
+ log.dim(err.message);
2310
+ process.exit(0);
2311
+ }
2203
2312
  log.error(err instanceof Error ? err.message : String(err));
2204
2313
  process.exit(1);
2205
2314
  }
@@ -2234,7 +2343,7 @@ async function runInit(opts) {
2234
2343
  }
2235
2344
  }
2236
2345
  async function promptProjectStatus() {
2237
- return await select5({
2346
+ return await select6({
2238
2347
  message: "T\xECnh tr\u1EA1ng d\u1EF1 \xE1n c\u1EE7a b\u1EA1n?",
2239
2348
  choices: [
2240
2349
  { name: "1. \u0110\xE3 c\xF3 repo git remote (URL c\xF3 s\u1EB5n)", value: "existing-remote" },
@@ -2312,7 +2421,7 @@ async function runInitFromScratch(opts, ownerEmail) {
2312
2421
  message: "T\xEAn d\u1EF1 \xE1n:",
2313
2422
  validate: (v) => v.length > 0 ? true : "T\xEAn b\u1EAFt bu\u1ED9c"
2314
2423
  });
2315
- const visibility = opts.repoVisibility ?? await select5({
2424
+ const visibility = opts.repoVisibility ?? await select6({
2316
2425
  message: "Visibility?",
2317
2426
  choices: [
2318
2427
  { name: "private (m\u1EB7c \u0111\u1ECBnh)", value: "private" },
@@ -2381,7 +2490,7 @@ async function getOrCreateOriginRemote(folderPath, opts) {
2381
2490
  return void 0;
2382
2491
  }
2383
2492
  await ensureGitHubReady();
2384
- const visibility = opts.repoVisibility ?? await select5({
2493
+ const visibility = opts.repoVisibility ?? await select6({
2385
2494
  message: "Visibility?",
2386
2495
  choices: [
2387
2496
  { name: "private (m\u1EB7c \u0111\u1ECBnh)", value: "private" },
@@ -2478,7 +2587,7 @@ async function maybeCreateWorkspaceRemote(args) {
2478
2587
  });
2479
2588
  }
2480
2589
  if (!shouldCreate) return;
2481
- const visibility = args.repoVisibility ?? (args.autoYes ? "private" : await select5({
2590
+ const visibility = args.repoVisibility ?? (args.autoYes ? "private" : await select6({
2482
2591
  message: "Workspace visibility?",
2483
2592
  choices: [
2484
2593
  { name: "private (m\u1EB7c \u0111\u1ECBnh, an to\xE0n)", value: "private" },
@@ -2839,7 +2948,7 @@ async function removeSubmoduleEntry(gitmodulesPath, submodulePath) {
2839
2948
  }
2840
2949
 
2841
2950
  // src/commands/uninstall.ts
2842
- var CLI_VERSION = "1.2.3";
2951
+ var CLI_VERSION = "1.2.4";
2843
2952
  function registerUninstallCommand(program2) {
2844
2953
  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) => {
2845
2954
  try {
@@ -2921,7 +3030,7 @@ function printUninstallSuccessBox(backupPath) {
2921
3030
  }
2922
3031
 
2923
3032
  // src/index.ts
2924
- var CLI_VERSION2 = "1.2.3";
3033
+ var CLI_VERSION2 = "1.2.4";
2925
3034
  var program = new Command();
2926
3035
  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(
2927
3036
  "beforeAll",