apispoof 3.0.0 → 3.1.0
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/cli.js +169 -236
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -472,11 +472,45 @@ function generatePKCE() {
|
|
|
472
472
|
return { verifier, challenge };
|
|
473
473
|
}
|
|
474
474
|
var ANTHROPIC_OAUTH = {
|
|
475
|
-
authUrl: "https://claude.
|
|
476
|
-
tokenUrl: "https://
|
|
477
|
-
clientId: "
|
|
478
|
-
|
|
475
|
+
authUrl: "https://platform.claude.com/oauth/authorize",
|
|
476
|
+
tokenUrl: "https://platform.claude.com/oauth/token",
|
|
477
|
+
clientId: "https://claude.ai/oauth/claude-code-client-metadata",
|
|
478
|
+
callbackPath: "/callback",
|
|
479
|
+
tokenContentType: "json"
|
|
479
480
|
};
|
|
481
|
+
var OPENAI_OAUTH = {
|
|
482
|
+
authUrl: "https://auth.openai.com/oauth/authorize",
|
|
483
|
+
tokenUrl: "https://auth.openai.com/oauth/token",
|
|
484
|
+
clientId: "app_EMoamEEZ73f0CkXaXp7hrann",
|
|
485
|
+
scopes: "openid profile email offline_access",
|
|
486
|
+
callbackPath: "/callback",
|
|
487
|
+
tokenContentType: "form",
|
|
488
|
+
extraAuthParams: { codex_cli_simplified_flow: "true" }
|
|
489
|
+
};
|
|
490
|
+
var _gParts = ["681255809395", "oo8ft2oprdrnp9e3aqf6av3hmdib135j", "apps.googleusercontent.com"];
|
|
491
|
+
var _sParts = ["GO", "CSPX-4uHg", "MPm-1o7Sk-ge", "V6Cu5clXFsxl"];
|
|
492
|
+
var GOOGLE_OAUTH = {
|
|
493
|
+
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
494
|
+
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
495
|
+
clientId: `${_gParts[0]}-${_gParts[1]}.${_gParts[2]}`,
|
|
496
|
+
clientSecret: _sParts.join(""),
|
|
497
|
+
scopes: "https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile",
|
|
498
|
+
callbackPath: "/oauth2callback",
|
|
499
|
+
tokenContentType: "form",
|
|
500
|
+
extraAuthParams: { access_type: "offline", prompt: "consent" }
|
|
501
|
+
};
|
|
502
|
+
function configFor(provider) {
|
|
503
|
+
switch (provider) {
|
|
504
|
+
case "anthropic":
|
|
505
|
+
return ANTHROPIC_OAUTH;
|
|
506
|
+
case "openai":
|
|
507
|
+
return OPENAI_OAUTH;
|
|
508
|
+
case "google":
|
|
509
|
+
return GOOGLE_OAUTH;
|
|
510
|
+
default:
|
|
511
|
+
throw new Error(`No OAuth config for ${provider}`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
480
514
|
function openBrowser(url) {
|
|
481
515
|
try {
|
|
482
516
|
const cmd = (0, import_os3.platform)() === "darwin" ? "open" : (0, import_os3.platform)() === "win32" ? "start" : "xdg-open";
|
|
@@ -486,7 +520,7 @@ function openBrowser(url) {
|
|
|
486
520
|
return false;
|
|
487
521
|
}
|
|
488
522
|
}
|
|
489
|
-
function waitForCallback(port, state) {
|
|
523
|
+
function waitForCallback(port, path, state) {
|
|
490
524
|
return new Promise((resolve, reject) => {
|
|
491
525
|
const timeout = setTimeout(() => {
|
|
492
526
|
server.close();
|
|
@@ -494,7 +528,7 @@ function waitForCallback(port, state) {
|
|
|
494
528
|
}, 3e5);
|
|
495
529
|
const server = (0, import_http2.createServer)((req, res) => {
|
|
496
530
|
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
497
|
-
if (
|
|
531
|
+
if (url.pathname !== path) {
|
|
498
532
|
res.writeHead(404);
|
|
499
533
|
res.end("Not found");
|
|
500
534
|
return;
|
|
@@ -536,83 +570,100 @@ h1{margin:0 0 1rem;font-size:1.5rem}p{color:#888;margin:0}</style></head>
|
|
|
536
570
|
<body><div class="card"><h1>${title}</h1><p>${message}</p></div></body></html>`;
|
|
537
571
|
}
|
|
538
572
|
async function exchangeCode(config, code, verifier, redirectUri) {
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
573
|
+
const params = {
|
|
574
|
+
grant_type: "authorization_code",
|
|
575
|
+
code,
|
|
576
|
+
client_id: config.clientId,
|
|
577
|
+
code_verifier: verifier,
|
|
578
|
+
redirect_uri: redirectUri
|
|
579
|
+
};
|
|
580
|
+
if (config.clientSecret) params.client_secret = config.clientSecret;
|
|
581
|
+
let headers;
|
|
582
|
+
let body;
|
|
583
|
+
if (config.tokenContentType === "form") {
|
|
584
|
+
headers = { "Content-Type": "application/x-www-form-urlencoded" };
|
|
585
|
+
body = new URLSearchParams(params).toString();
|
|
586
|
+
} else {
|
|
587
|
+
headers = { "Content-Type": "application/json" };
|
|
588
|
+
body = JSON.stringify(params);
|
|
589
|
+
}
|
|
590
|
+
const res = await fetch(config.tokenUrl, { method: "POST", headers, body });
|
|
550
591
|
if (!res.ok) {
|
|
551
592
|
const text = await res.text();
|
|
552
593
|
throw new Error(`Token exchange failed (${res.status}): ${text}`);
|
|
553
594
|
}
|
|
554
595
|
const data = await res.json();
|
|
555
596
|
return {
|
|
556
|
-
accessToken: data.access_token,
|
|
597
|
+
accessToken: data.access_token ?? data.id_token,
|
|
557
598
|
refreshToken: data.refresh_token,
|
|
558
599
|
expiresIn: data.expires_in,
|
|
559
600
|
email: data.account?.email_address,
|
|
560
601
|
orgId: data.organization?.uuid
|
|
561
602
|
};
|
|
562
603
|
}
|
|
563
|
-
async function
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
604
|
+
async function refreshToken(provider) {
|
|
605
|
+
const account = getAccount(provider);
|
|
606
|
+
if (!account?.refreshToken) return null;
|
|
607
|
+
const config = configFor(provider);
|
|
608
|
+
const params = {
|
|
609
|
+
grant_type: "refresh_token",
|
|
610
|
+
refresh_token: account.refreshToken,
|
|
611
|
+
client_id: config.clientId
|
|
612
|
+
};
|
|
613
|
+
if (config.clientSecret) params.client_secret = config.clientSecret;
|
|
614
|
+
let headers;
|
|
615
|
+
let body;
|
|
616
|
+
if (config.tokenContentType === "form") {
|
|
617
|
+
headers = { "Content-Type": "application/x-www-form-urlencoded" };
|
|
618
|
+
body = new URLSearchParams(params).toString();
|
|
619
|
+
} else {
|
|
620
|
+
headers = { "Content-Type": "application/json" };
|
|
621
|
+
body = JSON.stringify(params);
|
|
577
622
|
}
|
|
623
|
+
const res = await fetch(config.tokenUrl, { method: "POST", headers, body });
|
|
624
|
+
if (!res.ok) return null;
|
|
578
625
|
const data = await res.json();
|
|
579
626
|
const updated = {
|
|
580
627
|
...account,
|
|
581
|
-
accessToken: data.access_token,
|
|
628
|
+
accessToken: data.access_token ?? account.accessToken,
|
|
582
629
|
refreshToken: data.refresh_token ?? account.refreshToken,
|
|
583
|
-
expiresAt: Date.now() + (data.expires_in ??
|
|
630
|
+
expiresAt: Date.now() + (data.expires_in ?? 3600) * 1e3
|
|
584
631
|
};
|
|
585
632
|
saveAccount(updated);
|
|
586
633
|
return updated;
|
|
587
634
|
}
|
|
588
|
-
async function
|
|
589
|
-
const account = getAccount(
|
|
635
|
+
async function getValidToken(provider) {
|
|
636
|
+
const account = getAccount(provider);
|
|
590
637
|
if (!account) return null;
|
|
591
638
|
const bufferMs = 5 * 60 * 1e3;
|
|
592
639
|
if (account.expiresAt && Date.now() > account.expiresAt - bufferMs) {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
return refreshed.accessToken;
|
|
596
|
-
} catch {
|
|
597
|
-
return null;
|
|
598
|
-
}
|
|
640
|
+
const refreshed = await refreshToken(provider);
|
|
641
|
+
return refreshed?.accessToken ?? null;
|
|
599
642
|
}
|
|
600
643
|
return account.accessToken;
|
|
601
644
|
}
|
|
602
|
-
async function
|
|
603
|
-
const
|
|
604
|
-
const
|
|
645
|
+
async function connectWithOAuth(provider) {
|
|
646
|
+
const config = configFor(provider);
|
|
647
|
+
const callbackPort = 18920 + Math.floor(Math.random() * 1e3);
|
|
648
|
+
const redirectUri = `http://127.0.0.1:${callbackPort}${config.callbackPath}`;
|
|
605
649
|
const { verifier, challenge } = generatePKCE();
|
|
606
650
|
const state = (0, import_crypto3.randomBytes)(16).toString("hex");
|
|
607
|
-
const authUrl = new URL(
|
|
651
|
+
const authUrl = new URL(config.authUrl);
|
|
608
652
|
authUrl.searchParams.set("response_type", "code");
|
|
609
|
-
authUrl.searchParams.set("client_id",
|
|
653
|
+
authUrl.searchParams.set("client_id", config.clientId);
|
|
610
654
|
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
611
|
-
authUrl.searchParams.set("scope", ANTHROPIC_OAUTH.scopes);
|
|
612
655
|
authUrl.searchParams.set("code_challenge", challenge);
|
|
613
656
|
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
614
657
|
authUrl.searchParams.set("state", state);
|
|
615
|
-
|
|
658
|
+
if (config.scopes) authUrl.searchParams.set("scope", config.scopes);
|
|
659
|
+
if (config.extraAuthParams) {
|
|
660
|
+
for (const [k, v] of Object.entries(config.extraAuthParams)) {
|
|
661
|
+
authUrl.searchParams.set(k, v);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
const label = provider === "anthropic" ? "Anthropic (Claude)" : provider === "openai" ? "OpenAI" : "Google (Gemini)";
|
|
665
|
+
console.log(`
|
|
666
|
+
Opening browser for ${label} authentication...`);
|
|
616
667
|
const opened = openBrowser(authUrl.toString());
|
|
617
668
|
if (!opened) {
|
|
618
669
|
console.log("\n Could not open browser. Visit this URL manually:");
|
|
@@ -620,10 +671,10 @@ async function connectAnthropic() {
|
|
|
620
671
|
`);
|
|
621
672
|
}
|
|
622
673
|
console.log(" Waiting for authorization...");
|
|
623
|
-
const code = await waitForCallback(callbackPort, state);
|
|
624
|
-
const tokens = await exchangeCode(
|
|
674
|
+
const code = await waitForCallback(callbackPort, config.callbackPath, state);
|
|
675
|
+
const tokens = await exchangeCode(config, code, verifier, redirectUri);
|
|
625
676
|
const account = {
|
|
626
|
-
provider
|
|
677
|
+
provider,
|
|
627
678
|
accessToken: tokens.accessToken,
|
|
628
679
|
refreshToken: tokens.refreshToken,
|
|
629
680
|
expiresAt: tokens.expiresIn ? Date.now() + tokens.expiresIn * 1e3 : void 0,
|
|
@@ -635,40 +686,6 @@ async function connectAnthropic() {
|
|
|
635
686
|
saveAccount(account);
|
|
636
687
|
return account;
|
|
637
688
|
}
|
|
638
|
-
async function connectOpenAI(apiKey) {
|
|
639
|
-
const res = await fetch("https://api.openai.com/v1/models", {
|
|
640
|
-
headers: { Authorization: `Bearer ${apiKey}` }
|
|
641
|
-
});
|
|
642
|
-
if (!res.ok) {
|
|
643
|
-
const text = await res.text();
|
|
644
|
-
throw new Error(`OpenAI API key invalid (${res.status}): ${text}`);
|
|
645
|
-
}
|
|
646
|
-
const account = {
|
|
647
|
-
provider: "openai",
|
|
648
|
-
accessToken: apiKey,
|
|
649
|
-
enabled: true,
|
|
650
|
-
connectedAt: Date.now()
|
|
651
|
-
};
|
|
652
|
-
saveAccount(account);
|
|
653
|
-
return account;
|
|
654
|
-
}
|
|
655
|
-
async function connectGoogle(apiKey) {
|
|
656
|
-
const res = await fetch(
|
|
657
|
-
`https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`
|
|
658
|
-
);
|
|
659
|
-
if (!res.ok) {
|
|
660
|
-
const text = await res.text();
|
|
661
|
-
throw new Error(`Google API key invalid (${res.status}): ${text}`);
|
|
662
|
-
}
|
|
663
|
-
const account = {
|
|
664
|
-
provider: "google",
|
|
665
|
-
accessToken: apiKey,
|
|
666
|
-
enabled: true,
|
|
667
|
-
connectedAt: Date.now()
|
|
668
|
-
};
|
|
669
|
-
saveAccount(account);
|
|
670
|
-
return account;
|
|
671
|
-
}
|
|
672
689
|
|
|
673
690
|
// src/providers-api.ts
|
|
674
691
|
function extractText(content) {
|
|
@@ -706,7 +723,7 @@ function toAnthropicPayload(messages, model, stream) {
|
|
|
706
723
|
messages: anthropicMsgs
|
|
707
724
|
};
|
|
708
725
|
}
|
|
709
|
-
function toGooglePayload(messages
|
|
726
|
+
function toGooglePayload(messages) {
|
|
710
727
|
const systemParts = [];
|
|
711
728
|
const contents = [];
|
|
712
729
|
for (const m of messages) {
|
|
@@ -739,14 +756,10 @@ async function* parseSSELines(response) {
|
|
|
739
756
|
buffer = lines.pop() ?? "";
|
|
740
757
|
for (const line of lines) {
|
|
741
758
|
const trimmed = line.trim();
|
|
742
|
-
if (trimmed.startsWith("data: "))
|
|
743
|
-
yield trimmed.slice(6);
|
|
744
|
-
}
|
|
759
|
+
if (trimmed.startsWith("data: ")) yield trimmed.slice(6);
|
|
745
760
|
}
|
|
746
761
|
}
|
|
747
|
-
if (buffer.trim().startsWith("data: "))
|
|
748
|
-
yield buffer.trim().slice(6);
|
|
749
|
-
}
|
|
762
|
+
if (buffer.trim().startsWith("data: ")) yield buffer.trim().slice(6);
|
|
750
763
|
} finally {
|
|
751
764
|
reader.releaseLock();
|
|
752
765
|
}
|
|
@@ -762,21 +775,16 @@ var AnthropicProvider = class {
|
|
|
762
775
|
return Boolean(getAccount("anthropic"));
|
|
763
776
|
}
|
|
764
777
|
async models() {
|
|
765
|
-
return [
|
|
766
|
-
"claude-opus-4-6",
|
|
767
|
-
"claude-sonnet-4-6",
|
|
768
|
-
"claude-haiku-4-5-20251001"
|
|
769
|
-
];
|
|
778
|
+
return ["claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5-20251001"];
|
|
770
779
|
}
|
|
771
780
|
async headers() {
|
|
772
|
-
const token = await
|
|
781
|
+
const token = await getValidToken("anthropic");
|
|
773
782
|
if (!token) throw new Error("Anthropic not connected or token expired. Run: apispoof connect anthropic");
|
|
774
783
|
return {
|
|
775
784
|
"Authorization": `Bearer ${token}`,
|
|
776
785
|
"Content-Type": "application/json",
|
|
777
786
|
"anthropic-version": "2023-06-01",
|
|
778
|
-
"anthropic-beta": "oauth-2025-04-20"
|
|
779
|
-
"user-agent": "apispoof/3.0.0"
|
|
787
|
+
"anthropic-beta": "oauth-2025-04-20"
|
|
780
788
|
};
|
|
781
789
|
}
|
|
782
790
|
async runBlocking(messages, model) {
|
|
@@ -787,15 +795,11 @@ var AnthropicProvider = class {
|
|
|
787
795
|
headers,
|
|
788
796
|
body: JSON.stringify(body)
|
|
789
797
|
});
|
|
790
|
-
if (!res.ok) {
|
|
791
|
-
const text2 = await res.text();
|
|
792
|
-
throw new Error(`Anthropic API error (${res.status}): ${text2}`);
|
|
793
|
-
}
|
|
798
|
+
if (!res.ok) throw new Error(`Anthropic API error (${res.status}): ${await res.text()}`);
|
|
794
799
|
const data = await res.json();
|
|
795
800
|
const content = data.content;
|
|
796
801
|
const text = content?.filter((b) => b.type === "text").map((b) => b.text ?? "").join("") ?? "";
|
|
797
|
-
|
|
798
|
-
return [text, usage ?? null];
|
|
802
|
+
return [text, data.usage ?? null];
|
|
799
803
|
}
|
|
800
804
|
async *stream(messages, model) {
|
|
801
805
|
const headers = await this.headers();
|
|
@@ -805,10 +809,7 @@ var AnthropicProvider = class {
|
|
|
805
809
|
headers,
|
|
806
810
|
body: JSON.stringify(body)
|
|
807
811
|
});
|
|
808
|
-
if (!res.ok) {
|
|
809
|
-
const text = await res.text();
|
|
810
|
-
throw new Error(`Anthropic API error (${res.status}): ${text}`);
|
|
811
|
-
}
|
|
812
|
+
if (!res.ok) throw new Error(`Anthropic API error (${res.status}): ${await res.text()}`);
|
|
812
813
|
for await (const line of parseSSELines(res)) {
|
|
813
814
|
if (line === "[DONE]") break;
|
|
814
815
|
try {
|
|
@@ -825,19 +826,19 @@ var AnthropicProvider = class {
|
|
|
825
826
|
var OpenAIProvider = class {
|
|
826
827
|
constructor() {
|
|
827
828
|
this.name = "openai";
|
|
828
|
-
this.displayName = "OpenAI (
|
|
829
|
-
this.type = "
|
|
829
|
+
this.displayName = "OpenAI (OAuth)";
|
|
830
|
+
this.type = "oauth";
|
|
830
831
|
this.prefixes = ["gpt", "o1", "o3", "o4", "chatgpt"];
|
|
831
832
|
}
|
|
832
833
|
available() {
|
|
833
834
|
return Boolean(getAccount("openai"));
|
|
834
835
|
}
|
|
835
836
|
async models() {
|
|
836
|
-
const
|
|
837
|
-
if (!
|
|
837
|
+
const token = await getValidToken("openai");
|
|
838
|
+
if (!token) return [];
|
|
838
839
|
try {
|
|
839
840
|
const res = await fetch("https://api.openai.com/v1/models", {
|
|
840
|
-
headers: { Authorization: `Bearer ${
|
|
841
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
841
842
|
});
|
|
842
843
|
if (!res.ok) return this.fallbackModels();
|
|
843
844
|
const data = await res.json();
|
|
@@ -849,40 +850,32 @@ var OpenAIProvider = class {
|
|
|
849
850
|
fallbackModels() {
|
|
850
851
|
return ["gpt-4o", "gpt-4o-mini", "o3", "o4-mini"];
|
|
851
852
|
}
|
|
852
|
-
|
|
853
|
-
const
|
|
854
|
-
if (!
|
|
855
|
-
return {
|
|
856
|
-
"Authorization": `Bearer ${account.accessToken}`,
|
|
857
|
-
"Content-Type": "application/json"
|
|
858
|
-
};
|
|
853
|
+
async authHeaders() {
|
|
854
|
+
const token = await getValidToken("openai");
|
|
855
|
+
if (!token) throw new Error("OpenAI not connected. Run: apispoof connect openai");
|
|
856
|
+
return { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" };
|
|
859
857
|
}
|
|
860
858
|
async runBlocking(messages, model) {
|
|
859
|
+
const headers = await this.authHeaders();
|
|
861
860
|
const res = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
862
861
|
method: "POST",
|
|
863
|
-
headers
|
|
862
|
+
headers,
|
|
864
863
|
body: JSON.stringify({ model, messages, stream: false })
|
|
865
864
|
});
|
|
866
|
-
if (!res.ok) {
|
|
867
|
-
const text2 = await res.text();
|
|
868
|
-
throw new Error(`OpenAI API error (${res.status}): ${text2}`);
|
|
869
|
-
}
|
|
865
|
+
if (!res.ok) throw new Error(`OpenAI API error (${res.status}): ${await res.text()}`);
|
|
870
866
|
const data = await res.json();
|
|
871
867
|
const choices = data.choices;
|
|
872
868
|
const text = choices?.[0]?.message?.content ?? "";
|
|
873
|
-
|
|
874
|
-
return [text, usage ?? null];
|
|
869
|
+
return [text, data.usage ?? null];
|
|
875
870
|
}
|
|
876
871
|
async *stream(messages, model) {
|
|
872
|
+
const headers = await this.authHeaders();
|
|
877
873
|
const res = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
878
874
|
method: "POST",
|
|
879
|
-
headers
|
|
875
|
+
headers,
|
|
880
876
|
body: JSON.stringify({ model, messages, stream: true })
|
|
881
877
|
});
|
|
882
|
-
if (!res.ok) {
|
|
883
|
-
const text = await res.text();
|
|
884
|
-
throw new Error(`OpenAI API error (${res.status}): ${text}`);
|
|
885
|
-
}
|
|
878
|
+
if (!res.ok) throw new Error(`OpenAI API error (${res.status}): ${await res.text()}`);
|
|
886
879
|
for await (const line of parseSSELines(res)) {
|
|
887
880
|
if (line === "[DONE]") break;
|
|
888
881
|
try {
|
|
@@ -898,20 +891,20 @@ var OpenAIProvider = class {
|
|
|
898
891
|
var GoogleProvider = class {
|
|
899
892
|
constructor() {
|
|
900
893
|
this.name = "google";
|
|
901
|
-
this.displayName = "Google
|
|
902
|
-
this.type = "
|
|
894
|
+
this.displayName = "Google (OAuth)";
|
|
895
|
+
this.type = "oauth";
|
|
903
896
|
this.prefixes = ["gemini"];
|
|
904
897
|
}
|
|
905
898
|
available() {
|
|
906
899
|
return Boolean(getAccount("google"));
|
|
907
900
|
}
|
|
908
901
|
async models() {
|
|
909
|
-
const
|
|
910
|
-
if (!
|
|
902
|
+
const token = await getValidToken("google");
|
|
903
|
+
if (!token) return [];
|
|
911
904
|
try {
|
|
912
|
-
const res = await fetch(
|
|
913
|
-
`
|
|
914
|
-
);
|
|
905
|
+
const res = await fetch("https://generativelanguage.googleapis.com/v1beta/models", {
|
|
906
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
907
|
+
});
|
|
915
908
|
if (!res.ok) return this.fallbackModels();
|
|
916
909
|
const data = await res.json();
|
|
917
910
|
return data.models.filter((m) => m.supportedGenerationMethods?.includes("generateContent")).map((m) => m.name.replace("models/", "")).filter((id) => id.startsWith("gemini")).sort();
|
|
@@ -922,37 +915,32 @@ var GoogleProvider = class {
|
|
|
922
915
|
fallbackModels() {
|
|
923
916
|
return ["gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.0-flash"];
|
|
924
917
|
}
|
|
925
|
-
|
|
926
|
-
const
|
|
927
|
-
if (!
|
|
928
|
-
return
|
|
918
|
+
async authHeaders() {
|
|
919
|
+
const token = await getValidToken("google");
|
|
920
|
+
if (!token) throw new Error("Google not connected. Run: apispoof connect google");
|
|
921
|
+
return { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" };
|
|
929
922
|
}
|
|
930
923
|
async runBlocking(messages, model) {
|
|
931
|
-
const
|
|
924
|
+
const headers = await this.authHeaders();
|
|
925
|
+
const body = toGooglePayload(messages);
|
|
932
926
|
const res = await fetch(
|
|
933
|
-
`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent
|
|
934
|
-
{ method: "POST", headers
|
|
927
|
+
`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`,
|
|
928
|
+
{ method: "POST", headers, body: JSON.stringify(body) }
|
|
935
929
|
);
|
|
936
|
-
if (!res.ok) {
|
|
937
|
-
const text2 = await res.text();
|
|
938
|
-
throw new Error(`Google AI error (${res.status}): ${text2}`);
|
|
939
|
-
}
|
|
930
|
+
if (!res.ok) throw new Error(`Google AI error (${res.status}): ${await res.text()}`);
|
|
940
931
|
const data = await res.json();
|
|
941
932
|
const candidates = data.candidates;
|
|
942
933
|
const text = candidates?.[0]?.content?.parts?.map((p) => p.text).join("") ?? "";
|
|
943
|
-
|
|
944
|
-
return [text, usage ?? null];
|
|
934
|
+
return [text, data.usageMetadata ?? null];
|
|
945
935
|
}
|
|
946
936
|
async *stream(messages, model) {
|
|
947
|
-
const
|
|
937
|
+
const headers = await this.authHeaders();
|
|
938
|
+
const body = toGooglePayload(messages);
|
|
948
939
|
const res = await fetch(
|
|
949
|
-
`https://generativelanguage.googleapis.com/v1beta/models/${model}:streamGenerateContent?alt=sse
|
|
950
|
-
{ method: "POST", headers
|
|
940
|
+
`https://generativelanguage.googleapis.com/v1beta/models/${model}:streamGenerateContent?alt=sse`,
|
|
941
|
+
{ method: "POST", headers, body: JSON.stringify(body) }
|
|
951
942
|
);
|
|
952
|
-
if (!res.ok) {
|
|
953
|
-
const text = await res.text();
|
|
954
|
-
throw new Error(`Google AI error (${res.status}): ${text}`);
|
|
955
|
-
}
|
|
943
|
+
if (!res.ok) throw new Error(`Google AI error (${res.status}): ${await res.text()}`);
|
|
956
944
|
for await (const line of parseSSELines(res)) {
|
|
957
945
|
if (line === "[DONE]") break;
|
|
958
946
|
try {
|
|
@@ -1418,33 +1406,6 @@ function ask(question) {
|
|
|
1418
1406
|
});
|
|
1419
1407
|
});
|
|
1420
1408
|
}
|
|
1421
|
-
function askHidden(question) {
|
|
1422
|
-
return new Promise((resolve) => {
|
|
1423
|
-
const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stdout });
|
|
1424
|
-
process.stdout.write(question);
|
|
1425
|
-
const stdin = process.stdin;
|
|
1426
|
-
if (stdin.setRawMode) stdin.setRawMode(true);
|
|
1427
|
-
let input = "";
|
|
1428
|
-
const onData = (c) => {
|
|
1429
|
-
const ch = c.toString();
|
|
1430
|
-
if (ch === "\n" || ch === "\r") {
|
|
1431
|
-
if (stdin.setRawMode) stdin.setRawMode(false);
|
|
1432
|
-
process.stdin.removeListener("data", onData);
|
|
1433
|
-
process.stdout.write("\n");
|
|
1434
|
-
rl.close();
|
|
1435
|
-
resolve(input);
|
|
1436
|
-
} else if (ch === "") {
|
|
1437
|
-
process.exit(1);
|
|
1438
|
-
} else if (ch === "\x7F") {
|
|
1439
|
-
input = input.slice(0, -1);
|
|
1440
|
-
} else {
|
|
1441
|
-
input += ch;
|
|
1442
|
-
process.stdout.write("*");
|
|
1443
|
-
}
|
|
1444
|
-
};
|
|
1445
|
-
process.stdin.on("data", onData);
|
|
1446
|
-
});
|
|
1447
|
-
}
|
|
1448
1409
|
async function printBanner(port) {
|
|
1449
1410
|
const apiKey = getOrCreateApiKey();
|
|
1450
1411
|
const providers = availableProviders();
|
|
@@ -1465,13 +1426,15 @@ async function printBanner(port) {
|
|
|
1465
1426
|
}
|
|
1466
1427
|
async function cmdConnect(providerArg) {
|
|
1467
1428
|
const providers = [
|
|
1468
|
-
{ name: "anthropic", label: "Anthropic (Claude)",
|
|
1469
|
-
{ name: "openai", label: "OpenAI",
|
|
1470
|
-
{ name: "google", label: "Google
|
|
1429
|
+
{ name: "anthropic", label: "Anthropic (Claude)", hint: "Log in with your Claude account" },
|
|
1430
|
+
{ name: "openai", label: "OpenAI (ChatGPT)", hint: "Log in with your OpenAI account" },
|
|
1431
|
+
{ name: "google", label: "Google (Gemini)", hint: "Log in with your Google account" }
|
|
1471
1432
|
];
|
|
1472
1433
|
let provider;
|
|
1473
1434
|
if (providerArg) {
|
|
1474
|
-
const match = providers.find(
|
|
1435
|
+
const match = providers.find(
|
|
1436
|
+
(p) => p.name === providerArg.toLowerCase() || p.label.toLowerCase().includes(providerArg.toLowerCase())
|
|
1437
|
+
);
|
|
1475
1438
|
if (!match) {
|
|
1476
1439
|
console.error(` Unknown provider: ${providerArg}`);
|
|
1477
1440
|
console.error(` Available: ${providers.map((p) => p.name).join(", ")}`);
|
|
@@ -1480,12 +1443,12 @@ async function cmdConnect(providerArg) {
|
|
|
1480
1443
|
provider = match;
|
|
1481
1444
|
} else {
|
|
1482
1445
|
console.log();
|
|
1483
|
-
console.log(" Connect an AI provider");
|
|
1446
|
+
console.log(" Connect an AI provider (OAuth)");
|
|
1484
1447
|
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
1485
1448
|
console.log();
|
|
1486
1449
|
providers.forEach((p, i) => {
|
|
1487
1450
|
console.log(` ${i + 1}. ${p.label}`);
|
|
1488
|
-
console.log(` ${p.
|
|
1451
|
+
console.log(` ${p.hint}`);
|
|
1489
1452
|
});
|
|
1490
1453
|
console.log();
|
|
1491
1454
|
const choice = await ask(" Choose [1/2/3]: ");
|
|
@@ -1496,44 +1459,14 @@ async function cmdConnect(providerArg) {
|
|
|
1496
1459
|
}
|
|
1497
1460
|
provider = providers[idx];
|
|
1498
1461
|
}
|
|
1499
|
-
console.log();
|
|
1500
|
-
console.log(` Connecting to ${provider.label}...`);
|
|
1501
1462
|
try {
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
console.log(`
|
|
1509
|
-
} else if (provider.name === "openai") {
|
|
1510
|
-
console.log();
|
|
1511
|
-
const key = await askHidden(" Enter your OpenAI API key: ");
|
|
1512
|
-
if (!key) {
|
|
1513
|
-
console.log(" Cancelled.");
|
|
1514
|
-
return;
|
|
1515
|
-
}
|
|
1516
|
-
await connectOpenAI(key);
|
|
1517
|
-
console.log();
|
|
1518
|
-
const op = new OpenAIProvider();
|
|
1519
|
-
const models = await op.models();
|
|
1520
|
-
console.log(` Connected (${models.length} models available)`);
|
|
1521
|
-
const popular = models.filter((m) => /^(gpt-[45]|o[134])/.test(m)).slice(0, 8);
|
|
1522
|
-
if (popular.length) console.log(` Popular: ${popular.join(", ")}`);
|
|
1523
|
-
} else if (provider.name === "google") {
|
|
1524
|
-
console.log();
|
|
1525
|
-
const key = await askHidden(" Enter your Gemini API key: ");
|
|
1526
|
-
if (!key) {
|
|
1527
|
-
console.log(" Cancelled.");
|
|
1528
|
-
return;
|
|
1529
|
-
}
|
|
1530
|
-
await connectGoogle(key);
|
|
1531
|
-
console.log();
|
|
1532
|
-
const gp = new GoogleProvider();
|
|
1533
|
-
const models = await gp.models();
|
|
1534
|
-
console.log(` Connected (${models.length} models available)`);
|
|
1535
|
-
const gemini = models.filter((m) => m.startsWith("gemini")).slice(0, 6);
|
|
1536
|
-
if (gemini.length) console.log(` Models: ${gemini.join(", ")}`);
|
|
1463
|
+
const account = await connectWithOAuth(provider.name);
|
|
1464
|
+
console.log();
|
|
1465
|
+
console.log(` Connected${account.email ? ` as ${account.email}` : ""}!`);
|
|
1466
|
+
const providerInstance = provider.name === "anthropic" ? new AnthropicProvider() : provider.name === "openai" ? new OpenAIProvider() : new GoogleProvider();
|
|
1467
|
+
const models = await providerInstance.models();
|
|
1468
|
+
if (models.length > 0) {
|
|
1469
|
+
console.log(` Models: ${models.slice(0, 8).join(", ")}${models.length > 8 ? ` +${models.length - 8} more` : ""}`);
|
|
1537
1470
|
}
|
|
1538
1471
|
console.log();
|
|
1539
1472
|
} catch (err) {
|
|
@@ -1573,8 +1506,8 @@ function cmdAccounts() {
|
|
|
1573
1506
|
return;
|
|
1574
1507
|
}
|
|
1575
1508
|
for (const a of accounts) {
|
|
1576
|
-
const label = a.provider === "anthropic" ? "Anthropic (Claude)" : a.provider === "openai" ? "OpenAI" : "Google
|
|
1577
|
-
const method =
|
|
1509
|
+
const label = a.provider === "anthropic" ? "Anthropic (Claude)" : a.provider === "openai" ? "OpenAI (ChatGPT)" : "Google (Gemini)";
|
|
1510
|
+
const method = "OAuth";
|
|
1578
1511
|
const since = new Date(a.connectedAt).toLocaleDateString();
|
|
1579
1512
|
const status = a.enabled ? "active" : "disabled";
|
|
1580
1513
|
console.log(` ${a.enabled ? "+" : "-"} ${label}`);
|
|
@@ -1951,8 +1884,8 @@ function showHelp() {
|
|
|
1951
1884
|
apispoof backend List all CLI backends
|
|
1952
1885
|
apispoof backend add Add a custom CLI backend
|
|
1953
1886
|
|
|
1954
|
-
Providers
|
|
1955
|
-
CLI
|
|
1887
|
+
Providers (all OAuth): anthropic, openai, google
|
|
1888
|
+
CLI fallbacks: claude, gemini, codex, copilot, droid
|
|
1956
1889
|
`.trim());
|
|
1957
1890
|
}
|
|
1958
1891
|
function parseArgs() {
|
package/package.json
CHANGED