@nalvietnam/avatar-cli 1.5.0 → 1.6.1
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
|
@@ -473,27 +473,126 @@ async function promptAiProviderChoice(globalInfo = detectGlobalClaudeSettings())
|
|
|
473
473
|
message: "Ch\u1ECDn provider cho AI features:",
|
|
474
474
|
choices: [
|
|
475
475
|
{
|
|
476
|
-
name: "1. Claude Code Subscription (d\xF9ng quota c\xE1 nh\xE2n Anthropic)",
|
|
476
|
+
name: "1. Claude Code Subscription (d\xF9ng quota c\xE1 nh\xE2n Anthropic, OAuth login)",
|
|
477
477
|
value: "subscription"
|
|
478
478
|
},
|
|
479
479
|
{
|
|
480
|
-
name: "2. LLMLite API key (llm.nal.vn \u2014 NAL
|
|
480
|
+
name: "2. LLMLite API key (llm.nal.vn \u2014 NAL gateway, key sk-...)",
|
|
481
481
|
value: "llmlite"
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
name: "3. Anthropic API key tr\u1EF1c ti\u1EBFp (console.anthropic.com, key sk-ant-...)",
|
|
485
|
+
value: "anthropic"
|
|
482
486
|
}
|
|
483
487
|
]
|
|
484
488
|
});
|
|
485
489
|
}
|
|
486
490
|
|
|
491
|
+
// src/lib/setup-anthropic-api-key-and-model.ts
|
|
492
|
+
import { password, select as select2 } from "@inquirer/prompts";
|
|
493
|
+
var ANTHROPIC_BASE_URL = "https://api.anthropic.com";
|
|
494
|
+
var ANTHROPIC_API_VERSION = "2023-06-01";
|
|
495
|
+
var FETCH_TIMEOUT_MS = 1e4;
|
|
496
|
+
function maskAnthropicKey(key) {
|
|
497
|
+
if (key.length <= 12) return "sk-ant-***";
|
|
498
|
+
return `${key.slice(0, 7)}...${key.slice(-4)}`;
|
|
499
|
+
}
|
|
500
|
+
function validateAnthropicKeyFormat(key) {
|
|
501
|
+
const trimmed = key.trim();
|
|
502
|
+
if (trimmed.length === 0) return "API key b\u1EAFt bu\u1ED9c";
|
|
503
|
+
if (!trimmed.startsWith("sk-ant-")) {
|
|
504
|
+
return "Anthropic API key th\u01B0\u1EDDng b\u1EAFt \u0111\u1EA7u b\u1EB1ng 'sk-ant-' (l\u1EA5y t\u1EEB console.anthropic.com).";
|
|
505
|
+
}
|
|
506
|
+
return true;
|
|
507
|
+
}
|
|
508
|
+
async function promptAnthropicKeyHidden() {
|
|
509
|
+
return await password({
|
|
510
|
+
message: "Anthropic API key (sk-ant-..., \u1EA9n input):",
|
|
511
|
+
mask: "*",
|
|
512
|
+
validate: validateAnthropicKeyFormat
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
async function fetchAnthropicModels(apiKey) {
|
|
516
|
+
const controller = new AbortController();
|
|
517
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
518
|
+
try {
|
|
519
|
+
const res = await fetch(`${ANTHROPIC_BASE_URL}/v1/models`, {
|
|
520
|
+
method: "GET",
|
|
521
|
+
headers: {
|
|
522
|
+
"x-api-key": apiKey,
|
|
523
|
+
"anthropic-version": ANTHROPIC_API_VERSION,
|
|
524
|
+
Accept: "application/json"
|
|
525
|
+
},
|
|
526
|
+
signal: controller.signal
|
|
527
|
+
});
|
|
528
|
+
if (res.status === 401) {
|
|
529
|
+
throw new Error("API key invalid (HTTP 401). Check key tr\xEAn console.anthropic.com.");
|
|
530
|
+
}
|
|
531
|
+
if (res.status === 403) {
|
|
532
|
+
throw new Error(
|
|
533
|
+
"API key b\u1ECB reject (HTTP 403). Key c\xF3 th\u1EC3 \u0111\xE3 b\u1ECB revoke ho\u1EB7c thi\u1EBFu permission."
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
if (res.status === 429) {
|
|
537
|
+
throw new Error("Rate limit (HTTP 429). Ch\u1EDD v\xE0i gi\xE2y r\u1ED3i th\u1EED l\u1EA1i.");
|
|
538
|
+
}
|
|
539
|
+
if (!res.ok) {
|
|
540
|
+
throw new Error(`Fetch models th\u1EA5t b\u1EA1i (HTTP ${res.status}).`);
|
|
541
|
+
}
|
|
542
|
+
const json = await res.json();
|
|
543
|
+
const models = (json.data || []).map((m) => typeof m.id === "string" ? m.id : null).filter((id) => id !== null);
|
|
544
|
+
if (models.length === 0) {
|
|
545
|
+
throw new Error("Anthropic tr\u1EA3 v\u1EC1 list r\u1ED7ng. Li\xEAn h\u1EC7 support ho\u1EB7c check account.");
|
|
546
|
+
}
|
|
547
|
+
return models;
|
|
548
|
+
} catch (err) {
|
|
549
|
+
if (err.name === "AbortError") {
|
|
550
|
+
throw new Error(`Connect ${ANTHROPIC_BASE_URL} timeout sau ${FETCH_TIMEOUT_MS / 1e3}s.`);
|
|
551
|
+
}
|
|
552
|
+
throw err;
|
|
553
|
+
} finally {
|
|
554
|
+
clearTimeout(timer);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
async function promptAnthropicModelChoice(models) {
|
|
558
|
+
if (models.length === 1) {
|
|
559
|
+
log.info(`Auto-pick model: ${models[0]} (ch\u1EC9 1 model available)`);
|
|
560
|
+
return models[0];
|
|
561
|
+
}
|
|
562
|
+
const sorted = [...models].sort((a, b) => {
|
|
563
|
+
const score = (m) => {
|
|
564
|
+
const lower = m.toLowerCase();
|
|
565
|
+
if (lower.includes("sonnet")) return 0;
|
|
566
|
+
if (lower.includes("opus")) return 1;
|
|
567
|
+
if (lower.includes("haiku")) return 2;
|
|
568
|
+
return 3;
|
|
569
|
+
};
|
|
570
|
+
return score(a) - score(b);
|
|
571
|
+
});
|
|
572
|
+
return await select2({
|
|
573
|
+
message: "Ch\u1ECDn model m\u1EB7c \u0111\u1ECBnh cho project:",
|
|
574
|
+
choices: sorted.map((m) => ({ name: m, value: m }))
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
async function setupAnthropicApiKeyAndModel() {
|
|
578
|
+
const apiKey = await promptAnthropicKeyHidden();
|
|
579
|
+
log.info(`Verify key (${maskAnthropicKey(apiKey)}) qua ${ANTHROPIC_BASE_URL}/v1/models...`);
|
|
580
|
+
const models = await fetchAnthropicModels(apiKey);
|
|
581
|
+
log.success(`Endpoint OK \u2014 ${models.length} models available`);
|
|
582
|
+
const model = await promptAnthropicModelChoice(models);
|
|
583
|
+
return { apiKey, baseUrl: ANTHROPIC_BASE_URL, model };
|
|
584
|
+
}
|
|
585
|
+
|
|
487
586
|
// src/lib/setup-llmlite-api-key-and-model.ts
|
|
488
|
-
import { input, password, select as
|
|
587
|
+
import { input, password as password2, select as select3 } from "@inquirer/prompts";
|
|
489
588
|
var DEFAULT_BASE_URL = "https://llm.nal.vn";
|
|
490
|
-
var
|
|
589
|
+
var FETCH_TIMEOUT_MS2 = 1e4;
|
|
491
590
|
function maskApiKey(key) {
|
|
492
591
|
if (key.length <= 8) return "sk-***";
|
|
493
592
|
return `${key.slice(0, 3)}...${key.slice(-4)}`;
|
|
494
593
|
}
|
|
495
594
|
async function promptApiKeyHidden() {
|
|
496
|
-
return await
|
|
595
|
+
return await password2({
|
|
497
596
|
message: "LLMLite API key (\u1EA9n input):",
|
|
498
597
|
mask: "*",
|
|
499
598
|
validate: (v) => v.trim().length > 0 ? true : "API key b\u1EAFt bu\u1ED9c"
|
|
@@ -509,7 +608,7 @@ async function promptBaseUrl(defaultUrl = DEFAULT_BASE_URL) {
|
|
|
509
608
|
}
|
|
510
609
|
async function fetchAvailableModels(baseUrl, apiKey) {
|
|
511
610
|
const controller = new AbortController();
|
|
512
|
-
const timer = setTimeout(() => controller.abort(),
|
|
611
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS2);
|
|
513
612
|
try {
|
|
514
613
|
const res = await fetch(`${baseUrl}/v1/models`, {
|
|
515
614
|
method: "GET",
|
|
@@ -536,7 +635,7 @@ async function fetchAvailableModels(baseUrl, apiKey) {
|
|
|
536
635
|
return models;
|
|
537
636
|
} catch (err) {
|
|
538
637
|
if (err.name === "AbortError") {
|
|
539
|
-
throw new Error(`Connect ${baseUrl} timeout sau ${
|
|
638
|
+
throw new Error(`Connect ${baseUrl} timeout sau ${FETCH_TIMEOUT_MS2 / 1e3}s.`);
|
|
540
639
|
}
|
|
541
640
|
throw err;
|
|
542
641
|
} finally {
|
|
@@ -550,7 +649,7 @@ async function promptModelChoice(models) {
|
|
|
550
649
|
return claudeAliases[0];
|
|
551
650
|
}
|
|
552
651
|
const choiceList = claudeAliases.length > 0 ? claudeAliases : models;
|
|
553
|
-
return await
|
|
652
|
+
return await select3({
|
|
554
653
|
message: "Ch\u1ECDn model m\u1EB7c \u0111\u1ECBnh cho project:",
|
|
555
654
|
choices: choiceList.map((m) => ({ name: m, value: m }))
|
|
556
655
|
});
|
|
@@ -586,7 +685,12 @@ function applySubscription(existing, model) {
|
|
|
586
685
|
const { env: existingEnv, ...rest } = existing;
|
|
587
686
|
const merged = { ...rest, model };
|
|
588
687
|
if (existingEnv) {
|
|
589
|
-
const {
|
|
688
|
+
const {
|
|
689
|
+
ANTHROPIC_BASE_URL: _b,
|
|
690
|
+
ANTHROPIC_AUTH_TOKEN: _t,
|
|
691
|
+
ANTHROPIC_API_KEY: _k,
|
|
692
|
+
...envRest
|
|
693
|
+
} = existingEnv;
|
|
590
694
|
if (Object.keys(envRest).length > 0) {
|
|
591
695
|
merged.env = envRest;
|
|
592
696
|
}
|
|
@@ -594,16 +698,29 @@ function applySubscription(existing, model) {
|
|
|
594
698
|
return merged;
|
|
595
699
|
}
|
|
596
700
|
function applyLLMLite(existing, apiKey, baseUrl, model) {
|
|
701
|
+
const { ANTHROPIC_API_KEY: _k, ...envRest } = existing.env || {};
|
|
597
702
|
return {
|
|
598
703
|
...existing,
|
|
599
704
|
env: {
|
|
600
|
-
...
|
|
705
|
+
...envRest,
|
|
601
706
|
ANTHROPIC_BASE_URL: baseUrl,
|
|
602
707
|
ANTHROPIC_AUTH_TOKEN: apiKey
|
|
603
708
|
},
|
|
604
709
|
model
|
|
605
710
|
};
|
|
606
711
|
}
|
|
712
|
+
function applyAnthropic(existing, apiKey, baseUrl, model) {
|
|
713
|
+
const { ANTHROPIC_AUTH_TOKEN: _t, ...envRest } = existing.env || {};
|
|
714
|
+
return {
|
|
715
|
+
...existing,
|
|
716
|
+
env: {
|
|
717
|
+
...envRest,
|
|
718
|
+
ANTHROPIC_BASE_URL: baseUrl,
|
|
719
|
+
ANTHROPIC_API_KEY: apiKey
|
|
720
|
+
},
|
|
721
|
+
model
|
|
722
|
+
};
|
|
723
|
+
}
|
|
607
724
|
function applyUseGlobal(existing, source) {
|
|
608
725
|
const sourceEnv = source.env || {};
|
|
609
726
|
const sourceModel = typeof source.model === "string" ? source.model : void 0;
|
|
@@ -627,6 +744,9 @@ async function writeClaudeSettings(workspacePath, input6) {
|
|
|
627
744
|
case "llmlite":
|
|
628
745
|
merged = applyLLMLite(existing, input6.apiKey, input6.baseUrl, input6.model);
|
|
629
746
|
break;
|
|
747
|
+
case "anthropic":
|
|
748
|
+
merged = applyAnthropic(existing, input6.apiKey, input6.baseUrl, input6.model);
|
|
749
|
+
break;
|
|
630
750
|
case "use-global":
|
|
631
751
|
merged = applyUseGlobal(existing, input6.sourceSettings);
|
|
632
752
|
break;
|
|
@@ -701,6 +821,23 @@ async function runAiSetupPhase(args) {
|
|
|
701
821
|
log.success(`AI ready \xB7 LLMLite \xB7 model=${llmConfig.model} \xB7 ${llmConfig.baseUrl}`);
|
|
702
822
|
return { ok: true, provider: "llmlite", model: llmConfig.model };
|
|
703
823
|
}
|
|
824
|
+
case "anthropic": {
|
|
825
|
+
const anthropicConfig = await setupAnthropicApiKeyAndModel();
|
|
826
|
+
await writeClaudeSettings(args.workspacePath, {
|
|
827
|
+
provider: "anthropic",
|
|
828
|
+
apiKey: anthropicConfig.apiKey,
|
|
829
|
+
baseUrl: anthropicConfig.baseUrl,
|
|
830
|
+
model: anthropicConfig.model
|
|
831
|
+
});
|
|
832
|
+
await appendAuditEntry(
|
|
833
|
+
"ai_setup",
|
|
834
|
+
`provider=anthropic,result=ok,model=${anthropicConfig.model}`
|
|
835
|
+
);
|
|
836
|
+
log.success(
|
|
837
|
+
`AI ready \xB7 Anthropic Direct \xB7 model=${anthropicConfig.model} \xB7 ${anthropicConfig.baseUrl}`
|
|
838
|
+
);
|
|
839
|
+
return { ok: true, provider: "anthropic", model: anthropicConfig.model };
|
|
840
|
+
}
|
|
704
841
|
case "use-global": {
|
|
705
842
|
if (!globalInfo.rawSettings) {
|
|
706
843
|
throw new Error("use-global ch\u1ECDn nh\u01B0ng kh\xF4ng \u0111\u1ECDc \u0111\u01B0\u1EE3c global settings.");
|
|
@@ -725,14 +862,15 @@ async function runAiSetupPhase(args) {
|
|
|
725
862
|
|
|
726
863
|
// src/lib/test-ai-provider-by-detected-mode.ts
|
|
727
864
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
728
|
-
var
|
|
865
|
+
var FETCH_TIMEOUT_MS3 = 1e4;
|
|
729
866
|
var CLAUDE_PRINT_TIMEOUT_MS = 3e4;
|
|
730
867
|
var TEST_CHAT_MAX_TOKENS = 5;
|
|
731
868
|
var TEST_CHAT_PROMPT = "say ok";
|
|
869
|
+
var ANTHROPIC_API_VERSION2 = "2023-06-01";
|
|
732
870
|
async function testLLMLiteProvider(baseUrl, token, model) {
|
|
733
871
|
log.info(`Testing LLMLite provider: ${baseUrl} (key: ${maskApiKey(token)})`);
|
|
734
872
|
const controller = new AbortController();
|
|
735
|
-
const timer = setTimeout(() => controller.abort(),
|
|
873
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS3);
|
|
736
874
|
try {
|
|
737
875
|
const modelsRes = await fetch(`${baseUrl}/v1/models`, {
|
|
738
876
|
headers: { Authorization: `Bearer ${token}` },
|
|
@@ -777,7 +915,7 @@ async function testLLMLiteProvider(baseUrl, token, model) {
|
|
|
777
915
|
log.dim(` Tokens used: ${tokens}`);
|
|
778
916
|
} catch (err) {
|
|
779
917
|
if (err.name === "AbortError") {
|
|
780
|
-
throw new Error(`Timeout ${
|
|
918
|
+
throw new Error(`Timeout ${FETCH_TIMEOUT_MS3 / 1e3}s. Check m\u1EA1ng / endpoint ${baseUrl}.`);
|
|
781
919
|
}
|
|
782
920
|
throw err;
|
|
783
921
|
} finally {
|
|
@@ -806,11 +944,63 @@ function testSubscriptionProvider() {
|
|
|
806
944
|
}
|
|
807
945
|
log.success(`Response: "${(result.stdout || "").trim().slice(0, 100)}"`);
|
|
808
946
|
}
|
|
947
|
+
async function testAnthropicProvider(baseUrl, apiKey, model) {
|
|
948
|
+
log.info(`Testing Anthropic Direct provider: ${baseUrl} (key: ${maskApiKey(apiKey)})`);
|
|
949
|
+
const controller = new AbortController();
|
|
950
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS3);
|
|
951
|
+
try {
|
|
952
|
+
const modelsRes = await fetch(`${baseUrl}/v1/models`, {
|
|
953
|
+
headers: {
|
|
954
|
+
"x-api-key": apiKey,
|
|
955
|
+
"anthropic-version": ANTHROPIC_API_VERSION2
|
|
956
|
+
},
|
|
957
|
+
signal: controller.signal
|
|
958
|
+
});
|
|
959
|
+
if (modelsRes.status === 401 || modelsRes.status === 403) {
|
|
960
|
+
throw new Error(`API key invalid (HTTP ${modelsRes.status}). Re-run: avatar ai setup`);
|
|
961
|
+
}
|
|
962
|
+
if (!modelsRes.ok) {
|
|
963
|
+
throw new Error(`Endpoint /v1/models l\u1ED7i (HTTP ${modelsRes.status}).`);
|
|
964
|
+
}
|
|
965
|
+
const modelsJson = await modelsRes.json();
|
|
966
|
+
const models = (modelsJson.data || []).map((m) => typeof m.id === "string" ? m.id : null).filter((id) => id !== null);
|
|
967
|
+
log.success(`Connectivity OK \xB7 ${models.length} models available`);
|
|
968
|
+
log.info(`Testing message v\u1EDBi model "${model}"...`);
|
|
969
|
+
const msgRes = await fetch(`${baseUrl}/v1/messages`, {
|
|
970
|
+
method: "POST",
|
|
971
|
+
headers: {
|
|
972
|
+
"x-api-key": apiKey,
|
|
973
|
+
"anthropic-version": ANTHROPIC_API_VERSION2,
|
|
974
|
+
"Content-Type": "application/json"
|
|
975
|
+
},
|
|
976
|
+
body: JSON.stringify({
|
|
977
|
+
model,
|
|
978
|
+
max_tokens: TEST_CHAT_MAX_TOKENS,
|
|
979
|
+
messages: [{ role: "user", content: TEST_CHAT_PROMPT }]
|
|
980
|
+
}),
|
|
981
|
+
signal: controller.signal
|
|
982
|
+
});
|
|
983
|
+
if (!msgRes.ok) {
|
|
984
|
+
const errText = (await msgRes.text()).slice(0, 200);
|
|
985
|
+
throw new Error(`Message endpoint fail (HTTP ${msgRes.status}): ${errText}`);
|
|
986
|
+
}
|
|
987
|
+
const msgJson = await msgRes.json();
|
|
988
|
+
const text = (msgJson.content || []).map((c) => typeof c.text === "string" ? c.text : "").join("").trim().slice(0, 100);
|
|
989
|
+
log.success(`Response: "${text}"`);
|
|
990
|
+
} finally {
|
|
991
|
+
clearTimeout(timer);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
809
994
|
async function testAiProviderByDetectedMode(settings) {
|
|
810
995
|
const env = settings.env || {};
|
|
811
996
|
const baseUrl = typeof env.ANTHROPIC_BASE_URL === "string" ? env.ANTHROPIC_BASE_URL : void 0;
|
|
812
997
|
const token = typeof env.ANTHROPIC_AUTH_TOKEN === "string" ? env.ANTHROPIC_AUTH_TOKEN : void 0;
|
|
998
|
+
const apiKey = typeof env.ANTHROPIC_API_KEY === "string" ? env.ANTHROPIC_API_KEY : void 0;
|
|
813
999
|
const model = typeof settings.model === "string" ? settings.model : "default";
|
|
1000
|
+
if (apiKey && baseUrl) {
|
|
1001
|
+
await testAnthropicProvider(baseUrl, apiKey, model);
|
|
1002
|
+
return { ok: true, provider: "anthropic", message: "Anthropic Direct provider working" };
|
|
1003
|
+
}
|
|
814
1004
|
if (baseUrl && token) {
|
|
815
1005
|
await testLLMLiteProvider(baseUrl, token, model);
|
|
816
1006
|
return { ok: true, provider: "llmlite", message: "LLMLite provider working" };
|
|
@@ -856,12 +1046,27 @@ async function runAiStatus() {
|
|
|
856
1046
|
const env = settings.env || {};
|
|
857
1047
|
const baseUrl = typeof env.ANTHROPIC_BASE_URL === "string" ? env.ANTHROPIC_BASE_URL : void 0;
|
|
858
1048
|
const token = typeof env.ANTHROPIC_AUTH_TOKEN === "string" ? env.ANTHROPIC_AUTH_TOKEN : void 0;
|
|
1049
|
+
const apiKey = typeof env.ANTHROPIC_API_KEY === "string" ? env.ANTHROPIC_API_KEY : void 0;
|
|
859
1050
|
const model = typeof settings.model === "string" ? settings.model : void 0;
|
|
860
|
-
|
|
1051
|
+
let provider;
|
|
1052
|
+
let credentialDisplay;
|
|
1053
|
+
if (apiKey) {
|
|
1054
|
+
provider = baseUrl?.includes("api.anthropic.com") || apiKey.startsWith("sk-ant-") ? "Anthropic Direct" : "Custom (API key)";
|
|
1055
|
+
credentialDisplay = maskApiKey(apiKey);
|
|
1056
|
+
} else if (baseUrl && token) {
|
|
1057
|
+
provider = "LLMLite";
|
|
1058
|
+
credentialDisplay = maskApiKey(token);
|
|
1059
|
+
} else if (token) {
|
|
1060
|
+
provider = "Custom";
|
|
1061
|
+
credentialDisplay = maskApiKey(token);
|
|
1062
|
+
} else {
|
|
1063
|
+
provider = "Subscription (default)";
|
|
1064
|
+
credentialDisplay = "(kh\xF4ng set \u2014 d\xF9ng subscription auth)";
|
|
1065
|
+
}
|
|
861
1066
|
log.info(`Project: ${workspacePath}`);
|
|
862
1067
|
log.info(`Provider: ${provider}${baseUrl ? ` (${baseUrl})` : ""}`);
|
|
863
1068
|
log.info(`Model: ${model ?? "(default \u2014 Claude Code ch\u1ECDn)"}`);
|
|
864
|
-
log.info(`Token: ${
|
|
1069
|
+
log.info(`Token: ${credentialDisplay}`);
|
|
865
1070
|
}
|
|
866
1071
|
async function runAiTest() {
|
|
867
1072
|
const workspacePath = await ensureWorkspaceCwd();
|
|
@@ -891,7 +1096,12 @@ async function runAiReset(opts) {
|
|
|
891
1096
|
const { env: existingEnv, ...rest } = settings;
|
|
892
1097
|
const reset = { ...rest };
|
|
893
1098
|
if (existingEnv) {
|
|
894
|
-
const {
|
|
1099
|
+
const {
|
|
1100
|
+
ANTHROPIC_BASE_URL: _b,
|
|
1101
|
+
ANTHROPIC_AUTH_TOKEN: _t,
|
|
1102
|
+
ANTHROPIC_API_KEY: _k,
|
|
1103
|
+
...envRest
|
|
1104
|
+
} = existingEnv;
|
|
895
1105
|
if (Object.keys(envRest).length > 0) {
|
|
896
1106
|
reset.env = envRest;
|
|
897
1107
|
}
|
|
@@ -1549,7 +1759,7 @@ function installGitnexusViaNpm() {
|
|
|
1549
1759
|
}
|
|
1550
1760
|
|
|
1551
1761
|
// src/lib/prompt-recovery-action-on-failure.ts
|
|
1552
|
-
import { input as input3, select as
|
|
1762
|
+
import { input as input3, select as select4 } from "@inquirer/prompts";
|
|
1553
1763
|
var UserAbortedRecoveryError = class extends Error {
|
|
1554
1764
|
constructor(message) {
|
|
1555
1765
|
super(message);
|
|
@@ -1566,7 +1776,7 @@ async function promptRetryOrSkip(args) {
|
|
|
1566
1776
|
choices.push({ name: "B\u1ECF qua b\u01B0\u1EDBc n\xE0y v\xE0 ti\u1EBFp t\u1EE5c (Skip)", value: "skip" });
|
|
1567
1777
|
}
|
|
1568
1778
|
choices.push({ name: "T\u1EA1m ng\u01B0ng init \u2014 ch\u1EA1y l\u1EA1i sau (Abort)", value: "abort" });
|
|
1569
|
-
return await
|
|
1779
|
+
return await select4({
|
|
1570
1780
|
message: "C\xE1ch x\u1EED l\xFD?",
|
|
1571
1781
|
choices
|
|
1572
1782
|
});
|
|
@@ -1642,17 +1852,41 @@ import { join as join14 } from "path";
|
|
|
1642
1852
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
1643
1853
|
var WIKI_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
1644
1854
|
var DEFAULT_LLMLITE_MODEL = "nal-claude";
|
|
1855
|
+
var DEFAULT_ANTHROPIC_MODEL = "claude-sonnet-4-5";
|
|
1856
|
+
function normalizeAnthropicBaseUrl(rawBaseUrl) {
|
|
1857
|
+
const cleaned = rawBaseUrl.replace(/\/+$/, "");
|
|
1858
|
+
if (cleaned.endsWith("/v1")) return `${cleaned}/`;
|
|
1859
|
+
if (cleaned.endsWith("/v1/")) return cleaned;
|
|
1860
|
+
return `${cleaned}/v1/`;
|
|
1861
|
+
}
|
|
1645
1862
|
async function readSettingsForWikiCredentials(workspacePath) {
|
|
1646
1863
|
const settingsPath = join14(workspacePath, ".claude", "settings.json");
|
|
1647
1864
|
if (!await pathExists(settingsPath)) return null;
|
|
1648
1865
|
try {
|
|
1649
1866
|
const settings = await readJson(settingsPath);
|
|
1650
1867
|
const env = settings.env || {};
|
|
1651
|
-
const apiKey = typeof env.ANTHROPIC_AUTH_TOKEN === "string" ? env.ANTHROPIC_AUTH_TOKEN : null;
|
|
1652
1868
|
const baseUrl = typeof env.ANTHROPIC_BASE_URL === "string" ? env.ANTHROPIC_BASE_URL : null;
|
|
1653
|
-
if (!
|
|
1654
|
-
const
|
|
1655
|
-
|
|
1869
|
+
if (!baseUrl) return null;
|
|
1870
|
+
const userModel = typeof env.ANTHROPIC_MODEL === "string" ? env.ANTHROPIC_MODEL : "";
|
|
1871
|
+
const anthropicKey = typeof env.ANTHROPIC_API_KEY === "string" ? env.ANTHROPIC_API_KEY : null;
|
|
1872
|
+
if (anthropicKey) {
|
|
1873
|
+
return {
|
|
1874
|
+
provider: "anthropic",
|
|
1875
|
+
apiKey: anthropicKey,
|
|
1876
|
+
baseUrl: normalizeAnthropicBaseUrl(baseUrl),
|
|
1877
|
+
model: userModel.length > 0 ? userModel : DEFAULT_ANTHROPIC_MODEL
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
const llmliteToken = typeof env.ANTHROPIC_AUTH_TOKEN === "string" ? env.ANTHROPIC_AUTH_TOKEN : null;
|
|
1881
|
+
if (llmliteToken) {
|
|
1882
|
+
return {
|
|
1883
|
+
provider: "llmlite",
|
|
1884
|
+
apiKey: llmliteToken,
|
|
1885
|
+
baseUrl,
|
|
1886
|
+
model: userModel.length > 0 ? userModel : DEFAULT_LLMLITE_MODEL
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1889
|
+
return null;
|
|
1656
1890
|
} catch {
|
|
1657
1891
|
return null;
|
|
1658
1892
|
}
|
|
@@ -1670,8 +1904,10 @@ function tailLines2(text, n) {
|
|
|
1670
1904
|
async function runGitnexusWikiConditional(workspacePath) {
|
|
1671
1905
|
const creds = await readSettingsForWikiCredentials(workspacePath);
|
|
1672
1906
|
if (!creds) {
|
|
1673
|
-
log.warn("Subscription mode (
|
|
1674
|
-
log.dim(
|
|
1907
|
+
log.warn("Subscription mode (OAuth, kh\xF4ng c\xF3 API key trong settings.json) \u2192 skip wiki gen.");
|
|
1908
|
+
log.dim(
|
|
1909
|
+
"\u0110\u1EC3 gen wiki sau, ch\u1EA1y manual:\n gitnexus wiki . --api-key <key> --base-url <url> --model <model>"
|
|
1910
|
+
);
|
|
1675
1911
|
return { ran: false, skipped: true, reason: "subscription-mode" };
|
|
1676
1912
|
}
|
|
1677
1913
|
const proceed = await confirmWikiGeneration(creds.baseUrl, creds.model);
|
|
@@ -1681,7 +1917,9 @@ async function runGitnexusWikiConditional(workspacePath) {
|
|
|
1681
1917
|
);
|
|
1682
1918
|
return { ran: false, skipped: true, reason: "user-declined" };
|
|
1683
1919
|
}
|
|
1684
|
-
const sp = spinnerWithElapsed(
|
|
1920
|
+
const sp = spinnerWithElapsed(
|
|
1921
|
+
`Generating wiki via ${creds.baseUrl} (${creds.provider}) model=${creds.model}`
|
|
1922
|
+
);
|
|
1685
1923
|
const result = spawnSync10(
|
|
1686
1924
|
"gitnexus",
|
|
1687
1925
|
["wiki", ".", "--api-key", creds.apiKey, "--base-url", creds.baseUrl, "--model", creds.model],
|
|
@@ -1960,19 +2198,19 @@ function registerGitnexusCommand(program2) {
|
|
|
1960
2198
|
|
|
1961
2199
|
// src/commands/init.ts
|
|
1962
2200
|
import { basename, join as join22, relative as relative2, resolve } from "path";
|
|
1963
|
-
import { confirm as confirm5, input as input5, select as
|
|
2201
|
+
import { confirm as confirm5, input as input5, select as select9 } from "@inquirer/prompts";
|
|
1964
2202
|
import boxen5 from "boxen";
|
|
1965
2203
|
|
|
1966
2204
|
// src/lib/add-team-pack-submodule-with-retry-on-network-fail.ts
|
|
1967
2205
|
import { spawnSync as spawnSync13 } from "child_process";
|
|
1968
|
-
import { select as
|
|
2206
|
+
import { select as select6 } from "@inquirer/prompts";
|
|
1969
2207
|
|
|
1970
2208
|
// src/lib/team-pack-submodule-manager.ts
|
|
1971
2209
|
import { join as join16 } from "path";
|
|
1972
2210
|
|
|
1973
2211
|
// src/lib/check-team-pack-access-with-retry-loop.ts
|
|
1974
2212
|
import { spawnSync as spawnSync12 } from "child_process";
|
|
1975
|
-
import { confirm as confirm4, select as
|
|
2213
|
+
import { confirm as confirm4, select as select5 } from "@inquirer/prompts";
|
|
1976
2214
|
import boxen3 from "boxen";
|
|
1977
2215
|
function parseRepoSlugFromGitUrl(url) {
|
|
1978
2216
|
const httpsMatch = url.match(/github\.com[/:]([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
@@ -2051,7 +2289,7 @@ async function ensureTeamPackAccessWithRetry(args) {
|
|
|
2051
2289
|
while (true) {
|
|
2052
2290
|
const ghUser = getCurrentGhUser();
|
|
2053
2291
|
const ghUserDisplay = ghUser ?? "(ch\u01B0a gh auth)";
|
|
2054
|
-
const action = await
|
|
2292
|
+
const action = await select5({
|
|
2055
2293
|
message: "C\xE1ch x\u1EED l\xFD?",
|
|
2056
2294
|
choices: [
|
|
2057
2295
|
{
|
|
@@ -2168,7 +2406,7 @@ function openGithubSshKeysPage() {
|
|
|
2168
2406
|
}
|
|
2169
2407
|
}
|
|
2170
2408
|
async function handleSshPermissionError() {
|
|
2171
|
-
return await
|
|
2409
|
+
return await select6({
|
|
2172
2410
|
message: "SSH permission denied. C\xE1ch x\u1EED l\xFD?",
|
|
2173
2411
|
choices: [
|
|
2174
2412
|
{
|
|
@@ -2431,7 +2669,7 @@ function detectPackageManager() {
|
|
|
2431
2669
|
|
|
2432
2670
|
// src/lib/handle-remote-access-failure-with-account-switch.ts
|
|
2433
2671
|
import { spawnSync as spawnSync19 } from "child_process";
|
|
2434
|
-
import { input as input4, select as
|
|
2672
|
+
import { input as input4, select as select7 } from "@inquirer/prompts";
|
|
2435
2673
|
|
|
2436
2674
|
// src/lib/verify-git-remote-accessible.ts
|
|
2437
2675
|
import { spawnSync as spawnSync18 } from "child_process";
|
|
@@ -2515,7 +2753,7 @@ async function handleRemoteAccessFailureWithAccountSwitch(args) {
|
|
|
2515
2753
|
log.dim(` L\xFD do: ${reason}${detail ? ` \u2014 ${detail.slice(0, 150)}` : ""}`);
|
|
2516
2754
|
log.info(getReasonHint(reason, currentUrl, ghUser));
|
|
2517
2755
|
if (ghUser) log.dim(` gh CLI hi\u1EC7n \u0111ang login: ${ghUser}`);
|
|
2518
|
-
const action = await
|
|
2756
|
+
const action = await select7({
|
|
2519
2757
|
message: "C\xE1ch x\u1EED l\xFD?",
|
|
2520
2758
|
choices: [
|
|
2521
2759
|
{
|
|
@@ -2824,7 +3062,7 @@ function linkExistingRemoteToWorkspace(args) {
|
|
|
2824
3062
|
|
|
2825
3063
|
// src/lib/safe-bootstrap-for-dirty-folder.ts
|
|
2826
3064
|
import { readdirSync } from "fs";
|
|
2827
|
-
import { select as
|
|
3065
|
+
import { select as select8 } from "@inquirer/prompts";
|
|
2828
3066
|
import { simpleGit as simpleGit3 } from "simple-git";
|
|
2829
3067
|
|
|
2830
3068
|
// src/lib/check-folder-has-git.ts
|
|
@@ -2961,7 +3199,7 @@ async function promptBootstrapStrategy(state, opts) {
|
|
|
2961
3199
|
if (opts.presetStrategy) return opts.presetStrategy;
|
|
2962
3200
|
if (opts.autoYes) return "stash";
|
|
2963
3201
|
if (state === "empty" || state === "clean") return "commit-all";
|
|
2964
|
-
return await
|
|
3202
|
+
return await select8({
|
|
2965
3203
|
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:",
|
|
2966
3204
|
choices: [
|
|
2967
3205
|
{
|
|
@@ -3446,7 +3684,7 @@ async function runInit(opts) {
|
|
|
3446
3684
|
}
|
|
3447
3685
|
}
|
|
3448
3686
|
async function promptProjectStatus() {
|
|
3449
|
-
return await
|
|
3687
|
+
return await select9({
|
|
3450
3688
|
message: "T\xECnh tr\u1EA1ng d\u1EF1 \xE1n c\u1EE7a b\u1EA1n?",
|
|
3451
3689
|
choices: [
|
|
3452
3690
|
{ name: "1. \u0110\xE3 c\xF3 repo git remote (URL c\xF3 s\u1EB5n)", value: "existing-remote" },
|
|
@@ -3529,7 +3767,7 @@ async function runInitFromScratch(opts, ownerEmail) {
|
|
|
3529
3767
|
message: "T\xEAn d\u1EF1 \xE1n:",
|
|
3530
3768
|
validate: (v) => v.length > 0 ? true : "T\xEAn b\u1EAFt bu\u1ED9c"
|
|
3531
3769
|
});
|
|
3532
|
-
const visibility = opts.repoVisibility ?? await
|
|
3770
|
+
const visibility = opts.repoVisibility ?? await select9({
|
|
3533
3771
|
message: "Visibility?",
|
|
3534
3772
|
choices: [
|
|
3535
3773
|
{ name: "private (m\u1EB7c \u0111\u1ECBnh)", value: "private" },
|
|
@@ -3604,7 +3842,7 @@ async function getOrCreateOriginRemote(folderPath, opts) {
|
|
|
3604
3842
|
return void 0;
|
|
3605
3843
|
}
|
|
3606
3844
|
await ensureGitHubReady();
|
|
3607
|
-
const visibility = opts.repoVisibility ?? await
|
|
3845
|
+
const visibility = opts.repoVisibility ?? await select9({
|
|
3608
3846
|
message: "Visibility?",
|
|
3609
3847
|
choices: [
|
|
3610
3848
|
{ name: "private (m\u1EB7c \u0111\u1ECBnh)", value: "private" },
|
|
@@ -3730,7 +3968,7 @@ async function maybeCreateWorkspaceRemote(args) {
|
|
|
3730
3968
|
});
|
|
3731
3969
|
}
|
|
3732
3970
|
if (!shouldCreate) return;
|
|
3733
|
-
const visibility = args.repoVisibility ?? (args.autoYes ? "private" : await
|
|
3971
|
+
const visibility = args.repoVisibility ?? (args.autoYes ? "private" : await select9({
|
|
3734
3972
|
message: "Workspace visibility?",
|
|
3735
3973
|
choices: [
|
|
3736
3974
|
{ name: "private (m\u1EB7c \u0111\u1ECBnh, an to\xE0n)", value: "private" },
|
|
@@ -3749,7 +3987,7 @@ async function maybeCreateWorkspaceRemote(args) {
|
|
|
3749
3987
|
} catch (err) {
|
|
3750
3988
|
if (err instanceof CreateWorkspaceRemoteError && err.reason === "repo-exists") {
|
|
3751
3989
|
const fullName = err.fullName;
|
|
3752
|
-
const reuseAction = await
|
|
3990
|
+
const reuseAction = await select9({
|
|
3753
3991
|
message: `Repo '${fullName}' \u0111\xE3 t\u1ED3n t\u1EA1i tr\xEAn GitHub. C\xE1ch x\u1EED l\xFD?`,
|
|
3754
3992
|
choices: [
|
|
3755
3993
|
{
|
|
@@ -3818,7 +4056,7 @@ async function resolveWorkspacePath(parent, desiredName, force) {
|
|
|
3818
4056
|
}
|
|
3819
4057
|
choices.push({ name: "Nh\u1EADp t\xEAn workspace kh\xE1c (manual)", value: "manual" });
|
|
3820
4058
|
choices.push({ name: "T\u1EA1m ng\u01B0ng init", value: "abort" });
|
|
3821
|
-
const action = await
|
|
4059
|
+
const action = await select9({
|
|
3822
4060
|
message: "C\xE1ch x\u1EED l\xFD workspace path conflict?",
|
|
3823
4061
|
choices
|
|
3824
4062
|
});
|
|
@@ -4536,7 +4774,7 @@ async function removeSubmoduleEntry(gitmodulesPath, submodulePath) {
|
|
|
4536
4774
|
}
|
|
4537
4775
|
|
|
4538
4776
|
// src/commands/uninstall.ts
|
|
4539
|
-
var CLI_VERSION = "1.
|
|
4777
|
+
var CLI_VERSION = "1.6.1";
|
|
4540
4778
|
function registerUninstallCommand(program2) {
|
|
4541
4779
|
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) => {
|
|
4542
4780
|
try {
|
|
@@ -4618,7 +4856,7 @@ function printUninstallSuccessBox(backupPath) {
|
|
|
4618
4856
|
}
|
|
4619
4857
|
|
|
4620
4858
|
// src/index.ts
|
|
4621
|
-
var CLI_VERSION2 = "1.
|
|
4859
|
+
var CLI_VERSION2 = "1.6.1";
|
|
4622
4860
|
var program = new Command();
|
|
4623
4861
|
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(
|
|
4624
4862
|
"beforeAll",
|