@nalvietnam/avatar-cli 1.2.4 → 1.2.5
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 +405 -258
- package/dist/index.js.map +1 -1
- package/dist/lib/print-welcome-screen.js +1 -1
- package/dist/lib/print-welcome-screen.js.map +1 -1
- package/package.json +1 -1
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,
|
|
549
|
+
async function writeClaudeSettings(workspacePath, input4) {
|
|
550
550
|
const path = getClaudeSettingsPath(workspacePath);
|
|
551
551
|
const existing = await readExistingSettings(path);
|
|
552
552
|
let merged;
|
|
553
|
-
switch (
|
|
553
|
+
switch (input4.provider) {
|
|
554
554
|
case "subscription":
|
|
555
|
-
merged = applySubscription(existing,
|
|
555
|
+
merged = applySubscription(existing, input4.model);
|
|
556
556
|
break;
|
|
557
557
|
case "llmlite":
|
|
558
|
-
merged = applyLLMLite(existing,
|
|
558
|
+
merged = applyLLMLite(existing, input4.apiKey, input4.baseUrl, input4.model);
|
|
559
559
|
break;
|
|
560
560
|
case "use-global":
|
|
561
|
-
merged = applyUseGlobal(existing,
|
|
561
|
+
merged = applyUseGlobal(existing, input4.sourceSettings);
|
|
562
562
|
break;
|
|
563
563
|
}
|
|
564
564
|
await writeJsonAtomic(path, merged, SECRET_FILE_MODE2);
|
|
@@ -1194,9 +1194,199 @@ 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
|
|
1197
|
+
import { confirm as confirm3, input as input3, select as select7 } from "@inquirer/prompts";
|
|
1198
1198
|
import boxen3 from "boxen";
|
|
1199
1199
|
|
|
1200
|
+
// src/lib/prompt-recovery-action-on-failure.ts
|
|
1201
|
+
import { input as input2, select as select3 } from "@inquirer/prompts";
|
|
1202
|
+
var UserAbortedRecoveryError = class extends Error {
|
|
1203
|
+
constructor(message) {
|
|
1204
|
+
super(message);
|
|
1205
|
+
this.name = "UserAbortedRecoveryError";
|
|
1206
|
+
}
|
|
1207
|
+
};
|
|
1208
|
+
async function promptRetryOrSkip(args) {
|
|
1209
|
+
log.warn(`${args.taskName} th\u1EA5t b\u1EA1i: ${args.reason}`);
|
|
1210
|
+
if (args.hint) log.info(args.hint);
|
|
1211
|
+
const choices = [
|
|
1212
|
+
{ name: "Th\u1EED l\u1EA1i (Retry)", value: "retry" }
|
|
1213
|
+
];
|
|
1214
|
+
if (args.allowSkip) {
|
|
1215
|
+
choices.push({ name: "B\u1ECF qua b\u01B0\u1EDBc n\xE0y v\xE0 ti\u1EBFp t\u1EE5c (Skip)", value: "skip" });
|
|
1216
|
+
}
|
|
1217
|
+
choices.push({ name: "T\u1EA1m ng\u01B0ng init \u2014 ch\u1EA1y l\u1EA1i sau (Abort)", value: "abort" });
|
|
1218
|
+
return await select3({
|
|
1219
|
+
message: "C\xE1ch x\u1EED l\xFD?",
|
|
1220
|
+
choices
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// src/lib/team-pack-submodule-manager.ts
|
|
1225
|
+
import { join as join10 } from "path";
|
|
1226
|
+
|
|
1227
|
+
// src/lib/check-team-pack-access-with-retry-loop.ts
|
|
1228
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
1229
|
+
import { confirm as confirm2, select as select4 } from "@inquirer/prompts";
|
|
1230
|
+
function parseRepoSlugFromGitUrl(url) {
|
|
1231
|
+
const httpsMatch = url.match(/github\.com[/:]([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
1232
|
+
if (httpsMatch) return httpsMatch[1];
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
function checkRepoAccess(repoSlug) {
|
|
1236
|
+
const r = spawnSync6("gh", ["api", `repos/${repoSlug}`], { stdio: "ignore" });
|
|
1237
|
+
return r.status === 0;
|
|
1238
|
+
}
|
|
1239
|
+
function getCurrentGhUser() {
|
|
1240
|
+
const r = spawnSync6("gh", ["api", "user", "--jq", ".login"], {
|
|
1241
|
+
encoding: "utf8",
|
|
1242
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1243
|
+
});
|
|
1244
|
+
if (r.status !== 0) return null;
|
|
1245
|
+
return r.stdout.trim() || null;
|
|
1246
|
+
}
|
|
1247
|
+
async function copyInfoToClipboardWithConsent(info) {
|
|
1248
|
+
const ok = await confirm2({
|
|
1249
|
+
message: "Copy th\xF4ng tin (GitHub username + email) v\xE0o clipboard \u0111\u1EC3 d\xE1n v\xE0o Slack/email?",
|
|
1250
|
+
default: true
|
|
1251
|
+
});
|
|
1252
|
+
if (!ok) return;
|
|
1253
|
+
try {
|
|
1254
|
+
const { default: clipboardy } = await import("clipboardy");
|
|
1255
|
+
await clipboardy.write(info);
|
|
1256
|
+
log.success("\u0110\xE3 copy v\xE0o clipboard");
|
|
1257
|
+
} catch (err) {
|
|
1258
|
+
log.dim(`Copy clipboard fail: ${err.message}`);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
function buildAccessRequestInfo(ghUser, ssoEmail) {
|
|
1262
|
+
const lines = [
|
|
1263
|
+
"Request access team-ai-pack (NAL)",
|
|
1264
|
+
"",
|
|
1265
|
+
`GitHub username: ${ghUser ?? "(ch\u01B0a gh auth \u2014 ch\u1EA1y: gh auth login)"}`,
|
|
1266
|
+
`NAL email: ${ssoEmail ?? "(ch\u01B0a avatar login \u2014 ch\u1EA1y: avatar login)"}`,
|
|
1267
|
+
`Date: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`
|
|
1268
|
+
];
|
|
1269
|
+
return lines.join("\n");
|
|
1270
|
+
}
|
|
1271
|
+
async function ensureTeamPackAccessWithRetry(args) {
|
|
1272
|
+
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);
|
|
1281
|
+
while (true) {
|
|
1282
|
+
const action = await select4({
|
|
1283
|
+
message: "Ti\u1EBFp t\u1EE5c?",
|
|
1284
|
+
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" }
|
|
1287
|
+
]
|
|
1288
|
+
});
|
|
1289
|
+
if (action === "abort") {
|
|
1290
|
+
log.dim("T\u1EA1m ng\u01B0ng. Ch\u1EA1y l\u1EA1i 'avatar init' sau khi \u0111\xE3 accept invite t\u1EEB GitHub.");
|
|
1291
|
+
return false;
|
|
1292
|
+
}
|
|
1293
|
+
log.info("Ki\u1EC3m tra access...");
|
|
1294
|
+
if (checkRepoAccess(args.repoSlug)) {
|
|
1295
|
+
log.success("\u0110\xE3 c\xF3 access \u2014 ti\u1EBFp t\u1EE5c.");
|
|
1296
|
+
return true;
|
|
1297
|
+
}
|
|
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).");
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// src/lib/resolve-team-pack-repo-url.ts
|
|
1303
|
+
var ORG_DEFAULT = "https://github.com/nalvn/team-ai-pack.git";
|
|
1304
|
+
function resolveTeamPackRepoUrl() {
|
|
1305
|
+
if (process.env.AVATAR_TEAM_PACK_REPO_URL) {
|
|
1306
|
+
return process.env.AVATAR_TEAM_PACK_REPO_URL;
|
|
1307
|
+
}
|
|
1308
|
+
return ORG_DEFAULT;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// src/lib/team-pack-submodule-manager.ts
|
|
1312
|
+
var TEAM_PACK_REPO_URL = resolveTeamPackRepoUrl();
|
|
1313
|
+
var TEAM_PACK_RELATIVE_PATH = ".claude/pack";
|
|
1314
|
+
var TeamPackAccessAbortedError = class extends Error {
|
|
1315
|
+
constructor(message) {
|
|
1316
|
+
super(message);
|
|
1317
|
+
this.name = "TeamPackAccessAbortedError";
|
|
1318
|
+
}
|
|
1319
|
+
};
|
|
1320
|
+
async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
|
|
1321
|
+
const url = resolveTeamPackRepoUrl();
|
|
1322
|
+
const repoSlug = parseRepoSlugFromGitUrl(url);
|
|
1323
|
+
if (repoSlug) {
|
|
1324
|
+
const hasAccess = await ensureTeamPackAccessWithRetry({ repoSlug, ssoEmail });
|
|
1325
|
+
if (!hasAccess) {
|
|
1326
|
+
throw new TeamPackAccessAbortedError(
|
|
1327
|
+
"User ch\u1ECDn t\u1EA1m ng\u01B0ng. Ch\u1EA1y l\u1EA1i 'avatar init' sau khi \u0111\u01B0\u1EE3c add v\xE0o org."
|
|
1328
|
+
);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
try {
|
|
1332
|
+
await addSubmodule(url, TEAM_PACK_RELATIVE_PATH, projectRoot);
|
|
1333
|
+
} catch (err) {
|
|
1334
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1335
|
+
if (msg.includes("Repository not found") || msg.includes("not found")) {
|
|
1336
|
+
log.error(
|
|
1337
|
+
`Repo team-ai-pack kh\xF4ng t\u1ED3n t\u1EA1i: ${url}
|
|
1338
|
+
C\xE1ch fix:
|
|
1339
|
+
1. T\u1EA1o repo: gh repo create <owner>/team-ai-pack --private --add-readme
|
|
1340
|
+
2. Ho\u1EB7c override URL: export AVATAR_TEAM_PACK_REPO_URL=<url-repo-c\u1EE7a-b\u1EA1n>
|
|
1341
|
+
3. Ho\u1EB7c d\xF9ng flag --skip-team-pack`
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
throw err;
|
|
1345
|
+
}
|
|
1346
|
+
let target = tag ?? null;
|
|
1347
|
+
if (!target) {
|
|
1348
|
+
target = await latestTag(join10(projectRoot, TEAM_PACK_RELATIVE_PATH));
|
|
1349
|
+
}
|
|
1350
|
+
if (target) {
|
|
1351
|
+
await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, target, projectRoot);
|
|
1352
|
+
}
|
|
1353
|
+
return { pinnedTag: target };
|
|
1354
|
+
}
|
|
1355
|
+
async function readPinnedPackVersion(projectRoot) {
|
|
1356
|
+
const submoduleRoot = join10(projectRoot, TEAM_PACK_RELATIVE_PATH);
|
|
1357
|
+
const tag = await latestTag(submoduleRoot);
|
|
1358
|
+
if (tag) return tag;
|
|
1359
|
+
const sha = await currentCommitSha(submoduleRoot);
|
|
1360
|
+
return sha.slice(0, 7);
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// src/lib/add-team-pack-submodule-with-retry-on-network-fail.ts
|
|
1364
|
+
async function addTeamPackSubmoduleWithRetryOnNetworkFail(projectRoot, tag) {
|
|
1365
|
+
while (true) {
|
|
1366
|
+
try {
|
|
1367
|
+
const result = await addTeamPackSubmodule(projectRoot, tag);
|
|
1368
|
+
return { pinnedTag: result.pinnedTag, skipped: false };
|
|
1369
|
+
} catch (err) {
|
|
1370
|
+
if (err instanceof TeamPackAccessAbortedError) throw err;
|
|
1371
|
+
const action = await promptRetryOrSkip({
|
|
1372
|
+
taskName: "Pull team-ai-pack submodule",
|
|
1373
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
1374
|
+
allowSkip: true,
|
|
1375
|
+
hint: "Network glitch? Retry th\u01B0\u1EDDng work. N\u1EBFu skip, d\xF9ng `avatar sync` sau \u0111\u1EC3 pull pack."
|
|
1376
|
+
});
|
|
1377
|
+
if (action === "abort") {
|
|
1378
|
+
throw new UserAbortedRecoveryError("User abort t\u1EA1i b\u01B0\u1EDBc pull team-ai-pack.");
|
|
1379
|
+
}
|
|
1380
|
+
if (action === "skip") {
|
|
1381
|
+
log.warn(
|
|
1382
|
+
"Skip team-ai-pack. Workspace d\xF9ng \u0111\u01B0\u1EE3c nh\u01B0ng kh\xF4ng c\xF3 shared knowledge. Pull sau qua `avatar sync`."
|
|
1383
|
+
);
|
|
1384
|
+
return { pinnedTag: null, skipped: true };
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1200
1390
|
// src/lib/avatar-ascii-banner.ts
|
|
1201
1391
|
import chalk2 from "chalk";
|
|
1202
1392
|
var BANNER_LINES = [
|
|
@@ -1259,27 +1449,27 @@ ${renderAvatarBanner(opts)}
|
|
|
1259
1449
|
}
|
|
1260
1450
|
|
|
1261
1451
|
// src/lib/execute-gh-repo-create.ts
|
|
1262
|
-
import { spawnSync as
|
|
1452
|
+
import { spawnSync as spawnSync7 } from "child_process";
|
|
1263
1453
|
var RepoAlreadyExistsError = class extends Error {
|
|
1264
1454
|
constructor(fullName) {
|
|
1265
1455
|
super(`Repo "${fullName}" \u0111\xE3 t\u1ED3n t\u1EA1i tr\xEAn GitHub. \u0110\u1ED5i t\xEAn ho\u1EB7c x\xF3a repo c\u0169.`);
|
|
1266
1456
|
this.name = "RepoAlreadyExistsError";
|
|
1267
1457
|
}
|
|
1268
1458
|
};
|
|
1269
|
-
function executeGhRepoCreate(
|
|
1270
|
-
const fullName = `${
|
|
1459
|
+
function executeGhRepoCreate(input4) {
|
|
1460
|
+
const fullName = `${input4.org}/${input4.name}`;
|
|
1271
1461
|
const args = [
|
|
1272
1462
|
"repo",
|
|
1273
1463
|
"create",
|
|
1274
1464
|
fullName,
|
|
1275
|
-
`--${
|
|
1465
|
+
`--${input4.visibility}`,
|
|
1276
1466
|
"--source",
|
|
1277
|
-
|
|
1467
|
+
input4.folder,
|
|
1278
1468
|
"--remote",
|
|
1279
1469
|
"origin",
|
|
1280
1470
|
"--push"
|
|
1281
1471
|
];
|
|
1282
|
-
const r =
|
|
1472
|
+
const r = spawnSync7("gh", args, { stdio: "inherit" });
|
|
1283
1473
|
if (r.status !== 0) {
|
|
1284
1474
|
if (r.status === 1) {
|
|
1285
1475
|
throw new RepoAlreadyExistsError(fullName);
|
|
@@ -1293,9 +1483,9 @@ function executeGhRepoCreate(input3) {
|
|
|
1293
1483
|
}
|
|
1294
1484
|
|
|
1295
1485
|
// src/lib/resolve-github-username-default.ts
|
|
1296
|
-
import { spawnSync as
|
|
1486
|
+
import { spawnSync as spawnSync8 } from "child_process";
|
|
1297
1487
|
function resolveGithubUsernameDefault() {
|
|
1298
|
-
const r =
|
|
1488
|
+
const r = spawnSync8("gh", ["api", "user", "--jq", ".login"], {
|
|
1299
1489
|
encoding: "utf8",
|
|
1300
1490
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1301
1491
|
});
|
|
@@ -1327,28 +1517,28 @@ function validateRepoVisibility(v) {
|
|
|
1327
1517
|
}
|
|
1328
1518
|
|
|
1329
1519
|
// src/lib/create-github-remote-from-folder.ts
|
|
1330
|
-
function createGithubRemoteFromFolder(
|
|
1331
|
-
validateRepoName(
|
|
1332
|
-
validateRepoVisibility(
|
|
1333
|
-
const org =
|
|
1334
|
-
log.info(`T\u1EA1o GitHub repo ${org}/${
|
|
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})...`);
|
|
1335
1525
|
const urls = executeGhRepoCreate({
|
|
1336
|
-
folder:
|
|
1526
|
+
folder: input4.folder,
|
|
1337
1527
|
org,
|
|
1338
|
-
name:
|
|
1339
|
-
visibility:
|
|
1528
|
+
name: input4.name,
|
|
1529
|
+
visibility: input4.visibility
|
|
1340
1530
|
});
|
|
1341
1531
|
log.success(`\u0110\xE3 t\u1EA1o: ${urls.sshUrl}`);
|
|
1342
1532
|
return urls;
|
|
1343
1533
|
}
|
|
1344
1534
|
|
|
1345
1535
|
// src/lib/create-workspace-remote-via-gh.ts
|
|
1346
|
-
import { spawnSync as
|
|
1536
|
+
import { spawnSync as spawnSync16 } from "child_process";
|
|
1347
1537
|
|
|
1348
1538
|
// src/lib/check-gh-cli-auth-status.ts
|
|
1349
|
-
import { spawnSync as
|
|
1539
|
+
import { spawnSync as spawnSync9 } from "child_process";
|
|
1350
1540
|
function checkGhCliAuthStatus() {
|
|
1351
|
-
const r =
|
|
1541
|
+
const r = spawnSync9("gh", ["auth", "status"], { stdio: "ignore" });
|
|
1352
1542
|
if (r.error && r.error.code === "ENOENT") {
|
|
1353
1543
|
return "not-installed";
|
|
1354
1544
|
}
|
|
@@ -1356,12 +1546,12 @@ function checkGhCliAuthStatus() {
|
|
|
1356
1546
|
}
|
|
1357
1547
|
|
|
1358
1548
|
// src/lib/detect-package-manager.ts
|
|
1359
|
-
import { spawnSync as
|
|
1549
|
+
import { spawnSync as spawnSync10 } from "child_process";
|
|
1360
1550
|
function hasBinary(name) {
|
|
1361
1551
|
const platform2 = detectHostPlatform();
|
|
1362
1552
|
const probe = platform2 === "win32" ? "where" : "command";
|
|
1363
1553
|
const args = platform2 === "win32" ? [name] : ["-v", name];
|
|
1364
|
-
const r =
|
|
1554
|
+
const r = spawnSync10(probe, args, {
|
|
1365
1555
|
shell: platform2 !== "win32",
|
|
1366
1556
|
stdio: "ignore"
|
|
1367
1557
|
});
|
|
@@ -1377,11 +1567,11 @@ function detectPackageManager() {
|
|
|
1377
1567
|
}
|
|
1378
1568
|
|
|
1379
1569
|
// src/lib/handle-remote-access-failure-with-account-switch.ts
|
|
1380
|
-
import { spawnSync as
|
|
1381
|
-
import { select as
|
|
1570
|
+
import { spawnSync as spawnSync12 } from "child_process";
|
|
1571
|
+
import { select as select5 } from "@inquirer/prompts";
|
|
1382
1572
|
|
|
1383
1573
|
// src/lib/verify-git-remote-accessible.ts
|
|
1384
|
-
import { spawnSync as
|
|
1574
|
+
import { spawnSync as spawnSync11 } from "child_process";
|
|
1385
1575
|
var TIMEOUT_MS = 5e3;
|
|
1386
1576
|
function classifyRemoteError(stderr) {
|
|
1387
1577
|
const text = stderr.toLowerCase();
|
|
@@ -1397,7 +1587,7 @@ function classifyRemoteError(stderr) {
|
|
|
1397
1587
|
return "unknown";
|
|
1398
1588
|
}
|
|
1399
1589
|
function tryVerifyGitRemoteAccessible(url) {
|
|
1400
|
-
const r =
|
|
1590
|
+
const r = spawnSync11("git", ["ls-remote", "--exit-code", url, "HEAD"], {
|
|
1401
1591
|
encoding: "utf8",
|
|
1402
1592
|
timeout: TIMEOUT_MS,
|
|
1403
1593
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -1418,8 +1608,8 @@ var RemoteAccessAbortedError = class extends Error {
|
|
|
1418
1608
|
this.name = "RemoteAccessAbortedError";
|
|
1419
1609
|
}
|
|
1420
1610
|
};
|
|
1421
|
-
function
|
|
1422
|
-
const r =
|
|
1611
|
+
function getCurrentGhUser2() {
|
|
1612
|
+
const r = spawnSync12("gh", ["api", "user", "--jq", ".login"], {
|
|
1423
1613
|
encoding: "utf8",
|
|
1424
1614
|
stdio: ["ignore", "pipe", "pipe"]
|
|
1425
1615
|
});
|
|
@@ -1428,7 +1618,7 @@ function getCurrentGhUser() {
|
|
|
1428
1618
|
}
|
|
1429
1619
|
function triggerGhAuthLoginInteractive() {
|
|
1430
1620
|
log.info("M\u1EDF browser \u0111\u1EC3 switch GitHub account...");
|
|
1431
|
-
const r =
|
|
1621
|
+
const r = spawnSync12("gh", ["auth", "login", "--web"], { stdio: "inherit" });
|
|
1432
1622
|
if (r.status !== 0) {
|
|
1433
1623
|
log.warn(`gh auth login exit ${r.status}. B\u1EA1n c\xF3 th\u1EC3 ch\u1EA1y tay r\u1ED3i quay l\u1EA1i retry.`);
|
|
1434
1624
|
}
|
|
@@ -1451,12 +1641,12 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
|
|
|
1451
1641
|
let reason = args.initialReason;
|
|
1452
1642
|
let detail = args.initialDetail;
|
|
1453
1643
|
while (true) {
|
|
1454
|
-
const ghUser =
|
|
1644
|
+
const ghUser = getCurrentGhUser2();
|
|
1455
1645
|
log.warn(`Kh\xF4ng truy c\u1EADp \u0111\u01B0\u1EE3c ${args.url}`);
|
|
1456
1646
|
log.dim(` L\xFD do: ${reason}${detail ? ` \u2014 ${detail.slice(0, 150)}` : ""}`);
|
|
1457
1647
|
log.info(getReasonHint(reason, args.url, ghUser));
|
|
1458
1648
|
if (ghUser) log.dim(` gh CLI hi\u1EC7n \u0111ang login: ${ghUser}`);
|
|
1459
|
-
const action = await
|
|
1649
|
+
const action = await select5({
|
|
1460
1650
|
message: "C\xE1ch x\u1EED l\xFD?",
|
|
1461
1651
|
choices: [
|
|
1462
1652
|
{
|
|
@@ -1493,7 +1683,7 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
|
|
|
1493
1683
|
}
|
|
1494
1684
|
|
|
1495
1685
|
// src/lib/install-gh-cli-via-package-manager.ts
|
|
1496
|
-
import { spawnSync as
|
|
1686
|
+
import { spawnSync as spawnSync13 } from "child_process";
|
|
1497
1687
|
var INSTALL_COMMANDS = {
|
|
1498
1688
|
brew: { cmd: "brew", args: ["install", "gh"] },
|
|
1499
1689
|
apt: { cmd: "sudo", args: ["apt-get", "install", "-y", "gh"] },
|
|
@@ -1504,7 +1694,7 @@ var INSTALL_COMMANDS = {
|
|
|
1504
1694
|
function installGhCliViaPackageManager(pm) {
|
|
1505
1695
|
const spec = INSTALL_COMMANDS[pm];
|
|
1506
1696
|
log.info(`\u0110ang c\xE0i gh CLI qua ${pm}...`);
|
|
1507
|
-
const r =
|
|
1697
|
+
const r = spawnSync13(spec.cmd, spec.args, { stdio: "inherit" });
|
|
1508
1698
|
if (r.status !== 0) {
|
|
1509
1699
|
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.`);
|
|
1510
1700
|
}
|
|
@@ -1512,9 +1702,9 @@ function installGhCliViaPackageManager(pm) {
|
|
|
1512
1702
|
}
|
|
1513
1703
|
|
|
1514
1704
|
// src/lib/setup-git-credential-via-gh.ts
|
|
1515
|
-
import { spawnSync as
|
|
1705
|
+
import { spawnSync as spawnSync14 } from "child_process";
|
|
1516
1706
|
function setupGitCredentialViaGh() {
|
|
1517
|
-
const r =
|
|
1707
|
+
const r = spawnSync14("gh", ["auth", "setup-git"], { stdio: "ignore" });
|
|
1518
1708
|
if (r.status !== 0) {
|
|
1519
1709
|
log.warn("gh auth setup-git fail (non-fatal). N\u1EBFu git clone l\u1ED7i 128 \u2192 ch\u1EA1y th\u1EE7 c\xF4ng.");
|
|
1520
1710
|
return;
|
|
@@ -1523,10 +1713,10 @@ function setupGitCredentialViaGh() {
|
|
|
1523
1713
|
}
|
|
1524
1714
|
|
|
1525
1715
|
// src/lib/trigger-gh-cli-auth-login.ts
|
|
1526
|
-
import { spawnSync as
|
|
1716
|
+
import { spawnSync as spawnSync15 } from "child_process";
|
|
1527
1717
|
function triggerGhCliAuthLogin() {
|
|
1528
1718
|
log.info("Kh\u1EDFi \u0111\u1ED9ng \u0111\u0103ng nh\u1EADp GitHub qua gh CLI (browser s\u1EBD m\u1EDF)...");
|
|
1529
|
-
const r =
|
|
1719
|
+
const r = spawnSync15(
|
|
1530
1720
|
"gh",
|
|
1531
1721
|
["auth", "login", "--hostname", "github.com", "--web", "--git-protocol", "ssh"],
|
|
1532
1722
|
{ stdio: "inherit" }
|
|
@@ -1539,24 +1729,61 @@ function triggerGhCliAuthLogin() {
|
|
|
1539
1729
|
|
|
1540
1730
|
// src/lib/git-auth-and-install-orchestrator.ts
|
|
1541
1731
|
async function ensureGitHubReady(remoteUrl) {
|
|
1542
|
-
|
|
1543
|
-
if (state === "not-installed") {
|
|
1732
|
+
while (checkGhCliAuthStatus() === "not-installed") {
|
|
1544
1733
|
log.warn("gh CLI ch\u01B0a c\xE0i. Avatar s\u1EBD t\u1EF1 c\xE0i.");
|
|
1545
1734
|
const pm = detectPackageManager();
|
|
1546
1735
|
if (!pm) {
|
|
1547
|
-
|
|
1548
|
-
"
|
|
1549
|
-
|
|
1736
|
+
const action = await promptRetryOrSkip({
|
|
1737
|
+
taskName: "Ph\xE1t hi\u1EC7n package manager",
|
|
1738
|
+
reason: "Kh\xF4ng t\xECm th\u1EA5y brew/apt/dnf/pacman/winget tr\xEAn m\xE1y.",
|
|
1739
|
+
allowSkip: false,
|
|
1740
|
+
hint: "C\xE0i gh CLI tay (https://cli.github.com) r\u1ED3i ch\u1ECDn Retry."
|
|
1741
|
+
});
|
|
1742
|
+
if (action === "abort") {
|
|
1743
|
+
throw new UserAbortedRecoveryError("User abort t\u1EA1i b\u01B0\u1EDBc c\xE0i gh CLI.");
|
|
1744
|
+
}
|
|
1745
|
+
continue;
|
|
1746
|
+
}
|
|
1747
|
+
try {
|
|
1748
|
+
installGhCliViaPackageManager(pm);
|
|
1749
|
+
} catch (err) {
|
|
1750
|
+
const action = await promptRetryOrSkip({
|
|
1751
|
+
taskName: `C\xE0i gh CLI qua ${pm}`,
|
|
1752
|
+
reason: err.message,
|
|
1753
|
+
allowSkip: false,
|
|
1754
|
+
hint: "C\xE0i tay (https://cli.github.com) r\u1ED3i ch\u1ECDn Retry, ho\u1EB7c Abort."
|
|
1755
|
+
});
|
|
1756
|
+
if (action === "abort") {
|
|
1757
|
+
throw new UserAbortedRecoveryError("User abort t\u1EA1i b\u01B0\u1EDBc c\xE0i gh CLI.");
|
|
1758
|
+
}
|
|
1550
1759
|
}
|
|
1551
|
-
installGhCliViaPackageManager(pm);
|
|
1552
|
-
state = checkGhCliAuthStatus();
|
|
1553
1760
|
}
|
|
1554
|
-
|
|
1761
|
+
while (checkGhCliAuthStatus() === "not-authenticated") {
|
|
1555
1762
|
log.warn("Ch\u01B0a \u0111\u0103ng nh\u1EADp GitHub.");
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1763
|
+
try {
|
|
1764
|
+
triggerGhCliAuthLogin();
|
|
1765
|
+
} catch (err) {
|
|
1766
|
+
const action = await promptRetryOrSkip({
|
|
1767
|
+
taskName: "\u0110\u0103ng nh\u1EADp GitHub qua gh",
|
|
1768
|
+
reason: err.message,
|
|
1769
|
+
allowSkip: false,
|
|
1770
|
+
hint: "Th\u1EED l\u1EA1i \u2014 ch\u1ECDn c\xE1ch login kh\xE1c (browser vs token) khi gh prompt."
|
|
1771
|
+
});
|
|
1772
|
+
if (action === "abort") {
|
|
1773
|
+
throw new UserAbortedRecoveryError("User abort t\u1EA1i b\u01B0\u1EDBc gh auth login.");
|
|
1774
|
+
}
|
|
1775
|
+
continue;
|
|
1776
|
+
}
|
|
1777
|
+
if (checkGhCliAuthStatus() !== "authenticated") {
|
|
1778
|
+
const action = await promptRetryOrSkip({
|
|
1779
|
+
taskName: "Verify gh auth",
|
|
1780
|
+
reason: "Sau gh auth login v\u1EABn b\xE1o not-authenticated.",
|
|
1781
|
+
allowSkip: false,
|
|
1782
|
+
hint: "C\xF3 th\u1EC3 user cancel browser. Th\u1EED l\u1EA1i ho\u1EB7c abort."
|
|
1783
|
+
});
|
|
1784
|
+
if (action === "abort") {
|
|
1785
|
+
throw new UserAbortedRecoveryError("User abort t\u1EA1i b\u01B0\u1EDBc verify gh auth.");
|
|
1786
|
+
}
|
|
1560
1787
|
}
|
|
1561
1788
|
}
|
|
1562
1789
|
log.success("gh CLI s\u1EB5n s\xE0ng");
|
|
@@ -1578,13 +1805,13 @@ async function ensureGitHubReady(remoteUrl) {
|
|
|
1578
1805
|
// src/lib/create-workspace-remote-via-gh.ts
|
|
1579
1806
|
function canCreateInNamespace(org, ghUser) {
|
|
1580
1807
|
if (org.toLowerCase() === ghUser.toLowerCase()) return { ok: true };
|
|
1581
|
-
const r =
|
|
1808
|
+
const r = spawnSync16("gh", ["api", `orgs/${org}/members/${ghUser}`, "--silent"], {
|
|
1582
1809
|
stdio: "ignore"
|
|
1583
1810
|
});
|
|
1584
1811
|
if (r.status === 0) return { ok: true };
|
|
1585
|
-
const orgCheck =
|
|
1812
|
+
const orgCheck = spawnSync16("gh", ["api", `orgs/${org}`, "--silent"], { stdio: "ignore" });
|
|
1586
1813
|
if (orgCheck.status !== 0) {
|
|
1587
|
-
const userCheck =
|
|
1814
|
+
const userCheck = spawnSync16("gh", ["api", `users/${org}`, "--silent"], { stdio: "ignore" });
|
|
1588
1815
|
if (userCheck.status === 0) {
|
|
1589
1816
|
return {
|
|
1590
1817
|
ok: false,
|
|
@@ -1601,27 +1828,27 @@ function canCreateInNamespace(org, ghUser) {
|
|
|
1601
1828
|
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.`
|
|
1602
1829
|
};
|
|
1603
1830
|
}
|
|
1604
|
-
async function createWorkspaceRemoteViaGh(
|
|
1605
|
-
validateRepoName(
|
|
1606
|
-
validateRepoVisibility(
|
|
1831
|
+
async function createWorkspaceRemoteViaGh(input4) {
|
|
1832
|
+
validateRepoName(input4.workspaceName);
|
|
1833
|
+
validateRepoVisibility(input4.visibility);
|
|
1607
1834
|
await ensureGitHubReady();
|
|
1608
1835
|
const ghUser = resolveGithubUsernameDefault();
|
|
1609
|
-
const org =
|
|
1836
|
+
const org = input4.org ?? ghUser;
|
|
1610
1837
|
const namespaceCheck = canCreateInNamespace(org, ghUser);
|
|
1611
1838
|
if (!namespaceCheck.ok) {
|
|
1612
1839
|
throw new Error(`Kh\xF4ng th\u1EC3 t\u1EA1o repo d\u01B0\u1EDBi '${org}/': ${namespaceCheck.reason}`);
|
|
1613
1840
|
}
|
|
1614
|
-
const fullName = `${org}/${
|
|
1615
|
-
log.info(`T\u1EA1o GitHub repo cho workspace: ${fullName} (${
|
|
1616
|
-
const r =
|
|
1841
|
+
const fullName = `${org}/${input4.workspaceName}`;
|
|
1842
|
+
log.info(`T\u1EA1o GitHub repo cho workspace: ${fullName} (${input4.visibility})...`);
|
|
1843
|
+
const r = spawnSync16(
|
|
1617
1844
|
"gh",
|
|
1618
1845
|
[
|
|
1619
1846
|
"repo",
|
|
1620
1847
|
"create",
|
|
1621
1848
|
fullName,
|
|
1622
|
-
`--${
|
|
1849
|
+
`--${input4.visibility}`,
|
|
1623
1850
|
"--source",
|
|
1624
|
-
|
|
1851
|
+
input4.workspacePath,
|
|
1625
1852
|
"--remote",
|
|
1626
1853
|
"origin",
|
|
1627
1854
|
"--push"
|
|
@@ -1633,7 +1860,7 @@ async function createWorkspaceRemoteViaGh(input3) {
|
|
|
1633
1860
|
if (stderr) process.stderr.write(`${stderr}
|
|
1634
1861
|
`);
|
|
1635
1862
|
throw new Error(
|
|
1636
|
-
`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} --${
|
|
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`
|
|
1637
1864
|
);
|
|
1638
1865
|
}
|
|
1639
1866
|
const sshUrl = `git@github.com:${fullName}.git`;
|
|
@@ -1644,14 +1871,14 @@ async function createWorkspaceRemoteViaGh(input3) {
|
|
|
1644
1871
|
|
|
1645
1872
|
// src/lib/safe-bootstrap-for-dirty-folder.ts
|
|
1646
1873
|
import { readdirSync } from "fs";
|
|
1647
|
-
import { select as
|
|
1874
|
+
import { select as select6 } from "@inquirer/prompts";
|
|
1648
1875
|
import { simpleGit as simpleGit3 } from "simple-git";
|
|
1649
1876
|
|
|
1650
1877
|
// src/lib/check-folder-has-git.ts
|
|
1651
1878
|
import { existsSync as existsSync2, statSync } from "fs";
|
|
1652
|
-
import { join as
|
|
1879
|
+
import { join as join11 } from "path";
|
|
1653
1880
|
function checkFolderHasGit(folderPath) {
|
|
1654
|
-
const gitPath =
|
|
1881
|
+
const gitPath = join11(folderPath, ".git");
|
|
1655
1882
|
if (!existsSync2(gitPath)) return false;
|
|
1656
1883
|
const stat = statSync(gitPath);
|
|
1657
1884
|
return stat.isDirectory() || stat.isFile();
|
|
@@ -1683,7 +1910,7 @@ async function createInitialGitCommit(folderPath) {
|
|
|
1683
1910
|
|
|
1684
1911
|
// src/lib/detect-folder-tech-stack.ts
|
|
1685
1912
|
import { existsSync as existsSync3 } from "fs";
|
|
1686
|
-
import { join as
|
|
1913
|
+
import { join as join12 } from "path";
|
|
1687
1914
|
var SIGNATURES = {
|
|
1688
1915
|
node: ["package.json"],
|
|
1689
1916
|
python: ["pyproject.toml", "requirements.txt", "setup.py", "Pipfile"],
|
|
@@ -1695,7 +1922,7 @@ var SIGNATURES = {
|
|
|
1695
1922
|
function detectFolderTechStack(folderPath) {
|
|
1696
1923
|
const matched = [];
|
|
1697
1924
|
for (const [stack, files] of Object.entries(SIGNATURES)) {
|
|
1698
|
-
if (files.some((f) => existsSync3(
|
|
1925
|
+
if (files.some((f) => existsSync3(join12(folderPath, f)))) {
|
|
1699
1926
|
matched.push(stack);
|
|
1700
1927
|
}
|
|
1701
1928
|
}
|
|
@@ -1704,19 +1931,19 @@ function detectFolderTechStack(folderPath) {
|
|
|
1704
1931
|
|
|
1705
1932
|
// src/lib/gitignore-template-loader.ts
|
|
1706
1933
|
import { readFileSync as readFileSync2 } from "fs";
|
|
1707
|
-
import { dirname as dirname3, join as
|
|
1934
|
+
import { dirname as dirname3, join as join13 } from "path";
|
|
1708
1935
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1709
1936
|
var __dirname = dirname3(fileURLToPath2(import.meta.url));
|
|
1710
1937
|
var CANDIDATE_DIRS = [
|
|
1711
|
-
|
|
1712
|
-
|
|
1938
|
+
join13(__dirname, "..", "templates", "gitignore"),
|
|
1939
|
+
join13(__dirname, "..", "..", "src", "templates", "gitignore")
|
|
1713
1940
|
];
|
|
1714
1941
|
var AVATAR_MARKER_START = "# === avatar ===";
|
|
1715
1942
|
var AVATAR_MARKER_END = "# === /avatar ===";
|
|
1716
1943
|
function readTemplate(stack) {
|
|
1717
1944
|
for (const dir of CANDIDATE_DIRS) {
|
|
1718
1945
|
try {
|
|
1719
|
-
return readFileSync2(
|
|
1946
|
+
return readFileSync2(join13(dir, `${stack}.txt`), "utf8");
|
|
1720
1947
|
} catch {
|
|
1721
1948
|
}
|
|
1722
1949
|
}
|
|
@@ -1731,9 +1958,9 @@ ${readTemplate(s).trim()}`);
|
|
|
1731
1958
|
|
|
1732
1959
|
// src/lib/write-or-merge-gitignore.ts
|
|
1733
1960
|
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
1734
|
-
import { join as
|
|
1961
|
+
import { join as join14 } from "path";
|
|
1735
1962
|
function writeOrMergeGitignore(folderPath, avatarBlock) {
|
|
1736
|
-
const path =
|
|
1963
|
+
const path = join14(folderPath, ".gitignore");
|
|
1737
1964
|
if (!existsSync4(path)) {
|
|
1738
1965
|
writeFileSync(path, avatarBlock, "utf8");
|
|
1739
1966
|
return;
|
|
@@ -1775,7 +2002,7 @@ async function promptBootstrapStrategy(state, opts) {
|
|
|
1775
2002
|
if (opts.presetStrategy) return opts.presetStrategy;
|
|
1776
2003
|
if (opts.autoYes) return "stash";
|
|
1777
2004
|
if (state === "empty" || state === "clean") return "commit-all";
|
|
1778
|
-
return await
|
|
2005
|
+
return await select6({
|
|
1779
2006
|
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:",
|
|
1780
2007
|
choices: [
|
|
1781
2008
|
{
|
|
@@ -1908,145 +2135,6 @@ async function safeBootstrapGitInFolder(folderPath, opts = {}) {
|
|
|
1908
2135
|
await appendAuditEntry("bootstrap", `state=${state},strategy=${strategy}`);
|
|
1909
2136
|
}
|
|
1910
2137
|
|
|
1911
|
-
// src/lib/team-pack-submodule-manager.ts
|
|
1912
|
-
import { join as join14 } from "path";
|
|
1913
|
-
|
|
1914
|
-
// src/lib/check-team-pack-access-with-retry-loop.ts
|
|
1915
|
-
import { spawnSync as spawnSync16 } from "child_process";
|
|
1916
|
-
import { confirm as confirm2, select as select5 } from "@inquirer/prompts";
|
|
1917
|
-
function parseRepoSlugFromGitUrl(url) {
|
|
1918
|
-
const httpsMatch = url.match(/github\.com[/:]([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
1919
|
-
if (httpsMatch) return httpsMatch[1];
|
|
1920
|
-
return null;
|
|
1921
|
-
}
|
|
1922
|
-
function checkRepoAccess(repoSlug) {
|
|
1923
|
-
const r = spawnSync16("gh", ["api", `repos/${repoSlug}`], { stdio: "ignore" });
|
|
1924
|
-
return r.status === 0;
|
|
1925
|
-
}
|
|
1926
|
-
function getCurrentGhUser2() {
|
|
1927
|
-
const r = spawnSync16("gh", ["api", "user", "--jq", ".login"], {
|
|
1928
|
-
encoding: "utf8",
|
|
1929
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1930
|
-
});
|
|
1931
|
-
if (r.status !== 0) return null;
|
|
1932
|
-
return r.stdout.trim() || null;
|
|
1933
|
-
}
|
|
1934
|
-
async function copyInfoToClipboardWithConsent(info) {
|
|
1935
|
-
const ok = await confirm2({
|
|
1936
|
-
message: "Copy th\xF4ng tin (GitHub username + email) v\xE0o clipboard \u0111\u1EC3 d\xE1n v\xE0o Slack/email?",
|
|
1937
|
-
default: true
|
|
1938
|
-
});
|
|
1939
|
-
if (!ok) return;
|
|
1940
|
-
try {
|
|
1941
|
-
const { default: clipboardy } = await import("clipboardy");
|
|
1942
|
-
await clipboardy.write(info);
|
|
1943
|
-
log.success("\u0110\xE3 copy v\xE0o clipboard");
|
|
1944
|
-
} catch (err) {
|
|
1945
|
-
log.dim(`Copy clipboard fail: ${err.message}`);
|
|
1946
|
-
}
|
|
1947
|
-
}
|
|
1948
|
-
function buildAccessRequestInfo(ghUser, ssoEmail) {
|
|
1949
|
-
const lines = [
|
|
1950
|
-
"Request access team-ai-pack (NAL)",
|
|
1951
|
-
"",
|
|
1952
|
-
`GitHub username: ${ghUser ?? "(ch\u01B0a gh auth \u2014 ch\u1EA1y: gh auth login)"}`,
|
|
1953
|
-
`NAL email: ${ssoEmail ?? "(ch\u01B0a avatar login \u2014 ch\u1EA1y: avatar login)"}`,
|
|
1954
|
-
`Date: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`
|
|
1955
|
-
];
|
|
1956
|
-
return lines.join("\n");
|
|
1957
|
-
}
|
|
1958
|
-
async function ensureTeamPackAccessWithRetry(args) {
|
|
1959
|
-
if (checkRepoAccess(args.repoSlug)) return true;
|
|
1960
|
-
const ghUser = getCurrentGhUser2();
|
|
1961
|
-
const info = buildAccessRequestInfo(ghUser, args.ssoEmail ?? null);
|
|
1962
|
-
log.warn(`B\u1EA1n ch\u01B0a c\xF3 quy\u1EC1n access v\xE0o b\u1ED9 package ${args.repoSlug}.`);
|
|
1963
|
-
log.info("Li\xEAn h\u1EC7 admin (Luke @nal.vn) \u0111\u1EC3 \u0111\u01B0\u1EE3c add v\xE0o org nalvn.");
|
|
1964
|
-
log.plain("");
|
|
1965
|
-
log.plain(info);
|
|
1966
|
-
log.plain("");
|
|
1967
|
-
await copyInfoToClipboardWithConsent(info);
|
|
1968
|
-
while (true) {
|
|
1969
|
-
const action = await select5({
|
|
1970
|
-
message: "Ti\u1EBFp t\u1EE5c?",
|
|
1971
|
-
choices: [
|
|
1972
|
-
{ name: "\u0110\xE3 \u0111\u01B0\u1EE3c add \u2014 ki\u1EC3m tra l\u1EA1i v\xE0 ti\u1EBFp t\u1EE5c", value: "retry" },
|
|
1973
|
-
{ name: "T\u1EA1m ng\u01B0ng \u2014 ch\u1EA1y l\u1EA1i 'avatar init' sau", value: "abort" }
|
|
1974
|
-
]
|
|
1975
|
-
});
|
|
1976
|
-
if (action === "abort") {
|
|
1977
|
-
log.dim("T\u1EA1m ng\u01B0ng. Ch\u1EA1y l\u1EA1i 'avatar init' sau khi \u0111\xE3 accept invite t\u1EEB GitHub.");
|
|
1978
|
-
return false;
|
|
1979
|
-
}
|
|
1980
|
-
log.info("Ki\u1EC3m tra access...");
|
|
1981
|
-
if (checkRepoAccess(args.repoSlug)) {
|
|
1982
|
-
log.success("\u0110\xE3 c\xF3 access \u2014 ti\u1EBFp t\u1EE5c.");
|
|
1983
|
-
return true;
|
|
1984
|
-
}
|
|
1985
|
-
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).");
|
|
1986
|
-
}
|
|
1987
|
-
}
|
|
1988
|
-
|
|
1989
|
-
// src/lib/resolve-team-pack-repo-url.ts
|
|
1990
|
-
var ORG_DEFAULT = "https://github.com/nalvn/team-ai-pack.git";
|
|
1991
|
-
function resolveTeamPackRepoUrl() {
|
|
1992
|
-
if (process.env.AVATAR_TEAM_PACK_REPO_URL) {
|
|
1993
|
-
return process.env.AVATAR_TEAM_PACK_REPO_URL;
|
|
1994
|
-
}
|
|
1995
|
-
return ORG_DEFAULT;
|
|
1996
|
-
}
|
|
1997
|
-
|
|
1998
|
-
// src/lib/team-pack-submodule-manager.ts
|
|
1999
|
-
var TEAM_PACK_REPO_URL = resolveTeamPackRepoUrl();
|
|
2000
|
-
var TEAM_PACK_RELATIVE_PATH = ".claude/pack";
|
|
2001
|
-
var TeamPackAccessAbortedError = class extends Error {
|
|
2002
|
-
constructor(message) {
|
|
2003
|
-
super(message);
|
|
2004
|
-
this.name = "TeamPackAccessAbortedError";
|
|
2005
|
-
}
|
|
2006
|
-
};
|
|
2007
|
-
async function addTeamPackSubmodule(projectRoot, tag, ssoEmail) {
|
|
2008
|
-
const url = resolveTeamPackRepoUrl();
|
|
2009
|
-
const repoSlug = parseRepoSlugFromGitUrl(url);
|
|
2010
|
-
if (repoSlug) {
|
|
2011
|
-
const hasAccess = await ensureTeamPackAccessWithRetry({ repoSlug, ssoEmail });
|
|
2012
|
-
if (!hasAccess) {
|
|
2013
|
-
throw new TeamPackAccessAbortedError(
|
|
2014
|
-
"User ch\u1ECDn t\u1EA1m ng\u01B0ng. Ch\u1EA1y l\u1EA1i 'avatar init' sau khi \u0111\u01B0\u1EE3c add v\xE0o org."
|
|
2015
|
-
);
|
|
2016
|
-
}
|
|
2017
|
-
}
|
|
2018
|
-
try {
|
|
2019
|
-
await addSubmodule(url, TEAM_PACK_RELATIVE_PATH, projectRoot);
|
|
2020
|
-
} catch (err) {
|
|
2021
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2022
|
-
if (msg.includes("Repository not found") || msg.includes("not found")) {
|
|
2023
|
-
log.error(
|
|
2024
|
-
`Repo team-ai-pack kh\xF4ng t\u1ED3n t\u1EA1i: ${url}
|
|
2025
|
-
C\xE1ch fix:
|
|
2026
|
-
1. T\u1EA1o repo: gh repo create <owner>/team-ai-pack --private --add-readme
|
|
2027
|
-
2. Ho\u1EB7c override URL: export AVATAR_TEAM_PACK_REPO_URL=<url-repo-c\u1EE7a-b\u1EA1n>
|
|
2028
|
-
3. Ho\u1EB7c d\xF9ng flag --skip-team-pack`
|
|
2029
|
-
);
|
|
2030
|
-
}
|
|
2031
|
-
throw err;
|
|
2032
|
-
}
|
|
2033
|
-
let target = tag ?? null;
|
|
2034
|
-
if (!target) {
|
|
2035
|
-
target = await latestTag(join14(projectRoot, TEAM_PACK_RELATIVE_PATH));
|
|
2036
|
-
}
|
|
2037
|
-
if (target) {
|
|
2038
|
-
await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, target, projectRoot);
|
|
2039
|
-
}
|
|
2040
|
-
return { pinnedTag: target };
|
|
2041
|
-
}
|
|
2042
|
-
async function readPinnedPackVersion(projectRoot) {
|
|
2043
|
-
const submoduleRoot = join14(projectRoot, TEAM_PACK_RELATIVE_PATH);
|
|
2044
|
-
const tag = await latestTag(submoduleRoot);
|
|
2045
|
-
if (tag) return tag;
|
|
2046
|
-
const sha = await currentCommitSha(submoduleRoot);
|
|
2047
|
-
return sha.slice(0, 7);
|
|
2048
|
-
}
|
|
2049
|
-
|
|
2050
2138
|
// src/commands/init-conflict-detection-helpers.ts
|
|
2051
2139
|
import { readdir } from "fs/promises";
|
|
2052
2140
|
import { join as join15 } from "path";
|
|
@@ -2309,6 +2397,10 @@ function registerInitCommand(program2) {
|
|
|
2309
2397
|
log.dim(err.message);
|
|
2310
2398
|
process.exit(0);
|
|
2311
2399
|
}
|
|
2400
|
+
if (err instanceof UserAbortedRecoveryError) {
|
|
2401
|
+
log.dim(err.message);
|
|
2402
|
+
process.exit(0);
|
|
2403
|
+
}
|
|
2312
2404
|
log.error(err instanceof Error ? err.message : String(err));
|
|
2313
2405
|
process.exit(1);
|
|
2314
2406
|
}
|
|
@@ -2320,13 +2412,26 @@ async function runInit(opts) {
|
|
|
2320
2412
|
log.warn("Flag --mode \u0111\xE3 deprecated t\u1EEB v1.1. D\xF9ng --project-status thay th\u1EBF.");
|
|
2321
2413
|
}
|
|
2322
2414
|
let userConfig = await readUserConfig();
|
|
2323
|
-
|
|
2415
|
+
while (!userConfig || isTokenExpired(userConfig)) {
|
|
2324
2416
|
log.info("Ch\u01B0a \u0111\u0103ng nh\u1EADp \u2014 ch\u1EA1y login flow tr\u01B0\u1EDBc khi init...");
|
|
2325
|
-
|
|
2417
|
+
try {
|
|
2418
|
+
await runLogin({});
|
|
2419
|
+
} catch (err) {
|
|
2420
|
+
log.warn(`Login fail: ${err.message}`);
|
|
2421
|
+
}
|
|
2326
2422
|
userConfig = await readUserConfig();
|
|
2327
|
-
if (
|
|
2328
|
-
|
|
2329
|
-
|
|
2423
|
+
if (userConfig && !isTokenExpired(userConfig)) break;
|
|
2424
|
+
const action = await promptRetryOrSkip({
|
|
2425
|
+
taskName: "\u0110\u0103ng nh\u1EADp SSO Google",
|
|
2426
|
+
reason: "Token ch\u01B0a \u0111\u01B0\u1EE3c t\u1EA1o ho\u1EB7c \u0111\xE3 h\u1EBFt h\u1EA1n.",
|
|
2427
|
+
allowSkip: false,
|
|
2428
|
+
// Login bắt buộc, không skip được.
|
|
2429
|
+
hint: "\u0110\u1EA3m b\u1EA3o b\u1EA1n ch\u1ECDn 'Allow' tr\xEAn browser v\xE0 d\xF9ng email @nal.vn."
|
|
2430
|
+
});
|
|
2431
|
+
if (action === "abort") {
|
|
2432
|
+
throw new UserAbortedRecoveryError(
|
|
2433
|
+
"User abort t\u1EA1i b\u01B0\u1EDBc login. Ch\u1EA1y 'avatar login' tay r\u1ED3i 'avatar init' l\u1EA1i."
|
|
2434
|
+
);
|
|
2330
2435
|
}
|
|
2331
2436
|
}
|
|
2332
2437
|
const status = opts.projectStatus ?? await promptProjectStatus();
|
|
@@ -2343,7 +2448,7 @@ async function runInit(opts) {
|
|
|
2343
2448
|
}
|
|
2344
2449
|
}
|
|
2345
2450
|
async function promptProjectStatus() {
|
|
2346
|
-
return await
|
|
2451
|
+
return await select7({
|
|
2347
2452
|
message: "T\xECnh tr\u1EA1ng d\u1EF1 \xE1n c\u1EE7a b\u1EA1n?",
|
|
2348
2453
|
choices: [
|
|
2349
2454
|
{ name: "1. \u0110\xE3 c\xF3 repo git remote (URL c\xF3 s\u1EB5n)", value: "existing-remote" },
|
|
@@ -2353,14 +2458,14 @@ async function promptProjectStatus() {
|
|
|
2353
2458
|
});
|
|
2354
2459
|
}
|
|
2355
2460
|
async function runInitFromExistingRemote(opts, ownerEmail) {
|
|
2356
|
-
const remoteUrl = opts.clientRepo ?? await
|
|
2461
|
+
const remoteUrl = opts.clientRepo ?? await input3({
|
|
2357
2462
|
message: "URL git c\u1EE7a repo:",
|
|
2358
2463
|
validate: (v) => v.length > 0 ? true : "URL b\u1EAFt bu\u1ED9c"
|
|
2359
2464
|
});
|
|
2360
2465
|
await ensureGitHubReady(remoteUrl);
|
|
2361
2466
|
const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
|
|
2362
2467
|
const inferredName = inferWorkspaceName(remoteUrl);
|
|
2363
|
-
const workspaceName = opts.workspaceName ?? await
|
|
2468
|
+
const workspaceName = opts.workspaceName ?? await input3({ message: "T\xEAn workspace:", default: inferredName });
|
|
2364
2469
|
const workspaceParent = resolve(opts.workspaceParent ?? ".");
|
|
2365
2470
|
const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);
|
|
2366
2471
|
await scaffoldWorkspaceWithSrcSubmodule({
|
|
@@ -2382,7 +2487,7 @@ async function runInitFromExistingRemote(opts, ownerEmail) {
|
|
|
2382
2487
|
}
|
|
2383
2488
|
async function runInitFromExistingFolder(opts, ownerEmail) {
|
|
2384
2489
|
const folderPath = resolve(
|
|
2385
|
-
opts.folderPath ?? await
|
|
2490
|
+
opts.folderPath ?? await input3({
|
|
2386
2491
|
message: "\u0110\u01B0\u1EDDng d\u1EABn folder hi\u1EC7n c\xF3:",
|
|
2387
2492
|
validate: (v) => v.length > 0 ? true : "Path b\u1EAFt bu\u1ED9c"
|
|
2388
2493
|
})
|
|
@@ -2394,7 +2499,7 @@ async function runInitFromExistingFolder(opts, ownerEmail) {
|
|
|
2394
2499
|
const remoteUrl = await getOrCreateOriginRemote(folderPath, opts);
|
|
2395
2500
|
const teamOwner = opts.teamOwner ?? await promptTeamOwner(ownerEmail);
|
|
2396
2501
|
const inferredName = opts.workspaceName ?? `${basename(folderPath)}-avatar-workspace`;
|
|
2397
|
-
const workspaceName = opts.workspaceName ?? await
|
|
2502
|
+
const workspaceName = opts.workspaceName ?? await input3({ message: "T\xEAn workspace:", default: inferredName });
|
|
2398
2503
|
const workspaceParent = resolve(opts.workspaceParent ?? ".");
|
|
2399
2504
|
const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);
|
|
2400
2505
|
await scaffoldWorkspaceWithSrcSubmodule({
|
|
@@ -2417,11 +2522,11 @@ async function runInitFromExistingFolder(opts, ownerEmail) {
|
|
|
2417
2522
|
}
|
|
2418
2523
|
async function runInitFromScratch(opts, ownerEmail) {
|
|
2419
2524
|
await ensureGitHubReady();
|
|
2420
|
-
const projectName = opts.workspaceName ?? await
|
|
2525
|
+
const projectName = opts.workspaceName ?? await input3({
|
|
2421
2526
|
message: "T\xEAn d\u1EF1 \xE1n:",
|
|
2422
2527
|
validate: (v) => v.length > 0 ? true : "T\xEAn b\u1EAFt bu\u1ED9c"
|
|
2423
2528
|
});
|
|
2424
|
-
const visibility = opts.repoVisibility ?? await
|
|
2529
|
+
const visibility = opts.repoVisibility ?? await select7({
|
|
2425
2530
|
message: "Visibility?",
|
|
2426
2531
|
choices: [
|
|
2427
2532
|
{ name: "private (m\u1EB7c \u0111\u1ECBnh)", value: "private" },
|
|
@@ -2449,7 +2554,10 @@ async function runInitFromScratch(opts, ownerEmail) {
|
|
|
2449
2554
|
await git(workspacePath).subModule(["add", urls.sshUrl, "src"]);
|
|
2450
2555
|
let pinnedTag = "HEAD";
|
|
2451
2556
|
if (!opts.skipTeamPack) {
|
|
2452
|
-
const result = await
|
|
2557
|
+
const result = await addTeamPackSubmoduleWithRetryOnNetworkFail(
|
|
2558
|
+
workspacePath,
|
|
2559
|
+
opts.packVersion
|
|
2560
|
+
);
|
|
2453
2561
|
pinnedTag = result.pinnedTag ?? "HEAD";
|
|
2454
2562
|
sp.succeed(`Pin team-ai-pack v\xE0o ${pinnedTag}`);
|
|
2455
2563
|
} else {
|
|
@@ -2490,14 +2598,14 @@ async function getOrCreateOriginRemote(folderPath, opts) {
|
|
|
2490
2598
|
return void 0;
|
|
2491
2599
|
}
|
|
2492
2600
|
await ensureGitHubReady();
|
|
2493
|
-
const visibility = opts.repoVisibility ?? await
|
|
2601
|
+
const visibility = opts.repoVisibility ?? await select7({
|
|
2494
2602
|
message: "Visibility?",
|
|
2495
2603
|
choices: [
|
|
2496
2604
|
{ name: "private (m\u1EB7c \u0111\u1ECBnh)", value: "private" },
|
|
2497
2605
|
{ name: "public", value: "public" }
|
|
2498
2606
|
]
|
|
2499
2607
|
});
|
|
2500
|
-
const repoName = await
|
|
2608
|
+
const repoName = await input3({
|
|
2501
2609
|
message: "T\xEAn repo:",
|
|
2502
2610
|
default: basename(folderPath)
|
|
2503
2611
|
});
|
|
@@ -2519,7 +2627,10 @@ async function scaffoldWorkspaceWithSrcSubmodule(args) {
|
|
|
2519
2627
|
await git(args.workspacePath).subModule(["add", args.srcRemoteUrl, "src"]);
|
|
2520
2628
|
let pinnedTag = "HEAD";
|
|
2521
2629
|
if (!args.skipTeamPack) {
|
|
2522
|
-
const result = await
|
|
2630
|
+
const result = await addTeamPackSubmoduleWithRetryOnNetworkFail(
|
|
2631
|
+
args.workspacePath,
|
|
2632
|
+
args.packVersion
|
|
2633
|
+
);
|
|
2523
2634
|
pinnedTag = result.pinnedTag ?? "HEAD";
|
|
2524
2635
|
sp.succeed(`Pin team-ai-pack v\xE0o ${pinnedTag}`);
|
|
2525
2636
|
} else {
|
|
@@ -2587,43 +2698,79 @@ async function maybeCreateWorkspaceRemote(args) {
|
|
|
2587
2698
|
});
|
|
2588
2699
|
}
|
|
2589
2700
|
if (!shouldCreate) return;
|
|
2590
|
-
const visibility = args.repoVisibility ?? (args.autoYes ? "private" : await
|
|
2701
|
+
const visibility = args.repoVisibility ?? (args.autoYes ? "private" : await select7({
|
|
2591
2702
|
message: "Workspace visibility?",
|
|
2592
2703
|
choices: [
|
|
2593
2704
|
{ name: "private (m\u1EB7c \u0111\u1ECBnh, an to\xE0n)", value: "private" },
|
|
2594
2705
|
{ name: "public", value: "public" }
|
|
2595
2706
|
]
|
|
2596
2707
|
}));
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2708
|
+
while (true) {
|
|
2709
|
+
try {
|
|
2710
|
+
await createWorkspaceRemoteViaGh({
|
|
2711
|
+
workspacePath: args.workspacePath,
|
|
2712
|
+
workspaceName: args.workspaceName,
|
|
2713
|
+
visibility,
|
|
2714
|
+
org: args.repoOrg
|
|
2715
|
+
});
|
|
2716
|
+
return;
|
|
2717
|
+
} catch (err) {
|
|
2718
|
+
const action = await promptRetryOrSkip({
|
|
2719
|
+
taskName: "T\u1EA1o workspace remote tr\xEAn GitHub",
|
|
2720
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
2721
|
+
allowSkip: true,
|
|
2722
|
+
// Workspace remote OPTIONAL — skip OK, workspace local vẫn dùng được.
|
|
2723
|
+
hint: "Tip: sai org? Pass --repo-org=<your-gh-user>. Ho\u1EB7c switch gh: gh auth login."
|
|
2724
|
+
});
|
|
2725
|
+
if (action === "abort") {
|
|
2726
|
+
throw new UserAbortedRecoveryError("User abort t\u1EA1i b\u01B0\u1EDBc t\u1EA1o workspace remote.");
|
|
2727
|
+
}
|
|
2728
|
+
if (action === "skip") {
|
|
2729
|
+
log.warn("Workspace v\u1EABn s\u1EB5n s\xE0ng local-only. Setup remote sau khi c\u1EA7n.");
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2607
2733
|
}
|
|
2608
2734
|
}
|
|
2609
2735
|
async function resolveWorkspacePath(parent, desiredName, force) {
|
|
2610
2736
|
const desired = join16(parent, desiredName);
|
|
2611
2737
|
if (await isEmptyOrMissing(desired)) return desired;
|
|
2612
|
-
const alternative = await findAlternativeWorkspaceName(parent, desiredName);
|
|
2613
|
-
if (!alternative) {
|
|
2614
|
-
throw new Error(`Kh\xF4ng t\xECm \u0111\u01B0\u1EE3c workspace path kh\u1EA3 d\u1EE5ng trong ${parent}`);
|
|
2615
|
-
}
|
|
2616
2738
|
log.warn(`Workspace path "${desired}" \u0111\xE3 c\xF3 n\u1ED9i dung.`);
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2739
|
+
while (true) {
|
|
2740
|
+
const alternative = await findAlternativeWorkspaceName(parent, desiredName);
|
|
2741
|
+
if (force && alternative) {
|
|
2742
|
+
log.info(`--force: d\xF9ng ${alternative}`);
|
|
2743
|
+
return alternative;
|
|
2744
|
+
}
|
|
2745
|
+
const choices = [];
|
|
2746
|
+
if (alternative) {
|
|
2747
|
+
choices.push({ name: `D\xF9ng "${alternative}" (suggest)`, value: "use-alt" });
|
|
2748
|
+
}
|
|
2749
|
+
choices.push({ name: "Nh\u1EADp t\xEAn workspace kh\xE1c (manual)", value: "manual" });
|
|
2750
|
+
choices.push({ name: "T\u1EA1m ng\u01B0ng init", value: "abort" });
|
|
2751
|
+
const action = await select7({
|
|
2752
|
+
message: "C\xE1ch x\u1EED l\xFD workspace path conflict?",
|
|
2753
|
+
choices
|
|
2754
|
+
});
|
|
2755
|
+
if (action === "abort") {
|
|
2756
|
+
throw new UserAbortedRecoveryError(
|
|
2757
|
+
"User abort t\u1EA1i b\u01B0\u1EDBc resolve workspace path. Ch\u1EA1y l\u1EA1i v\u1EDBi --workspace-name kh\xE1c."
|
|
2758
|
+
);
|
|
2759
|
+
}
|
|
2760
|
+
if (action === "use-alt" && alternative) {
|
|
2761
|
+
return alternative;
|
|
2762
|
+
}
|
|
2763
|
+
const newName = await input3({
|
|
2764
|
+
message: "T\xEAn workspace m\u1EDBi:",
|
|
2765
|
+
validate: (v) => v.trim().length > 0 ? true : "T\xEAn kh\xF4ng \u0111\u01B0\u1EE3c r\u1ED7ng"
|
|
2766
|
+
});
|
|
2767
|
+
const newPath = join16(parent, newName.trim());
|
|
2768
|
+
if (await isEmptyOrMissing(newPath)) return newPath;
|
|
2769
|
+
log.warn(`"${newPath}" c\u0169ng \u0111\xE3 c\xF3 n\u1ED9i dung. Th\u1EED t\xEAn kh\xE1c.`);
|
|
2620
2770
|
}
|
|
2621
|
-
const useAlt = await confirm3({ message: `D\xF9ng "${alternative}" thay th\u1EBF?`, default: true });
|
|
2622
|
-
if (!useAlt) throw new Error("H\u1EE7y init. Ch\u1EA1y l\u1EA1i v\u1EDBi --workspace-name kh\xE1c.");
|
|
2623
|
-
return alternative;
|
|
2624
2771
|
}
|
|
2625
2772
|
async function promptTeamOwner(currentUserEmail) {
|
|
2626
|
-
return await
|
|
2773
|
+
return await input3({ message: "Team owner email:", default: currentUserEmail });
|
|
2627
2774
|
}
|
|
2628
2775
|
async function maybeCommitWorkspace(workspacePath, skipCommit) {
|
|
2629
2776
|
if (skipCommit) {
|
|
@@ -2948,7 +3095,7 @@ async function removeSubmoduleEntry(gitmodulesPath, submodulePath) {
|
|
|
2948
3095
|
}
|
|
2949
3096
|
|
|
2950
3097
|
// src/commands/uninstall.ts
|
|
2951
|
-
var CLI_VERSION = "1.2.
|
|
3098
|
+
var CLI_VERSION = "1.2.5";
|
|
2952
3099
|
function registerUninstallCommand(program2) {
|
|
2953
3100
|
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) => {
|
|
2954
3101
|
try {
|
|
@@ -3030,7 +3177,7 @@ function printUninstallSuccessBox(backupPath) {
|
|
|
3030
3177
|
}
|
|
3031
3178
|
|
|
3032
3179
|
// src/index.ts
|
|
3033
|
-
var CLI_VERSION2 = "1.2.
|
|
3180
|
+
var CLI_VERSION2 = "1.2.5";
|
|
3034
3181
|
var program = new Command();
|
|
3035
3182
|
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(
|
|
3036
3183
|
"beforeAll",
|