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