@nalvietnam/avatar-cli 1.2.5 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -546,19 +546,19 @@ function applyUseGlobal(existing, source) {
546
546
  ...sourceModel ? { model: sourceModel } : {}
547
547
  };
548
548
  }
549
- async function writeClaudeSettings(workspacePath, input4) {
549
+ async function writeClaudeSettings(workspacePath, input5) {
550
550
  const path = getClaudeSettingsPath(workspacePath);
551
551
  const existing = await readExistingSettings(path);
552
552
  let merged;
553
- switch (input4.provider) {
553
+ switch (input5.provider) {
554
554
  case "subscription":
555
- merged = applySubscription(existing, input4.model);
555
+ merged = applySubscription(existing, input5.model);
556
556
  break;
557
557
  case "llmlite":
558
- merged = applyLLMLite(existing, input4.apiKey, input4.baseUrl, input4.model);
558
+ merged = applyLLMLite(existing, input5.apiKey, input5.baseUrl, input5.model);
559
559
  break;
560
560
  case "use-global":
561
- merged = applyUseGlobal(existing, input4.sourceSettings);
561
+ merged = applyUseGlobal(existing, input5.sourceSettings);
562
562
  break;
563
563
  }
564
564
  await writeJsonAtomic(path, merged, SECRET_FILE_MODE2);
@@ -1194,8 +1194,8 @@ 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 input3, select as select7 } from "@inquirer/prompts";
1198
- import boxen3 from "boxen";
1197
+ import { confirm as confirm3, input as input4, select as select7 } from "@inquirer/prompts";
1198
+ import boxen4 from "boxen";
1199
1199
 
1200
1200
  // src/lib/prompt-recovery-action-on-failure.ts
1201
1201
  import { input as input2, select as select3 } from "@inquirer/prompts";
@@ -1227,6 +1227,7 @@ import { join as join10 } from "path";
1227
1227
  // src/lib/check-team-pack-access-with-retry-loop.ts
1228
1228
  import { spawnSync as spawnSync6 } from "child_process";
1229
1229
  import { confirm as confirm2, select as select4 } from "@inquirer/prompts";
1230
+ import boxen2 from "boxen";
1230
1231
  function parseRepoSlugFromGitUrl(url) {
1231
1232
  const httpsMatch = url.match(/github\.com[/:]([^/]+\/[^/]+?)(?:\.git)?$/);
1232
1233
  if (httpsMatch) return httpsMatch[1];
@@ -1244,6 +1245,13 @@ function getCurrentGhUser() {
1244
1245
  if (r.status !== 0) return null;
1245
1246
  return r.stdout.trim() || null;
1246
1247
  }
1248
+ function triggerGhAuthLoginInteractive() {
1249
+ log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
1250
+ const r = spawnSync6("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1251
+ if (r.status !== 0) {
1252
+ log.warn(`gh auth login exit ${r.status}. C\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
1253
+ }
1254
+ }
1247
1255
  async function copyInfoToClipboardWithConsent(info) {
1248
1256
  const ok = await confirm2({
1249
1257
  message: "Copy th\xF4ng tin (GitHub username + email) v\xE0o clipboard \u0111\u1EC3 d\xE1n v\xE0o Slack/email?",
@@ -1258,44 +1266,78 @@ async function copyInfoToClipboardWithConsent(info) {
1258
1266
  log.dim(`Copy clipboard fail: ${err.message}`);
1259
1267
  }
1260
1268
  }
1261
- function buildAccessRequestInfo(ghUser, ssoEmail) {
1269
+ function printAccessWarningBox(repoSlug, ghUser, ssoEmail) {
1262
1270
  const lines = [
1263
- "Request access team-ai-pack (NAL)",
1271
+ `${chalk.red("\u26D4 KH\xD4NG C\xD3 QUY\u1EC0N ACCESS")}`,
1272
+ "",
1273
+ `Repo: ${chalk.bold(repoSlug)}`,
1274
+ "",
1275
+ "B\u1EA1n c\u1EA7n \u0111\u01B0\u1EE3c admin add v\xE0o org \u0111\u1EC3 pull team-ai-pack.",
1276
+ "",
1277
+ `${chalk.dim("Th\xF4ng tin g\u1EEDi admin:")}`,
1278
+ ` GitHub username: ${chalk.cyan(ghUser ?? "(ch\u01B0a gh auth \u2014 ch\u1EA1y: gh auth login)")}`,
1279
+ ` NAL email: ${chalk.cyan(ssoEmail ?? "(ch\u01B0a avatar login \u2014 ch\u1EA1y: avatar login)")}`,
1280
+ ` Date: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
1281
+ "",
1282
+ `${chalk.dim("Li\xEAn h\u1EC7:")} luke@nal.vn (Slack #avatar-setup)`
1283
+ ];
1284
+ process.stdout.write(
1285
+ `${boxen2(lines.join("\n"), { padding: 1, borderColor: "red", borderStyle: "round" })}
1286
+ `
1287
+ );
1288
+ }
1289
+ function buildAccessRequestInfo(repoSlug, ghUser, ssoEmail) {
1290
+ return [
1291
+ `Request access ${repoSlug} (NAL)`,
1264
1292
  "",
1265
1293
  `GitHub username: ${ghUser ?? "(ch\u01B0a gh auth \u2014 ch\u1EA1y: gh auth login)"}`,
1266
1294
  `NAL email: ${ssoEmail ?? "(ch\u01B0a avatar login \u2014 ch\u1EA1y: avatar login)"}`,
1267
1295
  `Date: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`
1268
- ];
1269
- return lines.join("\n");
1296
+ ].join("\n");
1270
1297
  }
1271
1298
  async function ensureTeamPackAccessWithRetry(args) {
1272
1299
  if (checkRepoAccess(args.repoSlug)) return true;
1273
- const ghUser = getCurrentGhUser();
1274
- const info = buildAccessRequestInfo(ghUser, args.ssoEmail ?? null);
1275
- log.warn(`B\u1EA1n ch\u01B0a c\xF3 quy\u1EC1n access v\xE0o b\u1ED9 package ${args.repoSlug}.`);
1276
- log.info("Li\xEAn h\u1EC7 admin (Luke @nal.vn) \u0111\u1EC3 \u0111\u01B0\u1EE3c add v\xE0o org nalvn.");
1277
- log.plain("");
1278
- log.plain(info);
1279
- log.plain("");
1280
- await copyInfoToClipboardWithConsent(info);
1300
+ const initialGhUser = getCurrentGhUser();
1301
+ printAccessWarningBox(args.repoSlug, initialGhUser, args.ssoEmail ?? null);
1302
+ await copyInfoToClipboardWithConsent(
1303
+ buildAccessRequestInfo(args.repoSlug, initialGhUser, args.ssoEmail ?? null)
1304
+ );
1281
1305
  while (true) {
1306
+ const ghUser = getCurrentGhUser();
1307
+ const ghUserDisplay = ghUser ?? "(ch\u01B0a gh auth)";
1282
1308
  const action = await select4({
1283
- message: "Ti\u1EBFp t\u1EE5c?",
1309
+ message: "C\xE1ch x\u1EED l\xFD?",
1284
1310
  choices: [
1285
- { name: "\u0110\xE3 \u0111\u01B0\u1EE3c add \u2014 ki\u1EC3m tra l\u1EA1i v\xE0 ti\u1EBFp t\u1EE5c", value: "retry" },
1286
- { name: "T\u1EA1m ng\u01B0ng \u2014 ch\u1EA1y l\u1EA1i 'avatar init' sau", value: "abort" }
1311
+ {
1312
+ name: `\u0110\xE3 \u0111\u01B0\u1EE3c grant access v\u1EDBi GitHub username '${ghUserDisplay}' \u2014 ki\u1EC3m tra l\u1EA1i`,
1313
+ value: "retry-same"
1314
+ },
1315
+ {
1316
+ name: "\u0110\xE3 grant v\u1EDBi GitHub account kh\xE1c \u2014 switch gh (m\u1EDF browser)",
1317
+ value: "switch-account"
1318
+ },
1319
+ {
1320
+ name: "T\u1EA1m ng\u01B0ng \u2014 ch\u1EA1y l\u1EA1i 'avatar init' sau",
1321
+ value: "abort"
1322
+ }
1287
1323
  ]
1288
1324
  });
1289
1325
  if (action === "abort") {
1290
1326
  log.dim("T\u1EA1m ng\u01B0ng. Ch\u1EA1y l\u1EA1i 'avatar init' sau khi \u0111\xE3 accept invite t\u1EEB GitHub.");
1291
1327
  return false;
1292
1328
  }
1329
+ if (action === "switch-account") {
1330
+ triggerGhAuthLoginInteractive();
1331
+ }
1293
1332
  log.info("Ki\u1EC3m tra access...");
1294
1333
  if (checkRepoAccess(args.repoSlug)) {
1295
- log.success("\u0110\xE3 c\xF3 access \u2014 ti\u1EBFp t\u1EE5c.");
1334
+ const finalUser = getCurrentGhUser();
1335
+ log.success(`\u0110\xE3 c\xF3 access v\u1EDBi '${finalUser ?? "(unknown)"}' \u2014 ti\u1EBFp t\u1EE5c.`);
1296
1336
  return true;
1297
1337
  }
1298
- log.warn("V\u1EABn ch\u01B0a c\xF3 access. \u0110\u1EA3m b\u1EA3o b\u1EA1n \u0111\xE3 accept email invite t\u1EEB GitHub (Inbox + Spam).");
1338
+ log.warn(
1339
+ `V\u1EABn ch\u01B0a c\xF3 access v\u1EDBi account '${ghUser ?? "(unknown)"}'. \u0110\u1EA3m b\u1EA3o \u0111\xE3 accept email invite ho\u1EB7c switch \u0111\xFAng account.`
1340
+ );
1299
1341
  }
1300
1342
  }
1301
1343
 
@@ -1361,10 +1403,10 @@ async function readPinnedPackVersion(projectRoot) {
1361
1403
  }
1362
1404
 
1363
1405
  // src/lib/add-team-pack-submodule-with-retry-on-network-fail.ts
1364
- async function addTeamPackSubmoduleWithRetryOnNetworkFail(projectRoot, tag) {
1406
+ async function addTeamPackSubmoduleWithRetryOnNetworkFail(projectRoot, tag, ssoEmail) {
1365
1407
  while (true) {
1366
1408
  try {
1367
- const result = await addTeamPackSubmodule(projectRoot, tag);
1409
+ const result = await addTeamPackSubmodule(projectRoot, tag, ssoEmail);
1368
1410
  return { pinnedTag: result.pinnedTag, skipped: false };
1369
1411
  } catch (err) {
1370
1412
  if (err instanceof TeamPackAccessAbortedError) throw err;
@@ -1456,15 +1498,15 @@ var RepoAlreadyExistsError = class extends Error {
1456
1498
  this.name = "RepoAlreadyExistsError";
1457
1499
  }
1458
1500
  };
1459
- function executeGhRepoCreate(input4) {
1460
- const fullName = `${input4.org}/${input4.name}`;
1501
+ function executeGhRepoCreate(input5) {
1502
+ const fullName = `${input5.org}/${input5.name}`;
1461
1503
  const args = [
1462
1504
  "repo",
1463
1505
  "create",
1464
1506
  fullName,
1465
- `--${input4.visibility}`,
1507
+ `--${input5.visibility}`,
1466
1508
  "--source",
1467
- input4.folder,
1509
+ input5.folder,
1468
1510
  "--remote",
1469
1511
  "origin",
1470
1512
  "--push"
@@ -1517,16 +1559,16 @@ function validateRepoVisibility(v) {
1517
1559
  }
1518
1560
 
1519
1561
  // src/lib/create-github-remote-from-folder.ts
1520
- function createGithubRemoteFromFolder(input4) {
1521
- validateRepoName(input4.name);
1522
- validateRepoVisibility(input4.visibility);
1523
- const org = input4.org ?? resolveGithubUsernameDefault();
1524
- log.info(`T\u1EA1o GitHub repo ${org}/${input4.name} (${input4.visibility})...`);
1562
+ function createGithubRemoteFromFolder(input5) {
1563
+ validateRepoName(input5.name);
1564
+ validateRepoVisibility(input5.visibility);
1565
+ const org = input5.org ?? resolveGithubUsernameDefault();
1566
+ log.info(`T\u1EA1o GitHub repo ${org}/${input5.name} (${input5.visibility})...`);
1525
1567
  const urls = executeGhRepoCreate({
1526
- folder: input4.folder,
1568
+ folder: input5.folder,
1527
1569
  org,
1528
- name: input4.name,
1529
- visibility: input4.visibility
1570
+ name: input5.name,
1571
+ visibility: input5.visibility
1530
1572
  });
1531
1573
  log.success(`\u0110\xE3 t\u1EA1o: ${urls.sshUrl}`);
1532
1574
  return urls;
@@ -1568,7 +1610,7 @@ function detectPackageManager() {
1568
1610
 
1569
1611
  // src/lib/handle-remote-access-failure-with-account-switch.ts
1570
1612
  import { spawnSync as spawnSync12 } from "child_process";
1571
- import { select as select5 } from "@inquirer/prompts";
1613
+ import { input as input3, select as select5 } from "@inquirer/prompts";
1572
1614
 
1573
1615
  // src/lib/verify-git-remote-accessible.ts
1574
1616
  import { spawnSync as spawnSync11 } from "child_process";
@@ -1616,7 +1658,7 @@ function getCurrentGhUser2() {
1616
1658
  if (r.status !== 0) return null;
1617
1659
  return r.stdout.trim() || null;
1618
1660
  }
1619
- function triggerGhAuthLoginInteractive() {
1661
+ function triggerGhAuthLoginInteractive2() {
1620
1662
  log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
1621
1663
  const r = spawnSync12("gh", ["auth", "login", "--web"], { stdio: "inherit" });
1622
1664
  if (r.status !== 0) {
@@ -1628,33 +1670,43 @@ function getReasonHint(reason, url, ghUser) {
1628
1670
  case "no-access":
1629
1671
  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.";
1630
1672
  case "not-found":
1631
- return `URL c\xF3 th\u1EC3 sai ch\xEDnh t\u1EA3, ho\u1EB7c repo \u0111\xE3 b\u1ECB x\xF3a/rename. Check: ${url}`;
1673
+ return `URL c\xF3 th\u1EC3 sai ch\xEDnh t\u1EA3, ho\u1EB7c repo \u0111\xE3 b\u1ECB x\xF3a/rename. Nh\u1EADp l\u1EA1i URL \u0111\xFAng. Check: ${url}`;
1632
1674
  case "network":
1633
1675
  return "Kh\xF4ng k\u1EBFt n\u1ED1i \u0111\u01B0\u1EE3c GitHub. Check m\u1EA1ng / VPN / firewall.";
1634
1676
  case "timeout":
1635
1677
  return "Network ch\u1EADm > 5s. Check m\u1EA1ng r\u1ED3i retry.";
1636
1678
  default:
1637
- return "L\u1ED7i kh\xF4ng x\xE1c \u0111\u1ECBnh. Check URL + gh auth status.";
1679
+ return "L\u1ED7i kh\xF4ng x\xE1c \u0111\u1ECBnh. URL c\xF3 th\u1EC3 sai \u2014 nh\u1EADp l\u1EA1i, ho\u1EB7c check gh auth status.";
1638
1680
  }
1639
1681
  }
1682
+ function isValidGitUrl(url) {
1683
+ const trimmed = url.trim();
1684
+ if (!trimmed) return false;
1685
+ return /^https?:\/\/[\w.@/-]+$/.test(trimmed) || /^git@[\w.-]+:[\w./-]+\.git$/.test(trimmed) || /^[\w.-]+\/[\w.-]+$/.test(trimmed);
1686
+ }
1640
1687
  async function handleRemoteAccessFailureWithAccountSwitch(args) {
1688
+ let currentUrl = args.url;
1641
1689
  let reason = args.initialReason;
1642
1690
  let detail = args.initialDetail;
1643
1691
  while (true) {
1644
1692
  const ghUser = getCurrentGhUser2();
1645
- log.warn(`Kh\xF4ng truy c\u1EADp \u0111\u01B0\u1EE3c ${args.url}`);
1693
+ log.warn(`Kh\xF4ng truy c\u1EADp \u0111\u01B0\u1EE3c ${currentUrl}`);
1646
1694
  log.dim(` L\xFD do: ${reason}${detail ? ` \u2014 ${detail.slice(0, 150)}` : ""}`);
1647
- log.info(getReasonHint(reason, args.url, ghUser));
1695
+ log.info(getReasonHint(reason, currentUrl, ghUser));
1648
1696
  if (ghUser) log.dim(` gh CLI hi\u1EC7n \u0111ang login: ${ghUser}`);
1649
1697
  const action = await select5({
1650
1698
  message: "C\xE1ch x\u1EED l\xFD?",
1651
1699
  choices: [
1700
+ {
1701
+ name: "Nh\u1EADp l\u1EA1i URL \u0111\xFAng (recommended khi URL sai ch\xEDnh t\u1EA3)",
1702
+ value: "re-input-url"
1703
+ },
1652
1704
  {
1653
1705
  name: "Switch GitHub account (gh auth login \u2014 m\u1EDF browser)",
1654
1706
  value: "switch"
1655
1707
  },
1656
1708
  {
1657
- name: "T\xF4i v\u1EEBa fix (accept invite / s\u1EEDa URL) \u2014 retry verify",
1709
+ name: "T\xF4i v\u1EEBa fix (accept invite / s\u1EEDa permission) \u2014 retry verify",
1658
1710
  value: "retry"
1659
1711
  },
1660
1712
  {
@@ -1665,17 +1717,25 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
1665
1717
  });
1666
1718
  if (action === "abort") {
1667
1719
  throw new RemoteAccessAbortedError(
1668
- `User ch\u1ECDn t\u1EA1m ng\u01B0ng. Fix access ${args.url} r\u1ED3i ch\u1EA1y l\u1EA1i 'avatar init'.`
1720
+ `User ch\u1ECDn t\u1EA1m ng\u01B0ng. Fix access ${currentUrl} r\u1ED3i ch\u1EA1y l\u1EA1i 'avatar init'.`
1669
1721
  );
1670
1722
  }
1723
+ if (action === "re-input-url") {
1724
+ const newUrl = await input3({
1725
+ message: "URL git remote (https://github.com/owner/repo.git ho\u1EB7c git@github.com:owner/repo.git):",
1726
+ default: currentUrl,
1727
+ validate: (v) => isValidGitUrl(v) || "URL kh\xF4ng \u0111\xFAng format git remote"
1728
+ });
1729
+ currentUrl = newUrl.trim();
1730
+ }
1671
1731
  if (action === "switch") {
1672
- triggerGhAuthLoginInteractive();
1732
+ triggerGhAuthLoginInteractive2();
1673
1733
  }
1674
- log.info("Verify remote l\u1EA1i...");
1675
- const result = tryVerifyGitRemoteAccessible(args.url);
1734
+ log.info(`Verify remote l\u1EA1i: ${currentUrl}...`);
1735
+ const result = tryVerifyGitRemoteAccessible(currentUrl);
1676
1736
  if (result.ok) {
1677
- log.success(`Remote accessible: ${args.url}`);
1678
- return;
1737
+ log.success(`Remote accessible: ${currentUrl}`);
1738
+ return { resolvedUrl: currentUrl };
1679
1739
  }
1680
1740
  reason = result.reason ?? "unknown";
1681
1741
  detail = result.detail;
@@ -1792,14 +1852,16 @@ async function ensureGitHubReady(remoteUrl) {
1792
1852
  const result = tryVerifyGitRemoteAccessible(remoteUrl);
1793
1853
  if (result.ok) {
1794
1854
  log.success(`Remote accessible: ${remoteUrl}`);
1795
- } else {
1796
- await handleRemoteAccessFailureWithAccountSwitch({
1797
- url: remoteUrl,
1798
- initialReason: result.reason ?? "unknown",
1799
- initialDetail: result.detail
1800
- });
1855
+ return { resolvedRemoteUrl: remoteUrl };
1801
1856
  }
1857
+ const recovered = await handleRemoteAccessFailureWithAccountSwitch({
1858
+ url: remoteUrl,
1859
+ initialReason: result.reason ?? "unknown",
1860
+ initialDetail: result.detail
1861
+ });
1862
+ return { resolvedRemoteUrl: recovered.resolvedUrl };
1802
1863
  }
1864
+ return {};
1803
1865
  }
1804
1866
 
1805
1867
  // src/lib/create-workspace-remote-via-gh.ts
@@ -1828,27 +1890,27 @@ function canCreateInNamespace(org, ghUser) {
1828
1890
  reason: `'${ghUser}' kh\xF4ng ph\u1EA3i member c\u1EE7a org '${org}'. Li\xEAn h\u1EC7 admin org \u0111\u1EC3 \u0111\u01B0\u1EE3c invite, ho\u1EB7c pass --repo-org=${ghUser} t\u1EA1o d\u01B0\u1EDBi personal account.`
1829
1891
  };
1830
1892
  }
1831
- async function createWorkspaceRemoteViaGh(input4) {
1832
- validateRepoName(input4.workspaceName);
1833
- validateRepoVisibility(input4.visibility);
1893
+ async function createWorkspaceRemoteViaGh(input5) {
1894
+ validateRepoName(input5.workspaceName);
1895
+ validateRepoVisibility(input5.visibility);
1834
1896
  await ensureGitHubReady();
1835
1897
  const ghUser = resolveGithubUsernameDefault();
1836
- const org = input4.org ?? ghUser;
1898
+ const org = input5.org ?? ghUser;
1837
1899
  const namespaceCheck = canCreateInNamespace(org, ghUser);
1838
1900
  if (!namespaceCheck.ok) {
1839
1901
  throw new Error(`Kh\xF4ng th\u1EC3 t\u1EA1o repo d\u01B0\u1EDBi '${org}/': ${namespaceCheck.reason}`);
1840
1902
  }
1841
- const fullName = `${org}/${input4.workspaceName}`;
1842
- log.info(`T\u1EA1o GitHub repo cho workspace: ${fullName} (${input4.visibility})...`);
1903
+ const fullName = `${org}/${input5.workspaceName}`;
1904
+ log.info(`T\u1EA1o GitHub repo cho workspace: ${fullName} (${input5.visibility})...`);
1843
1905
  const r = spawnSync16(
1844
1906
  "gh",
1845
1907
  [
1846
1908
  "repo",
1847
1909
  "create",
1848
1910
  fullName,
1849
- `--${input4.visibility}`,
1911
+ `--${input5.visibility}`,
1850
1912
  "--source",
1851
- input4.workspacePath,
1913
+ input5.workspacePath,
1852
1914
  "--remote",
1853
1915
  "origin",
1854
1916
  "--push"
@@ -1860,7 +1922,7 @@ async function createWorkspaceRemoteViaGh(input4) {
1860
1922
  if (stderr) process.stderr.write(`${stderr}
1861
1923
  `);
1862
1924
  throw new Error(
1863
- `T\u1EA1o workspace remote th\u1EA5t b\u1EA1i (exit ${r.status}). Workspace v\u1EABn d\xF9ng \u0111\u01B0\u1EE3c local. Setup remote sau qua: gh repo create ${fullName} --${input4.visibility} --source=. --remote=origin --push`
1925
+ `T\u1EA1o workspace remote th\u1EA5t b\u1EA1i (exit ${r.status}). Workspace v\u1EABn d\xF9ng \u0111\u01B0\u1EE3c local. Setup remote sau qua: gh repo create ${fullName} --${input5.visibility} --source=. --remote=origin --push`
1864
1926
  );
1865
1927
  }
1866
1928
  const sshUrl = `git@github.com:${fullName}.git`;
@@ -2176,7 +2238,7 @@ function buildScaffoldVariables(args) {
2176
2238
  }
2177
2239
 
2178
2240
  // src/commands/login.ts
2179
- import boxen2 from "boxen";
2241
+ import boxen3 from "boxen";
2180
2242
  import open from "open";
2181
2243
 
2182
2244
  // src/lib/google-oauth-device-flow.ts
@@ -2323,7 +2385,7 @@ async function runLogin(opts) {
2323
2385
  "",
2324
2386
  `Ho\u1EB7c Avatar t\u1EF1 m\u1EDF browser, click ${chalk.green("Allow")}...`
2325
2387
  ].join("\n");
2326
- process.stdout.write(`${boxen2(instructions, { padding: 1, borderStyle: "round" })}
2388
+ process.stdout.write(`${boxen3(instructions, { padding: 1, borderStyle: "round" })}
2327
2389
  `);
2328
2390
  void open(verificationUrl).catch(() => {
2329
2391
  log.dim("(Kh\xF4ng m\u1EDF \u0111\u01B0\u1EE3c browser t\u1EF1 \u0111\u1ED9ng \u2014 copy URL \u1EDF tr\xEAn)");
@@ -2458,14 +2520,15 @@ async function promptProjectStatus() {
2458
2520
  });
2459
2521
  }
2460
2522
  async function runInitFromExistingRemote(opts, ownerEmail) {
2461
- const remoteUrl = opts.clientRepo ?? await input3({
2523
+ const initialRemoteUrl = opts.clientRepo ?? await input4({
2462
2524
  message: "URL git c\u1EE7a repo:",
2463
2525
  validate: (v) => v.length > 0 ? true : "URL b\u1EAFt bu\u1ED9c"
2464
2526
  });
2465
- await ensureGitHubReady(remoteUrl);
2527
+ const { resolvedRemoteUrl } = await ensureGitHubReady(initialRemoteUrl);
2528
+ const remoteUrl = resolvedRemoteUrl ?? initialRemoteUrl;
2466
2529
  const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
2467
2530
  const inferredName = inferWorkspaceName(remoteUrl);
2468
- const workspaceName = opts.workspaceName ?? await input3({ message: "T\xEAn workspace:", default: inferredName });
2531
+ const workspaceName = opts.workspaceName ?? await input4({ message: "T\xEAn workspace:", default: inferredName });
2469
2532
  const workspaceParent = resolve(opts.workspaceParent ?? ".");
2470
2533
  const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);
2471
2534
  await scaffoldWorkspaceWithSrcSubmodule({
@@ -2482,12 +2545,13 @@ async function runInitFromExistingRemote(opts, ownerEmail) {
2482
2545
  repoVisibility: opts.repoVisibility,
2483
2546
  repoOrg: opts.repoOrg,
2484
2547
  flow: "existing-remote",
2485
- aiSkip: opts.aiSkip
2548
+ aiSkip: opts.aiSkip,
2549
+ ssoEmail: ownerEmail
2486
2550
  });
2487
2551
  }
2488
2552
  async function runInitFromExistingFolder(opts, ownerEmail) {
2489
2553
  const folderPath = resolve(
2490
- opts.folderPath ?? await input3({
2554
+ opts.folderPath ?? await input4({
2491
2555
  message: "\u0110\u01B0\u1EDDng d\u1EABn folder hi\u1EC7n c\xF3:",
2492
2556
  validate: (v) => v.length > 0 ? true : "Path b\u1EAFt bu\u1ED9c"
2493
2557
  })
@@ -2499,7 +2563,7 @@ async function runInitFromExistingFolder(opts, ownerEmail) {
2499
2563
  const remoteUrl = await getOrCreateOriginRemote(folderPath, opts);
2500
2564
  const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
2501
2565
  const inferredName = opts.workspaceName ?? `${basename(folderPath)}-avatar-workspace`;
2502
- const workspaceName = opts.workspaceName ?? await input3({ message: "T\xEAn workspace:", default: inferredName });
2566
+ const workspaceName = opts.workspaceName ?? await input4({ message: "T\xEAn workspace:", default: inferredName });
2503
2567
  const workspaceParent = resolve(opts.workspaceParent ?? ".");
2504
2568
  const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);
2505
2569
  await scaffoldWorkspaceWithSrcSubmodule({
@@ -2517,12 +2581,13 @@ async function runInitFromExistingFolder(opts, ownerEmail) {
2517
2581
  repoVisibility: opts.repoVisibility,
2518
2582
  repoOrg: opts.repoOrg,
2519
2583
  flow: "existing-folder",
2520
- aiSkip: opts.aiSkip
2584
+ aiSkip: opts.aiSkip,
2585
+ ssoEmail: ownerEmail
2521
2586
  });
2522
2587
  }
2523
2588
  async function runInitFromScratch(opts, ownerEmail) {
2524
2589
  await ensureGitHubReady();
2525
- const projectName = opts.workspaceName ?? await input3({
2590
+ const projectName = opts.workspaceName ?? await input4({
2526
2591
  message: "T\xEAn d\u1EF1 \xE1n:",
2527
2592
  validate: (v) => v.length > 0 ? true : "T\xEAn b\u1EAFt bu\u1ED9c"
2528
2593
  });
@@ -2554,9 +2619,11 @@ async function runInitFromScratch(opts, ownerEmail) {
2554
2619
  await git(workspacePath).subModule(["add", urls.sshUrl, "src"]);
2555
2620
  let pinnedTag = "HEAD";
2556
2621
  if (!opts.skipTeamPack) {
2622
+ sp.stop();
2557
2623
  const result = await addTeamPackSubmoduleWithRetryOnNetworkFail(
2558
2624
  workspacePath,
2559
- opts.packVersion
2625
+ opts.packVersion,
2626
+ ownerEmail
2560
2627
  );
2561
2628
  pinnedTag = result.pinnedTag ?? "HEAD";
2562
2629
  sp.succeed(`Pin team-ai-pack v\xE0o ${pinnedTag}`);
@@ -2605,7 +2672,7 @@ async function getOrCreateOriginRemote(folderPath, opts) {
2605
2672
  { name: "public", value: "public" }
2606
2673
  ]
2607
2674
  });
2608
- const repoName = await input3({
2675
+ const repoName = await input4({
2609
2676
  message: "T\xEAn repo:",
2610
2677
  default: basename(folderPath)
2611
2678
  });
@@ -2627,9 +2694,11 @@ async function scaffoldWorkspaceWithSrcSubmodule(args) {
2627
2694
  await git(args.workspacePath).subModule(["add", args.srcRemoteUrl, "src"]);
2628
2695
  let pinnedTag = "HEAD";
2629
2696
  if (!args.skipTeamPack) {
2697
+ sp.stop();
2630
2698
  const result = await addTeamPackSubmoduleWithRetryOnNetworkFail(
2631
2699
  args.workspacePath,
2632
- args.packVersion
2700
+ args.packVersion,
2701
+ args.ssoEmail
2633
2702
  );
2634
2703
  pinnedTag = result.pinnedTag ?? "HEAD";
2635
2704
  sp.succeed(`Pin team-ai-pack v\xE0o ${pinnedTag}`);
@@ -2760,7 +2829,7 @@ async function resolveWorkspacePath(parent, desiredName, force) {
2760
2829
  if (action === "use-alt" && alternative) {
2761
2830
  return alternative;
2762
2831
  }
2763
- const newName = await input3({
2832
+ const newName = await input4({
2764
2833
  message: "T\xEAn workspace m\u1EDBi:",
2765
2834
  validate: (v) => v.trim().length > 0 ? true : "T\xEAn kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng"
2766
2835
  });
@@ -2770,7 +2839,7 @@ async function resolveWorkspacePath(parent, desiredName, force) {
2770
2839
  }
2771
2840
  }
2772
2841
  async function promptTeamOwner(currentUserEmail) {
2773
- return await input3({ message: "Team owner email:", default: currentUserEmail });
2842
+ return await input4({ message: "Team owner email:", default: currentUserEmail });
2774
2843
  }
2775
2844
  async function maybeCommitWorkspace(workspacePath, skipCommit) {
2776
2845
  if (skipCommit) {
@@ -2806,7 +2875,7 @@ function printInitSuccessBox(rootPath, flow, aiResult = null) {
2806
2875
  ` ${chalk.cyan("avatar sync")} Pull team-ai-pack m\u1EDBi`,
2807
2876
  ` ${chalk.cyan("avatar uninstall")} G\u1EE1 Avatar (gi\u1EEF code)`
2808
2877
  ];
2809
- process.stdout.write(`${boxen3(lines.join("\n"), { padding: 1, borderStyle: "round" })}
2878
+ process.stdout.write(`${boxen4(lines.join("\n"), { padding: 1, borderStyle: "round" })}
2810
2879
  `);
2811
2880
  }
2812
2881
 
@@ -2843,7 +2912,7 @@ function registerSecretsCommand(program2) {
2843
2912
  // src/commands/status.ts
2844
2913
  import { promises as fs8 } from "fs";
2845
2914
  import { join as join18 } from "path";
2846
- import boxen4 from "boxen";
2915
+ import boxen5 from "boxen";
2847
2916
 
2848
2917
  // src/lib/pack-backup-manager.ts
2849
2918
  import { promises as fs7 } from "fs";
@@ -2921,7 +2990,7 @@ function renderStatusBox(s) {
2921
2990
  `${chalk.dim("Backups:")} ${s.backupCount}`,
2922
2991
  `${chalk.dim("Tech stack:")} ${s.techStackSummary}`
2923
2992
  ];
2924
- process.stdout.write(`${boxen4(lines.join("\n"), { padding: 1, borderStyle: "round" })}
2993
+ process.stdout.write(`${boxen5(lines.join("\n"), { padding: 1, borderStyle: "round" })}
2925
2994
  `);
2926
2995
  }
2927
2996
 
@@ -2941,7 +3010,7 @@ function registerToolsCommand(program2) {
2941
3010
  // src/commands/uninstall.ts
2942
3011
  import { relative as relative3 } from "path";
2943
3012
  import { confirm as confirm4 } from "@inquirer/prompts";
2944
- import boxen5 from "boxen";
3013
+ import boxen6 from "boxen";
2945
3014
 
2946
3015
  // src/lib/create-uninstall-backup-snapshot.ts
2947
3016
  import { cp, mkdir, writeFile } from "fs/promises";
@@ -3095,7 +3164,7 @@ async function removeSubmoduleEntry(gitmodulesPath, submodulePath) {
3095
3164
  }
3096
3165
 
3097
3166
  // src/commands/uninstall.ts
3098
- var CLI_VERSION = "1.2.5";
3167
+ var CLI_VERSION = "1.2.7";
3099
3168
  function registerUninstallCommand(program2) {
3100
3169
  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) => {
3101
3170
  try {
@@ -3172,12 +3241,12 @@ function printUninstallSuccessBox(backupPath) {
3172
3241
  lines.push(` ${chalk.dim("Backup:")} ${backupPath}`);
3173
3242
  lines.push(` ${chalk.dim("Restore:")} ${chalk.cyan(`cp -r "${backupPath}"/* .`)}`);
3174
3243
  }
3175
- process.stdout.write(`${boxen5(lines.join("\n"), { padding: 1, borderStyle: "round" })}
3244
+ process.stdout.write(`${boxen6(lines.join("\n"), { padding: 1, borderStyle: "round" })}
3176
3245
  `);
3177
3246
  }
3178
3247
 
3179
3248
  // src/index.ts
3180
- var CLI_VERSION2 = "1.2.5";
3249
+ var CLI_VERSION2 = "1.2.7";
3181
3250
  var program = new Command();
3182
3251
  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(
3183
3252
  "beforeAll",