bashio 0.7.0 → 1.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/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/index.ts
4
+ import { createRequire } from "module";
4
5
  import { Builtins, Cli } from "clipanion";
5
6
  import pc17 from "picocolors";
6
7
  import updateNotifier from "update-notifier";
@@ -20,7 +21,10 @@ import { join } from "path";
20
21
  import { z } from "zod";
21
22
  var ProviderName = z.enum([
22
23
  "claude",
24
+ "claude-subscription",
23
25
  "openai",
26
+ "chatgpt-subscription",
27
+ "copilot",
24
28
  "ollama",
25
29
  "openrouter"
26
30
  ]);
@@ -36,10 +40,41 @@ var LocalCredentials = z.object({
36
40
  type: z.literal("local"),
37
41
  host: z.string().default("http://localhost:11434")
38
42
  });
43
+ var ClaudeSubscriptionCredentials = z.object({
44
+ type: z.literal("claude_subscription"),
45
+ accessToken: z.string(),
46
+ refreshToken: z.string(),
47
+ expiresAt: z.number(),
48
+ // Unix timestamp in ms
49
+ email: z.string().optional()
50
+ });
51
+ var ChatGPTSubscriptionCredentials = z.object({
52
+ type: z.literal("chatgpt_subscription"),
53
+ accessToken: z.string(),
54
+ refreshToken: z.string().optional(),
55
+ expiresAt: z.number().optional(),
56
+ // Unix timestamp in ms
57
+ accountId: z.string().optional()
58
+ // ChatGPT account ID for API requests
59
+ });
60
+ var CopilotCredentials = z.object({
61
+ type: z.literal("copilot"),
62
+ githubToken: z.string(),
63
+ // GitHub OAuth access token (gho_xxx)
64
+ copilotToken: z.string(),
65
+ // Copilot API token (short-lived)
66
+ copilotTokenExpiresAt: z.number(),
67
+ // Unix timestamp in ms
68
+ apiEndpoint: z.string().optional()
69
+ // Derived from token (api.individual/business.githubcopilot.com)
70
+ });
39
71
  var Credentials = z.discriminatedUnion("type", [
40
72
  SessionCredentials,
41
73
  ApiKeyCredentials,
42
- LocalCredentials
74
+ LocalCredentials,
75
+ ClaudeSubscriptionCredentials,
76
+ ChatGPTSubscriptionCredentials,
77
+ CopilotCredentials
43
78
  ]);
44
79
  var Settings = z.object({
45
80
  confirmBeforeExecute: z.boolean().default(true),
@@ -587,6 +622,392 @@ import pc4 from "picocolors";
587
622
  import { input as input3, password, select } from "@inquirer/prompts";
588
623
  import pc3 from "picocolors";
589
624
 
625
+ // src/core/oauth.ts
626
+ import * as crypto from "crypto";
627
+ import * as http from "http";
628
+ import { URL as URL2 } from "url";
629
+ var CLAUDE_OAUTH_CONFIG = {
630
+ clientId: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
631
+ authorizationUrl: "https://claude.ai/oauth/authorize",
632
+ tokenUrl: "https://console.anthropic.com/v1/oauth/token",
633
+ redirectUri: "http://localhost:8765/callback",
634
+ scopes: "org:create_api_key user:profile user:inference",
635
+ callbackPort: 8765
636
+ };
637
+ var COPILOT_OAUTH_CONFIG = {
638
+ clientId: "Iv1.b507a08c87ecfe98",
639
+ // Official GitHub Copilot client ID
640
+ deviceCodeUrl: "https://github.com/login/device/code",
641
+ accessTokenUrl: "https://github.com/login/oauth/access_token",
642
+ copilotTokenUrl: "https://api.github.com/copilot_internal/v2/token",
643
+ apiEndpoint: "https://api.githubcopilot.com/chat/completions",
644
+ scope: "read:user"
645
+ };
646
+ var CHATGPT_OAUTH_CONFIG = {
647
+ clientId: "app_EMoamEEZ73f0CkXaXp7hrann",
648
+ // Official Codex CLI client ID
649
+ authorizationUrl: "https://auth.openai.com/oauth/authorize",
650
+ tokenUrl: "https://auth.openai.com/oauth/token",
651
+ redirectUri: "http://localhost:1455/auth/callback",
652
+ scopes: "openid profile email offline_access",
653
+ callbackPort: 1455,
654
+ audience: "https://api.openai.com/v1",
655
+ // Backend API endpoint (subscription OAuth uses this, NOT api.openai.com)
656
+ apiEndpoint: "https://chatgpt.com/backend-api/codex/responses"
657
+ };
658
+ function generateCodeVerifier() {
659
+ return crypto.randomBytes(32).toString("base64url");
660
+ }
661
+ function generateCodeChallenge(verifier) {
662
+ return crypto.createHash("sha256").update(verifier).digest("base64url");
663
+ }
664
+ function generateState() {
665
+ return crypto.randomBytes(16).toString("hex");
666
+ }
667
+ function generatePKCE() {
668
+ const codeVerifier = generateCodeVerifier();
669
+ const codeChallenge = generateCodeChallenge(codeVerifier);
670
+ const state = generateState();
671
+ return { codeVerifier, codeChallenge, state };
672
+ }
673
+ function buildClaudeAuthUrl(pkce) {
674
+ const params = new URLSearchParams({
675
+ client_id: CLAUDE_OAUTH_CONFIG.clientId,
676
+ redirect_uri: CLAUDE_OAUTH_CONFIG.redirectUri,
677
+ scope: CLAUDE_OAUTH_CONFIG.scopes,
678
+ code_challenge: pkce.codeChallenge,
679
+ code_challenge_method: "S256",
680
+ response_type: "code",
681
+ state: pkce.state
682
+ });
683
+ return `${CLAUDE_OAUTH_CONFIG.authorizationUrl}?${params.toString()}`;
684
+ }
685
+ async function exchangeClaudeCode(code, codeVerifier, state) {
686
+ const body = {
687
+ code,
688
+ state,
689
+ grant_type: "authorization_code",
690
+ client_id: CLAUDE_OAUTH_CONFIG.clientId,
691
+ redirect_uri: CLAUDE_OAUTH_CONFIG.redirectUri,
692
+ code_verifier: codeVerifier
693
+ };
694
+ const response = await fetch(CLAUDE_OAUTH_CONFIG.tokenUrl, {
695
+ method: "POST",
696
+ headers: { "Content-Type": "application/json" },
697
+ body: JSON.stringify(body)
698
+ });
699
+ if (!response.ok) {
700
+ const errorText = await response.text();
701
+ throw new Error(
702
+ `Claude token exchange failed: ${response.status} - ${errorText}`
703
+ );
704
+ }
705
+ const data = await response.json();
706
+ if (!data.refresh_token) {
707
+ throw new Error("Claude token exchange did not return a refresh_token");
708
+ }
709
+ return {
710
+ accessToken: data.access_token,
711
+ refreshToken: data.refresh_token,
712
+ expiresAt: Date.now() + data.expires_in * 1e3,
713
+ email: data.email
714
+ };
715
+ }
716
+ async function refreshClaudeToken(refreshToken) {
717
+ const body = {
718
+ grant_type: "refresh_token",
719
+ client_id: CLAUDE_OAUTH_CONFIG.clientId,
720
+ refresh_token: refreshToken
721
+ };
722
+ const response = await fetch(CLAUDE_OAUTH_CONFIG.tokenUrl, {
723
+ method: "POST",
724
+ headers: { "Content-Type": "application/json" },
725
+ body: JSON.stringify(body)
726
+ });
727
+ if (!response.ok) {
728
+ const errorText = await response.text();
729
+ throw new Error(
730
+ `Claude token refresh failed: ${response.status} - ${errorText}`
731
+ );
732
+ }
733
+ const data = await response.json();
734
+ return {
735
+ accessToken: data.access_token,
736
+ refreshToken: data.refresh_token || refreshToken,
737
+ expiresAt: Date.now() + data.expires_in * 1e3,
738
+ email: data.email
739
+ };
740
+ }
741
+ function isClaudeTokenExpired(expiresAt) {
742
+ const bufferMs = 5 * 60 * 1e3;
743
+ return Date.now() >= expiresAt - bufferMs;
744
+ }
745
+ function parseJWTClaims(token) {
746
+ try {
747
+ const parts = token.split(".");
748
+ if (parts.length !== 3) return null;
749
+ const payload = parts[1];
750
+ const decoded = Buffer.from(payload, "base64url").toString("utf-8");
751
+ return JSON.parse(decoded);
752
+ } catch {
753
+ return null;
754
+ }
755
+ }
756
+ function extractAccountIdFromToken(accessToken, idToken) {
757
+ const tokens = idToken ? [idToken, accessToken] : [accessToken];
758
+ for (const token of tokens) {
759
+ const claims = parseJWTClaims(token);
760
+ if (!claims) continue;
761
+ const accountId = claims.chatgpt_account_id || claims["https://api.openai.com/auth"]?.chatgpt_account_id || claims.organizations?.[0]?.id;
762
+ if (accountId) return accountId;
763
+ }
764
+ return void 0;
765
+ }
766
+ function buildChatGPTAuthUrl(pkce) {
767
+ const params = new URLSearchParams({
768
+ client_id: CHATGPT_OAUTH_CONFIG.clientId,
769
+ redirect_uri: CHATGPT_OAUTH_CONFIG.redirectUri,
770
+ scope: CHATGPT_OAUTH_CONFIG.scopes,
771
+ code_challenge: pkce.codeChallenge,
772
+ code_challenge_method: "S256",
773
+ response_type: "code",
774
+ state: pkce.state,
775
+ audience: CHATGPT_OAUTH_CONFIG.audience
776
+ });
777
+ return `${CHATGPT_OAUTH_CONFIG.authorizationUrl}?${params.toString()}`;
778
+ }
779
+ async function exchangeChatGPTCode(code, codeVerifier) {
780
+ const body = new URLSearchParams({
781
+ grant_type: "authorization_code",
782
+ client_id: CHATGPT_OAUTH_CONFIG.clientId,
783
+ code,
784
+ redirect_uri: CHATGPT_OAUTH_CONFIG.redirectUri,
785
+ code_verifier: codeVerifier
786
+ });
787
+ const response = await fetch(CHATGPT_OAUTH_CONFIG.tokenUrl, {
788
+ method: "POST",
789
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
790
+ body: body.toString()
791
+ });
792
+ if (!response.ok) {
793
+ const errorText = await response.text();
794
+ throw new Error(
795
+ `ChatGPT token exchange failed: ${response.status} - ${errorText}`
796
+ );
797
+ }
798
+ const data = await response.json();
799
+ const accountId = extractAccountIdFromToken(data.access_token, data.id_token);
800
+ return {
801
+ accessToken: data.access_token,
802
+ refreshToken: data.refresh_token || "",
803
+ expiresAt: data.expires_in ? Date.now() + data.expires_in * 1e3 : 0,
804
+ accountId
805
+ };
806
+ }
807
+ async function refreshChatGPTToken(refreshToken) {
808
+ const body = new URLSearchParams({
809
+ grant_type: "refresh_token",
810
+ client_id: CHATGPT_OAUTH_CONFIG.clientId,
811
+ refresh_token: refreshToken
812
+ });
813
+ const response = await fetch(CHATGPT_OAUTH_CONFIG.tokenUrl, {
814
+ method: "POST",
815
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
816
+ body: body.toString()
817
+ });
818
+ if (!response.ok) {
819
+ const errorText = await response.text();
820
+ throw new Error(
821
+ `ChatGPT token refresh failed: ${response.status} - ${errorText}`
822
+ );
823
+ }
824
+ const data = await response.json();
825
+ return {
826
+ accessToken: data.access_token,
827
+ refreshToken: data.refresh_token || refreshToken,
828
+ expiresAt: data.expires_in ? Date.now() + data.expires_in * 1e3 : 0
829
+ };
830
+ }
831
+ function startCallbackServer(port, expectedState, timeoutMs = 5 * 60 * 1e3) {
832
+ return new Promise((resolve, reject) => {
833
+ const server = http.createServer((req, res) => {
834
+ const parsedUrl = new URL2(req.url || "", `http://localhost:${port}`);
835
+ if (parsedUrl.pathname !== "/callback" && parsedUrl.pathname !== "/auth/callback") {
836
+ res.writeHead(404);
837
+ res.end("Not Found");
838
+ return;
839
+ }
840
+ const code = parsedUrl.searchParams.get("code");
841
+ const state = parsedUrl.searchParams.get("state");
842
+ const error = parsedUrl.searchParams.get("error");
843
+ if (error) {
844
+ res.writeHead(400, { "Content-Type": "text/html" });
845
+ res.end(
846
+ `<html><body><h1>Authentication Failed</h1><p>${error}</p></body></html>`
847
+ );
848
+ server.close();
849
+ reject(new Error(`OAuth error: ${error}`));
850
+ return;
851
+ }
852
+ if (!code || !state) {
853
+ res.writeHead(400, { "Content-Type": "text/html" });
854
+ res.end("<html><body><h1>Missing Parameters</h1></body></html>");
855
+ server.close();
856
+ reject(new Error("Missing code or state parameter"));
857
+ return;
858
+ }
859
+ if (state !== expectedState) {
860
+ res.writeHead(400, { "Content-Type": "text/html" });
861
+ res.end(
862
+ "<html><body><h1>Invalid State</h1><p>Possible CSRF attack.</p></body></html>"
863
+ );
864
+ server.close();
865
+ reject(new Error("State mismatch - possible CSRF attack"));
866
+ return;
867
+ }
868
+ res.writeHead(200, { "Content-Type": "text/html" });
869
+ res.end(`
870
+ <!DOCTYPE html>
871
+ <html>
872
+ <head><title>Authentication Successful</title></head>
873
+ <body style="font-family: system-ui; text-align: center; padding: 50px;">
874
+ <h1>Authentication Successful!</h1>
875
+ <p>You can close this window and return to your terminal.</p>
876
+ <script>setTimeout(() => window.close(), 2000);</script>
877
+ </body>
878
+ </html>
879
+ `);
880
+ server.close();
881
+ resolve({ code, state });
882
+ });
883
+ server.on("error", (err) => {
884
+ if (err.code === "EADDRINUSE") {
885
+ reject(
886
+ new Error(
887
+ `Port ${port} is already in use. Close other applications using this port.`
888
+ )
889
+ );
890
+ } else {
891
+ reject(err);
892
+ }
893
+ });
894
+ const timeout = setTimeout(() => {
895
+ server.close();
896
+ reject(new Error("Authentication timed out after 5 minutes"));
897
+ }, timeoutMs);
898
+ server.on("close", () => clearTimeout(timeout));
899
+ server.listen(port, "127.0.0.1");
900
+ });
901
+ }
902
+ async function requestCopilotDeviceCode() {
903
+ const body = new URLSearchParams({
904
+ client_id: COPILOT_OAUTH_CONFIG.clientId,
905
+ scope: COPILOT_OAUTH_CONFIG.scope
906
+ });
907
+ const response = await fetch(COPILOT_OAUTH_CONFIG.deviceCodeUrl, {
908
+ method: "POST",
909
+ headers: {
910
+ Accept: "application/json",
911
+ "Content-Type": "application/x-www-form-urlencoded"
912
+ },
913
+ body: body.toString()
914
+ });
915
+ if (!response.ok) {
916
+ const errorText = await response.text();
917
+ throw new Error(
918
+ `Failed to request device code: ${response.status} - ${errorText}`
919
+ );
920
+ }
921
+ return await response.json();
922
+ }
923
+ async function pollForCopilotAccessToken(deviceCode, intervalMs, expiresAt) {
924
+ const body = new URLSearchParams({
925
+ client_id: COPILOT_OAUTH_CONFIG.clientId,
926
+ device_code: deviceCode,
927
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
928
+ });
929
+ while (Date.now() < expiresAt) {
930
+ const response = await fetch(COPILOT_OAUTH_CONFIG.accessTokenUrl, {
931
+ method: "POST",
932
+ headers: {
933
+ Accept: "application/json",
934
+ "Content-Type": "application/x-www-form-urlencoded"
935
+ },
936
+ body: body.toString()
937
+ });
938
+ const data = await response.json();
939
+ if (data.access_token) {
940
+ return data.access_token;
941
+ }
942
+ if (data.error === "authorization_pending") {
943
+ await new Promise((r) => setTimeout(r, intervalMs));
944
+ continue;
945
+ }
946
+ if (data.error === "slow_down") {
947
+ await new Promise((r) => setTimeout(r, intervalMs + 5e3));
948
+ continue;
949
+ }
950
+ if (data.error === "expired_token") {
951
+ throw new Error("Device code expired. Please try again.");
952
+ }
953
+ if (data.error === "access_denied") {
954
+ throw new Error("Access denied. User cancelled authorization.");
955
+ }
956
+ throw new Error(`GitHub OAuth error: ${data.error || "Unknown error"}`);
957
+ }
958
+ throw new Error("Authorization timed out. Please try again.");
959
+ }
960
+ async function exchangeGitHubTokenForCopilot(githubToken) {
961
+ const response = await fetch(COPILOT_OAUTH_CONFIG.copilotTokenUrl, {
962
+ method: "GET",
963
+ headers: {
964
+ Accept: "application/json",
965
+ Authorization: `Bearer ${githubToken}`
966
+ }
967
+ });
968
+ if (!response.ok) {
969
+ const errorText = await response.text();
970
+ if (response.status === 401) {
971
+ throw new Error("GitHub token is invalid or expired.");
972
+ }
973
+ if (response.status === 403) {
974
+ throw new Error(
975
+ "You do not have access to GitHub Copilot. Please ensure you have an active Copilot subscription."
976
+ );
977
+ }
978
+ throw new Error(
979
+ `Failed to get Copilot token: ${response.status} - ${errorText}`
980
+ );
981
+ }
982
+ return await response.json();
983
+ }
984
+ function parseCopilotToken(token) {
985
+ let expiresAt = Date.now() + 30 * 60 * 1e3;
986
+ let apiEndpoint = COPILOT_OAUTH_CONFIG.apiEndpoint;
987
+ const pairs = token.split(";");
988
+ for (const pair of pairs) {
989
+ const [key, value] = pair.split("=");
990
+ if (key?.trim() === "exp" && value) {
991
+ expiresAt = Number.parseInt(value.trim(), 10) * 1e3;
992
+ }
993
+ if (key?.trim() === "proxy-ep" && value) {
994
+ let proxyUrl = value.trim();
995
+ if (!proxyUrl.startsWith("http")) {
996
+ proxyUrl = `https://${proxyUrl}`;
997
+ }
998
+ apiEndpoint = proxyUrl.replace(/\/\/proxy\./i, "//api.");
999
+ if (!apiEndpoint.includes("/chat/completions")) {
1000
+ apiEndpoint = `${apiEndpoint}/chat/completions`;
1001
+ }
1002
+ }
1003
+ }
1004
+ return { expiresAt, apiEndpoint };
1005
+ }
1006
+ function isCopilotTokenExpired(expiresAt) {
1007
+ const bufferMs = 5 * 60 * 1e3;
1008
+ return Date.now() >= expiresAt - bufferMs;
1009
+ }
1010
+
590
1011
  // src/providers/base.ts
591
1012
  var SYSTEM_PROMPT_GENERATE = `You are a shell command generator for macOS/Linux terminals.
592
1013
  Given a natural language description, return ONLY the shell command.
@@ -602,6 +1023,181 @@ Explain the given shell command in simple terms.
602
1023
  Break down each part of the command concisely.
603
1024
  Format as a simple list without markdown.`;
604
1025
 
1026
+ // src/providers/chatgpt-subscription.ts
1027
+ var ChatGPTSubscriptionProvider = class {
1028
+ name = "ChatGPT (Subscription)";
1029
+ model;
1030
+ accessToken;
1031
+ refreshToken;
1032
+ expiresAt;
1033
+ accountId;
1034
+ constructor(config) {
1035
+ this.model = config.model;
1036
+ if (config.credentials.type === "chatgpt_subscription") {
1037
+ this.accessToken = config.credentials.accessToken;
1038
+ this.refreshToken = config.credentials.refreshToken;
1039
+ this.expiresAt = config.credentials.expiresAt;
1040
+ this.accountId = config.credentials.accountId;
1041
+ if (!this.accountId) {
1042
+ this.accountId = extractAccountIdFromToken(this.accessToken);
1043
+ }
1044
+ } else {
1045
+ throw new Error("ChatGPT Subscription requires subscription credentials");
1046
+ }
1047
+ }
1048
+ isTokenExpired() {
1049
+ if (!this.expiresAt) return false;
1050
+ const bufferMs = 5 * 60 * 1e3;
1051
+ return Date.now() >= this.expiresAt - bufferMs;
1052
+ }
1053
+ async ensureValidToken() {
1054
+ if (this.isTokenExpired() && this.refreshToken) {
1055
+ try {
1056
+ const newTokens = await refreshChatGPTToken(this.refreshToken);
1057
+ this.accessToken = newTokens.accessToken;
1058
+ this.refreshToken = newTokens.refreshToken || this.refreshToken;
1059
+ this.expiresAt = newTokens.expiresAt || this.expiresAt;
1060
+ if (newTokens.accountId) {
1061
+ this.accountId = newTokens.accountId;
1062
+ } else if (!this.accountId) {
1063
+ this.accountId = extractAccountIdFromToken(this.accessToken);
1064
+ }
1065
+ const currentConfig = loadConfig();
1066
+ if (currentConfig && currentConfig.credentials.type === "chatgpt_subscription") {
1067
+ currentConfig.credentials.accessToken = newTokens.accessToken;
1068
+ if (newTokens.refreshToken) {
1069
+ currentConfig.credentials.refreshToken = newTokens.refreshToken;
1070
+ }
1071
+ if (newTokens.expiresAt) {
1072
+ currentConfig.credentials.expiresAt = newTokens.expiresAt;
1073
+ }
1074
+ if (this.accountId) {
1075
+ currentConfig.credentials.accountId = this.accountId;
1076
+ }
1077
+ saveConfig(currentConfig);
1078
+ }
1079
+ } catch (error) {
1080
+ throw new Error(
1081
+ `Token refresh failed. Please re-authenticate with: b --auth
1082
+ Error: ${error instanceof Error ? error.message : "Unknown error"}`
1083
+ );
1084
+ }
1085
+ }
1086
+ return this.accessToken;
1087
+ }
1088
+ async call(systemPrompt, userMessage) {
1089
+ const token = await this.ensureValidToken();
1090
+ const requestBody = {
1091
+ model: this.model,
1092
+ instructions: systemPrompt,
1093
+ input: [
1094
+ {
1095
+ type: "message",
1096
+ role: "user",
1097
+ content: [{ type: "input_text", text: userMessage }]
1098
+ }
1099
+ ],
1100
+ store: false,
1101
+ stream: true
1102
+ };
1103
+ const headers = {
1104
+ "Content-Type": "application/json",
1105
+ Authorization: `Bearer ${token}`,
1106
+ "OpenAI-Beta": "responses=experimental",
1107
+ originator: "bashio"
1108
+ };
1109
+ if (this.accountId) {
1110
+ headers["ChatGPT-Account-Id"] = this.accountId;
1111
+ }
1112
+ const response = await fetch(CHATGPT_OAUTH_CONFIG.apiEndpoint, {
1113
+ method: "POST",
1114
+ headers,
1115
+ body: JSON.stringify(requestBody)
1116
+ });
1117
+ if (!response.ok) {
1118
+ const errorText = await response.text();
1119
+ if (response.status === 401) {
1120
+ throw new Error(
1121
+ "Authentication failed. Please re-authenticate with: b --auth"
1122
+ );
1123
+ }
1124
+ if (response.status === 403) {
1125
+ throw new Error(
1126
+ `Access forbidden. Your ChatGPT subscription may not have access to this model.
1127
+ Error: ${errorText}`
1128
+ );
1129
+ }
1130
+ throw new Error(`ChatGPT API error: ${response.status} - ${errorText}`);
1131
+ }
1132
+ return this.parseStreamingResponse(response);
1133
+ }
1134
+ async parseStreamingResponse(response) {
1135
+ if (!response.body) {
1136
+ throw new Error("No response body from ChatGPT");
1137
+ }
1138
+ const reader = response.body.getReader();
1139
+ const decoder = new TextDecoder("utf-8");
1140
+ let buffer = "";
1141
+ let fullText = "";
1142
+ try {
1143
+ while (true) {
1144
+ const { done, value } = await reader.read();
1145
+ if (done) break;
1146
+ buffer += decoder.decode(value, { stream: true });
1147
+ const lines = buffer.split("\n");
1148
+ buffer = lines.pop() || "";
1149
+ for (const line of lines) {
1150
+ if (!line.trim() || !line.startsWith("data:")) continue;
1151
+ const data = line.substring(5).trim();
1152
+ if (data === "[DONE]") {
1153
+ return fullText.trim();
1154
+ }
1155
+ try {
1156
+ const chunk = JSON.parse(data);
1157
+ if (chunk.type === "response.output_text.delta" && chunk.delta) {
1158
+ fullText += chunk.delta;
1159
+ } else if (chunk.choices?.[0]?.delta?.content) {
1160
+ fullText += chunk.choices[0].delta.content;
1161
+ }
1162
+ } catch {
1163
+ }
1164
+ }
1165
+ }
1166
+ } finally {
1167
+ reader.releaseLock();
1168
+ }
1169
+ if (!fullText) {
1170
+ throw new Error("No response content from ChatGPT");
1171
+ }
1172
+ return fullText.trim();
1173
+ }
1174
+ async generateCommand(query, context) {
1175
+ const userMessage = context ? `Context: ${context}
1176
+
1177
+ Task: ${query}` : query;
1178
+ return this.call(SYSTEM_PROMPT_GENERATE, userMessage);
1179
+ }
1180
+ async explainCommand(command) {
1181
+ return this.call(SYSTEM_PROMPT_EXPLAIN, `Explain this command: ${command}`);
1182
+ }
1183
+ async validateCredentials() {
1184
+ try {
1185
+ const token = await this.ensureValidToken();
1186
+ return !!token && token.length > 0;
1187
+ } catch {
1188
+ return false;
1189
+ }
1190
+ }
1191
+ };
1192
+ var CHATGPT_SUBSCRIPTION_MODELS = [
1193
+ { value: "gpt-5.2-codex", label: "GPT-5.2-Codex (recommended for coding)" },
1194
+ { value: "gpt-5.2", label: "GPT-5.2 (most intelligent)" },
1195
+ { value: "gpt-5.1-codex-max", label: "GPT-5.1-Codex-Max (long tasks)" },
1196
+ { value: "gpt-5.1-codex-mini", label: "GPT-5.1-Codex-Mini (fast)" },
1197
+ { value: "o4-mini", label: "o4-mini (reasoning)" },
1198
+ { value: "gpt-4o", label: "GPT-4o (legacy)" }
1199
+ ];
1200
+
605
1201
  // src/providers/claude.ts
606
1202
  var ClaudeProvider = class {
607
1203
  name = "Claude";
@@ -680,9 +1276,337 @@ Task: ${query}` : query;
680
1276
  }
681
1277
  };
682
1278
  var CLAUDE_MODELS = [
683
- { value: "claude-sonnet-4-20250514", label: "Claude Sonnet 4 (recommended)" },
684
- { value: "claude-3-5-sonnet-20241022", label: "Claude 3.5 Sonnet" },
685
- { value: "claude-3-5-haiku-20241022", label: "Claude 3.5 Haiku (fast)" }
1279
+ {
1280
+ value: "claude-opus-4-5-20251101",
1281
+ label: "Claude Opus 4.5 (most intelligent)"
1282
+ },
1283
+ {
1284
+ value: "claude-sonnet-4-5-20250929",
1285
+ label: "Claude Sonnet 4.5 (recommended)"
1286
+ },
1287
+ { value: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5 (fast)" },
1288
+ { value: "claude-sonnet-4-20250514", label: "Claude Sonnet 4" },
1289
+ { value: "claude-opus-4-20250514", label: "Claude Opus 4" }
1290
+ ];
1291
+
1292
+ // src/providers/claude-subscription.ts
1293
+ var CLAUDE_CODE_PREFIX = "You are Claude Code, Anthropic's official CLI for Claude.";
1294
+ var CLAUDE_CODE_BETAS = [
1295
+ "oauth-2025-04-20",
1296
+ "claude-code-20250219",
1297
+ "interleaved-thinking-2025-05-14",
1298
+ "fine-grained-tool-streaming-2025-05-14"
1299
+ ].join(",");
1300
+ var ClaudeSubscriptionProvider = class {
1301
+ name = "Claude (Subscription)";
1302
+ model;
1303
+ accessToken;
1304
+ refreshToken;
1305
+ expiresAt;
1306
+ constructor(config) {
1307
+ this.model = config.model;
1308
+ if (config.credentials.type === "claude_subscription") {
1309
+ this.accessToken = config.credentials.accessToken;
1310
+ this.refreshToken = config.credentials.refreshToken;
1311
+ this.expiresAt = config.credentials.expiresAt;
1312
+ } else {
1313
+ throw new Error("Claude Subscription requires subscription credentials");
1314
+ }
1315
+ }
1316
+ async ensureValidToken() {
1317
+ if (isClaudeTokenExpired(this.expiresAt)) {
1318
+ try {
1319
+ const newTokens = await refreshClaudeToken(this.refreshToken);
1320
+ this.accessToken = newTokens.accessToken;
1321
+ this.refreshToken = newTokens.refreshToken;
1322
+ this.expiresAt = newTokens.expiresAt;
1323
+ const currentConfig = loadConfig();
1324
+ if (currentConfig && currentConfig.credentials.type === "claude_subscription") {
1325
+ currentConfig.credentials.accessToken = newTokens.accessToken;
1326
+ currentConfig.credentials.refreshToken = newTokens.refreshToken;
1327
+ currentConfig.credentials.expiresAt = newTokens.expiresAt;
1328
+ saveConfig(currentConfig);
1329
+ }
1330
+ } catch (error) {
1331
+ throw new Error(
1332
+ `Token refresh failed. Please re-authenticate with: b --auth
1333
+ Error: ${error instanceof Error ? error.message : "Unknown error"}`
1334
+ );
1335
+ }
1336
+ }
1337
+ return this.accessToken;
1338
+ }
1339
+ async call(systemPrompt, userMessage) {
1340
+ const token = await this.ensureValidToken();
1341
+ const messages = [
1342
+ { role: "user", content: userMessage }
1343
+ ];
1344
+ const systemBlocks = [
1345
+ { type: "text", text: CLAUDE_CODE_PREFIX },
1346
+ { type: "text", text: systemPrompt }
1347
+ ];
1348
+ const headers = {
1349
+ Authorization: `Bearer ${token}`,
1350
+ "Content-Type": "application/json",
1351
+ "anthropic-version": "2023-06-01",
1352
+ "anthropic-beta": CLAUDE_CODE_BETAS,
1353
+ "anthropic-dangerous-direct-browser-access": "true",
1354
+ "user-agent": "claude-cli/1.0.119 (external, cli)",
1355
+ "x-app": "cli",
1356
+ accept: "application/json"
1357
+ };
1358
+ const response = await fetch(
1359
+ "https://api.anthropic.com/v1/messages?beta=true",
1360
+ {
1361
+ method: "POST",
1362
+ headers,
1363
+ body: JSON.stringify({
1364
+ model: this.model,
1365
+ max_tokens: 1024,
1366
+ system: systemBlocks,
1367
+ messages
1368
+ })
1369
+ }
1370
+ );
1371
+ if (!response.ok) {
1372
+ const error = await response.text();
1373
+ if (response.status === 401) {
1374
+ throw new Error(
1375
+ "Authentication failed. Please re-authenticate with: b --auth"
1376
+ );
1377
+ }
1378
+ throw new Error(`Claude API error: ${response.status} - ${error}`);
1379
+ }
1380
+ const data = await response.json();
1381
+ if (data.error) {
1382
+ throw new Error(`Claude API error: ${data.error.message}`);
1383
+ }
1384
+ const textContent = data.content.find((c) => c.type === "text");
1385
+ if (!textContent) {
1386
+ throw new Error("No text response from Claude");
1387
+ }
1388
+ return textContent.text.trim();
1389
+ }
1390
+ async generateCommand(query, context) {
1391
+ const userMessage = context ? `Context: ${context}
1392
+
1393
+ Task: ${query}` : query;
1394
+ return this.call(SYSTEM_PROMPT_GENERATE, userMessage);
1395
+ }
1396
+ async explainCommand(command) {
1397
+ return this.call(SYSTEM_PROMPT_EXPLAIN, `Explain this command: ${command}`);
1398
+ }
1399
+ async validateCredentials() {
1400
+ try {
1401
+ const token = await this.ensureValidToken();
1402
+ const headers = {
1403
+ Authorization: `Bearer ${token}`,
1404
+ "Content-Type": "application/json",
1405
+ "anthropic-version": "2023-06-01",
1406
+ "anthropic-beta": CLAUDE_CODE_BETAS,
1407
+ "anthropic-dangerous-direct-browser-access": "true",
1408
+ "user-agent": "claude-cli/1.0.119 (external, cli)",
1409
+ "x-app": "cli",
1410
+ accept: "application/json"
1411
+ };
1412
+ const response = await fetch(
1413
+ "https://api.anthropic.com/v1/messages?beta=true",
1414
+ {
1415
+ method: "POST",
1416
+ headers,
1417
+ body: JSON.stringify({
1418
+ model: this.model,
1419
+ max_tokens: 10,
1420
+ system: [{ type: "text", text: CLAUDE_CODE_PREFIX }],
1421
+ messages: [{ role: "user", content: "hi" }]
1422
+ })
1423
+ }
1424
+ );
1425
+ return response.ok;
1426
+ } catch {
1427
+ return false;
1428
+ }
1429
+ }
1430
+ };
1431
+ var CLAUDE_SUBSCRIPTION_MODELS = [
1432
+ {
1433
+ value: "claude-opus-4-5-20251101",
1434
+ label: "Claude Opus 4.5 (most intelligent)"
1435
+ },
1436
+ {
1437
+ value: "claude-sonnet-4-5-20250929",
1438
+ label: "Claude Sonnet 4.5 (recommended)"
1439
+ },
1440
+ { value: "claude-haiku-4-5-20251001", label: "Claude Haiku 4.5 (fast)" },
1441
+ { value: "claude-sonnet-4-20250514", label: "Claude Sonnet 4" },
1442
+ { value: "claude-opus-4-20250514", label: "Claude Opus 4 (Max plan only)" }
1443
+ ];
1444
+
1445
+ // src/providers/copilot.ts
1446
+ var CopilotProvider = class {
1447
+ name = "GitHub Copilot";
1448
+ model;
1449
+ githubToken;
1450
+ copilotToken;
1451
+ copilotTokenExpiresAt;
1452
+ apiEndpoint;
1453
+ constructor(config) {
1454
+ this.model = config.model;
1455
+ if (config.credentials.type === "copilot") {
1456
+ this.githubToken = config.credentials.githubToken;
1457
+ this.copilotToken = config.credentials.copilotToken;
1458
+ this.copilotTokenExpiresAt = config.credentials.copilotTokenExpiresAt;
1459
+ this.apiEndpoint = this.normalizeEndpoint(
1460
+ config.credentials.apiEndpoint || COPILOT_OAUTH_CONFIG.apiEndpoint
1461
+ );
1462
+ } else {
1463
+ throw new Error("Copilot requires copilot credentials");
1464
+ }
1465
+ }
1466
+ normalizeEndpoint(endpoint) {
1467
+ let url = endpoint;
1468
+ if (!url.startsWith("http")) {
1469
+ url = `https://${url}`;
1470
+ }
1471
+ url = url.replace(/\/\/proxy\./i, "//api.");
1472
+ if (!url.includes("/chat/completions")) {
1473
+ url = `${url}/chat/completions`;
1474
+ }
1475
+ return url;
1476
+ }
1477
+ async ensureValidToken() {
1478
+ if (isCopilotTokenExpired(this.copilotTokenExpiresAt)) {
1479
+ try {
1480
+ const newTokenData = await exchangeGitHubTokenForCopilot(
1481
+ this.githubToken
1482
+ );
1483
+ this.copilotToken = newTokenData.token;
1484
+ const parsed = parseCopilotToken(newTokenData.token);
1485
+ this.copilotTokenExpiresAt = parsed.expiresAt;
1486
+ this.apiEndpoint = parsed.apiEndpoint;
1487
+ const currentConfig = loadConfig();
1488
+ if (currentConfig && currentConfig.credentials.type === "copilot") {
1489
+ currentConfig.credentials.copilotToken = newTokenData.token;
1490
+ currentConfig.credentials.copilotTokenExpiresAt = parsed.expiresAt;
1491
+ currentConfig.credentials.apiEndpoint = parsed.apiEndpoint;
1492
+ saveConfig(currentConfig);
1493
+ }
1494
+ } catch (error) {
1495
+ throw new Error(
1496
+ `Copilot token refresh failed. Please re-authenticate with: b --auth
1497
+ Error: ${error instanceof Error ? error.message : "Unknown error"}`
1498
+ );
1499
+ }
1500
+ }
1501
+ return this.copilotToken;
1502
+ }
1503
+ async call(systemPrompt, userMessage) {
1504
+ const token = await this.ensureValidToken();
1505
+ const requestBody = {
1506
+ model: this.model,
1507
+ messages: [
1508
+ { role: "system", content: systemPrompt },
1509
+ { role: "user", content: userMessage }
1510
+ ],
1511
+ temperature: 0.1,
1512
+ top_p: 1,
1513
+ n: 1,
1514
+ stream: true
1515
+ };
1516
+ const headers = {
1517
+ "Content-Type": "application/json",
1518
+ Accept: "application/json",
1519
+ Authorization: `Bearer ${token}`,
1520
+ "User-Agent": "GitHubCopilotChat/0.35.0",
1521
+ "Editor-Version": "vscode/1.107.0",
1522
+ "Editor-Plugin-Version": "copilot-chat/0.35.0",
1523
+ "Copilot-Integration-Id": "vscode-chat"
1524
+ };
1525
+ const response = await fetch(this.apiEndpoint, {
1526
+ method: "POST",
1527
+ headers,
1528
+ body: JSON.stringify(requestBody)
1529
+ });
1530
+ if (!response.ok) {
1531
+ const errorText = await response.text();
1532
+ if (response.status === 401) {
1533
+ throw new Error(
1534
+ "Authentication failed. Please re-authenticate with: b --auth"
1535
+ );
1536
+ }
1537
+ if (response.status === 403) {
1538
+ throw new Error(
1539
+ "Access forbidden. Please ensure you have an active GitHub Copilot subscription."
1540
+ );
1541
+ }
1542
+ throw new Error(`Copilot API error: ${response.status} - ${errorText}`);
1543
+ }
1544
+ return this.parseStreamingResponse(response);
1545
+ }
1546
+ async parseStreamingResponse(response) {
1547
+ if (!response.body) {
1548
+ throw new Error("No response body from Copilot");
1549
+ }
1550
+ const reader = response.body.getReader();
1551
+ const decoder = new TextDecoder("utf-8");
1552
+ let buffer = "";
1553
+ let fullText = "";
1554
+ try {
1555
+ while (true) {
1556
+ const { done, value } = await reader.read();
1557
+ if (done) break;
1558
+ buffer += decoder.decode(value, { stream: true });
1559
+ const lines = buffer.split("\n");
1560
+ buffer = lines.pop() || "";
1561
+ for (const line of lines) {
1562
+ if (!line.trim() || !line.startsWith("data:")) continue;
1563
+ const data = line.substring(5).trim();
1564
+ if (data === "[DONE]") {
1565
+ return fullText.trim();
1566
+ }
1567
+ try {
1568
+ const chunk = JSON.parse(data);
1569
+ const content = chunk.choices?.[0]?.delta?.content;
1570
+ if (content) {
1571
+ fullText += content;
1572
+ }
1573
+ } catch {
1574
+ }
1575
+ }
1576
+ }
1577
+ } finally {
1578
+ reader.releaseLock();
1579
+ }
1580
+ if (!fullText) {
1581
+ throw new Error("No response content from Copilot");
1582
+ }
1583
+ return fullText.trim();
1584
+ }
1585
+ async generateCommand(query, context) {
1586
+ const userMessage = context ? `Context: ${context}
1587
+
1588
+ Task: ${query}` : query;
1589
+ return this.call(SYSTEM_PROMPT_GENERATE, userMessage);
1590
+ }
1591
+ async explainCommand(command) {
1592
+ return this.call(SYSTEM_PROMPT_EXPLAIN, `Explain this command: ${command}`);
1593
+ }
1594
+ async validateCredentials() {
1595
+ try {
1596
+ const token = await this.ensureValidToken();
1597
+ return !!token && token.length > 0;
1598
+ } catch {
1599
+ return false;
1600
+ }
1601
+ }
1602
+ };
1603
+ var COPILOT_MODELS = [
1604
+ { value: "gpt-4.1", label: "GPT-4.1 (default)" },
1605
+ { value: "gpt-5.1", label: "GPT-5.1 (latest)" },
1606
+ { value: "gpt-5-mini", label: "GPT-5 Mini (fast)" },
1607
+ { value: "claude-sonnet-4", label: "Claude Sonnet 4" },
1608
+ { value: "claude-sonnet-4.5", label: "Claude Sonnet 4.5 (latest)" },
1609
+ { value: "gpt-4o", label: "GPT-4o" }
686
1610
  ];
687
1611
 
688
1612
  // src/providers/ollama.ts
@@ -823,9 +1747,12 @@ Task: ${query}` : query;
823
1747
  }
824
1748
  };
825
1749
  var OPENAI_MODELS = [
826
- { value: "gpt-4o", label: "GPT-4o (recommended)" },
827
- { value: "gpt-4o-mini", label: "GPT-4o Mini (fast)" },
828
- { value: "gpt-4-turbo", label: "GPT-4 Turbo" }
1750
+ { value: "gpt-5.2", label: "GPT-5.2 (most intelligent)" },
1751
+ { value: "gpt-5.1", label: "GPT-5.1 (recommended)" },
1752
+ { value: "gpt-5-mini", label: "GPT-5 Mini (fast)" },
1753
+ { value: "gpt-5-nano", label: "GPT-5 Nano (fastest)" },
1754
+ { value: "gpt-4.1", label: "GPT-4.1" },
1755
+ { value: "gpt-4o", label: "GPT-4o (legacy)" }
829
1756
  ];
830
1757
 
831
1758
  // src/providers/openrouter.ts
@@ -916,8 +1843,14 @@ function createProvider(config) {
916
1843
  switch (config.provider) {
917
1844
  case "claude":
918
1845
  return new ClaudeProvider(providerConfig);
1846
+ case "claude-subscription":
1847
+ return new ClaudeSubscriptionProvider(providerConfig);
919
1848
  case "openai":
920
1849
  return new OpenAIProvider(providerConfig);
1850
+ case "chatgpt-subscription":
1851
+ return new ChatGPTSubscriptionProvider(providerConfig);
1852
+ case "copilot":
1853
+ return new CopilotProvider(providerConfig);
921
1854
  case "ollama":
922
1855
  return new OllamaProvider(providerConfig);
923
1856
  case "openrouter":
@@ -937,6 +1870,19 @@ function createSpinner(text) {
937
1870
  }
938
1871
 
939
1872
  // src/core/auth.ts
1873
+ async function openBrowser(url) {
1874
+ try {
1875
+ const { exec: exec2 } = await import("child_process");
1876
+ const { promisify: promisify2 } = await import("util");
1877
+ const execAsync2 = promisify2(exec2);
1878
+ const platform = process.platform;
1879
+ const command = platform === "darwin" ? `open "${url}"` : platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
1880
+ await execAsync2(command);
1881
+ return true;
1882
+ } catch {
1883
+ return false;
1884
+ }
1885
+ }
940
1886
  function showWelcomeBanner() {
941
1887
  const cyan = pc3.cyan;
942
1888
  const dim = pc3.dim;
@@ -1000,14 +1946,29 @@ async function runAuthSetup(showBanner = true) {
1000
1946
  const provider = await select({
1001
1947
  message: "Select your AI provider:",
1002
1948
  choices: [
1949
+ {
1950
+ value: "claude-subscription",
1951
+ name: "Claude Pro/Max (Subscription)",
1952
+ description: "Use your existing Claude subscription"
1953
+ },
1954
+ {
1955
+ value: "chatgpt-subscription",
1956
+ name: "ChatGPT Plus/Pro (Subscription)",
1957
+ description: "Use your existing ChatGPT subscription"
1958
+ },
1959
+ {
1960
+ value: "copilot",
1961
+ name: "GitHub Copilot",
1962
+ description: "Use your GitHub Copilot subscription"
1963
+ },
1003
1964
  {
1004
1965
  value: "claude",
1005
- name: "Claude (Anthropic)",
1966
+ name: "Claude (API Key)",
1006
1967
  description: "Use Anthropic API key"
1007
1968
  },
1008
1969
  {
1009
1970
  value: "openai",
1010
- name: "ChatGPT (OpenAI)",
1971
+ name: "ChatGPT (API Key)",
1011
1972
  description: "Use OpenAI API key"
1012
1973
  },
1013
1974
  {
@@ -1025,6 +1986,105 @@ async function runAuthSetup(showBanner = true) {
1025
1986
  let credentials;
1026
1987
  let model;
1027
1988
  switch (provider) {
1989
+ case "claude-subscription": {
1990
+ console.log();
1991
+ console.log(
1992
+ pc3.yellow(
1993
+ " Note: This uses your Claude Pro/Max subscription via OAuth."
1994
+ )
1995
+ );
1996
+ console.log(
1997
+ pc3.dim(
1998
+ " Your credentials are stored locally and refreshed automatically."
1999
+ )
2000
+ );
2001
+ console.log();
2002
+ const tokens = await performClaudeOAuth();
2003
+ if (!tokens) {
2004
+ return false;
2005
+ }
2006
+ credentials = {
2007
+ type: "claude_subscription",
2008
+ accessToken: tokens.accessToken,
2009
+ refreshToken: tokens.refreshToken,
2010
+ expiresAt: tokens.expiresAt,
2011
+ email: tokens.email
2012
+ };
2013
+ model = await select({
2014
+ message: "Select model:",
2015
+ choices: CLAUDE_SUBSCRIPTION_MODELS.map((m) => ({
2016
+ value: m.value,
2017
+ name: m.label
2018
+ }))
2019
+ });
2020
+ break;
2021
+ }
2022
+ case "chatgpt-subscription": {
2023
+ console.log();
2024
+ console.log(
2025
+ pc3.yellow(
2026
+ " Note: This uses your ChatGPT Plus/Pro subscription via OAuth."
2027
+ )
2028
+ );
2029
+ console.log(
2030
+ pc3.red(" WARNING: This is EXPERIMENTAL and uses an unofficial API.")
2031
+ );
2032
+ console.log(
2033
+ pc3.dim(" The API may change or stop working without notice.")
2034
+ );
2035
+ console.log(
2036
+ pc3.dim(" For stable usage, consider using an API key instead.")
2037
+ );
2038
+ console.log();
2039
+ const tokens = await performChatGPTOAuth();
2040
+ if (!tokens) {
2041
+ return false;
2042
+ }
2043
+ credentials = {
2044
+ type: "chatgpt_subscription",
2045
+ accessToken: tokens.accessToken,
2046
+ refreshToken: tokens.refreshToken || void 0,
2047
+ expiresAt: tokens.expiresAt || void 0,
2048
+ accountId: tokens.accountId
2049
+ };
2050
+ model = await select({
2051
+ message: "Select model:",
2052
+ choices: CHATGPT_SUBSCRIPTION_MODELS.map((m) => ({
2053
+ value: m.value,
2054
+ name: m.label
2055
+ }))
2056
+ });
2057
+ break;
2058
+ }
2059
+ case "copilot": {
2060
+ console.log();
2061
+ console.log(
2062
+ pc3.yellow(" Note: This uses your GitHub Copilot subscription.")
2063
+ );
2064
+ console.log(
2065
+ pc3.dim(" You need an active GitHub Copilot subscription to use this.")
2066
+ );
2067
+ console.log();
2068
+ const copilotResult = await performCopilotDeviceFlow();
2069
+ if (!copilotResult) {
2070
+ return false;
2071
+ }
2072
+ credentials = {
2073
+ type: "copilot",
2074
+ githubToken: copilotResult.githubToken,
2075
+ copilotToken: copilotResult.copilotToken,
2076
+ copilotTokenExpiresAt: copilotResult.copilotTokenExpiresAt,
2077
+ apiEndpoint: copilotResult.apiEndpoint
2078
+ };
2079
+ model = await select({
2080
+ message: "Select model:",
2081
+ choices: COPILOT_MODELS.map((m) => ({
2082
+ value: m.value,
2083
+ name: m.label
2084
+ }))
2085
+ });
2086
+ break;
2087
+ }
1028
2088
  case "claude": {
1029
2089
  const apiKey = await password({
1030
2090
  message: "Enter your Anthropic API key:",
@@ -1134,6 +2194,214 @@ async function runAuthSetup(showBanner = true) {
1134
2194
  console.log();
1135
2195
  return true;
1136
2196
  }
2197
+ async function performClaudeOAuth() {
2198
+ const authMethod = await select({
2199
+ message: "How would you like to authenticate?",
2200
+ choices: [
2201
+ {
2202
+ value: "browser",
2203
+ name: "Open browser automatically",
2204
+ description: "Recommended - opens browser and waits for callback"
2205
+ },
2206
+ {
2207
+ value: "manual",
2208
+ name: "Manual URL copy/paste",
2209
+ description: "Copy URL to browser, then paste the callback URL"
2210
+ }
2211
+ ]
2212
+ });
2213
+ const pkce = generatePKCE();
2214
+ const authUrl = buildClaudeAuthUrl(pkce);
2215
+ if (authMethod === "browser") {
2216
+ return performBrowserOAuth(
2217
+ "Claude",
2218
+ authUrl,
2219
+ pkce,
2220
+ CLAUDE_OAUTH_CONFIG.callbackPort,
2221
+ async (code) => exchangeClaudeCode(code, pkce.codeVerifier, pkce.state)
2222
+ );
2223
+ }
2224
+ return performManualOAuth(
2225
+ "Claude",
2226
+ authUrl,
2227
+ pkce,
2228
+ async (code) => exchangeClaudeCode(code, pkce.codeVerifier, pkce.state)
2229
+ );
2230
+ }
2231
+ async function performChatGPTOAuth() {
2232
+ const authMethod = await select({
2233
+ message: "How would you like to authenticate?",
2234
+ choices: [
2235
+ {
2236
+ value: "browser",
2237
+ name: "Open browser automatically",
2238
+ description: "Recommended - opens browser and waits for callback"
2239
+ },
2240
+ {
2241
+ value: "manual",
2242
+ name: "Manual URL copy/paste",
2243
+ description: "Copy URL to browser, then paste the callback URL"
2244
+ }
2245
+ ]
2246
+ });
2247
+ const pkce = generatePKCE();
2248
+ const authUrl = buildChatGPTAuthUrl(pkce);
2249
+ if (authMethod === "browser") {
2250
+ return performBrowserOAuth(
2251
+ "ChatGPT",
2252
+ authUrl,
2253
+ pkce,
2254
+ CHATGPT_OAUTH_CONFIG.callbackPort,
2255
+ async (code) => exchangeChatGPTCode(code, pkce.codeVerifier)
2256
+ );
2257
+ }
2258
+ return performManualOAuth(
2259
+ "ChatGPT",
2260
+ authUrl,
2261
+ pkce,
2262
+ async (code) => exchangeChatGPTCode(code, pkce.codeVerifier)
2263
+ );
2264
+ }
2265
+ async function performBrowserOAuth(providerName, authUrl, pkce, port, exchangeCode) {
2266
+ console.log();
2267
+ console.log(pc3.dim(" Starting local callback server..."));
2268
+ const serverPromise = startCallbackServer(port, pkce.state);
2269
+ const browserOpened = await openBrowser(authUrl);
2270
+ if (browserOpened) {
2271
+ console.log(
2272
+ pc3.green(` Browser opened. Please log in to ${providerName}.`)
2273
+ );
2274
+ } else {
2275
+ console.log(pc3.yellow(" Could not open browser automatically."));
2276
+ console.log(pc3.dim(" Please open this URL manually:"));
2277
+ console.log();
2278
+ console.log(` ${pc3.cyan(authUrl)}`);
2279
+ }
2280
+ console.log();
2281
+ console.log(pc3.dim(" Waiting for authentication (5 minute timeout)..."));
2282
+ try {
2283
+ const { code } = await serverPromise;
2284
+ const spinner = createSpinner("Exchanging authorization code...").start();
2285
+ try {
2286
+ const tokens = await exchangeCode(code);
2287
+ spinner.succeed("Authentication successful!");
2288
+ return tokens;
2289
+ } catch (error) {
2290
+ spinner.fail(
2291
+ `Token exchange failed: ${error instanceof Error ? error.message : "Unknown error"}`
2292
+ );
2293
+ return null;
2294
+ }
2295
+ } catch (error) {
2296
+ logger.error(
2297
+ `Authentication failed: ${error instanceof Error ? error.message : "Unknown error"}`
2298
+ );
2299
+ return null;
2300
+ }
2301
+ }
2302
+ async function performManualOAuth(providerName, authUrl, pkce, exchangeCode) {
2303
+ console.log();
2304
+ console.log(
2305
+ pc3.dim(
2306
+ ` Open this URL in your browser to authenticate with ${providerName}:`
2307
+ )
2308
+ );
2309
+ console.log();
2310
+ console.log(` ${pc3.cyan(authUrl)}`);
2311
+ console.log();
2312
+ console.log(
2313
+ pc3.dim(" After logging in, you will be redirected to a localhost URL.")
2314
+ );
2315
+ console.log(pc3.dim(" Copy the full redirect URL and paste it below."));
2316
+ console.log();
2317
+ const callbackUrl = await input3({
2318
+ message: "Paste the callback URL here:"
2319
+ });
2320
+ try {
2321
+ const url = new URL(callbackUrl);
2322
+ const code = url.searchParams.get("code");
2323
+ const state = url.searchParams.get("state");
2324
+ const error = url.searchParams.get("error");
2325
+ if (error) {
2326
+ logger.error(`OAuth error: ${error}`);
2327
+ return null;
2328
+ }
2329
+ if (!code) {
2330
+ logger.error("No authorization code found in URL");
2331
+ return null;
2332
+ }
2333
+ if (state !== pkce.state) {
2334
+ logger.error("State mismatch - possible security issue");
2335
+ return null;
2336
+ }
2337
+ const spinner = createSpinner("Exchanging authorization code...").start();
2338
+ try {
2339
+ const tokens = await exchangeCode(code);
2340
+ spinner.succeed("Authentication successful!");
2341
+ return tokens;
2342
+ } catch (err) {
2343
+ spinner.fail(
2344
+ `Token exchange failed: ${err instanceof Error ? err.message : "Unknown error"}`
2345
+ );
2346
+ return null;
2347
+ }
2348
+ } catch {
2349
+ logger.error("Invalid URL format");
2350
+ return null;
2351
+ }
2352
+ }
2353
+ async function performCopilotDeviceFlow() {
2354
+ try {
2355
+ const spinner = createSpinner("Requesting device code...").start();
2356
+ const deviceCode = await requestCopilotDeviceCode();
2357
+ spinner.stop();
2358
+ console.log();
2359
+ console.log(pc3.bold(" To authenticate with GitHub Copilot:"));
2360
+ console.log();
2361
+ console.log(
2362
+ ` 1. Visit: ${pc3.cyan(pc3.underline(deviceCode.verification_uri))}`
2363
+ );
2364
+ console.log(` 2. Enter code: ${pc3.bold(pc3.green(deviceCode.user_code))}`);
2365
+ console.log();
2366
+ const browserOpened = await openBrowser(deviceCode.verification_uri);
2367
+ if (browserOpened) {
2368
+ console.log(pc3.dim(" Browser opened automatically."));
2369
+ }
2370
+ console.log(pc3.dim(" Waiting for authorization..."));
2371
+ console.log();
2372
+ const expiresAt = Date.now() + deviceCode.expires_in * 1e3;
2373
+ const intervalMs = (deviceCode.interval || 5) * 1e3;
2374
+ const githubToken = await pollForCopilotAccessToken(
2375
+ deviceCode.device_code,
2376
+ intervalMs,
2377
+ expiresAt
2378
+ );
2379
+ const exchangeSpinner = createSpinner(
2380
+ "Getting Copilot access token..."
2381
+ ).start();
2382
+ try {
2383
+ const copilotTokenData = await exchangeGitHubTokenForCopilot(githubToken);
2384
+ const parsed = parseCopilotToken(copilotTokenData.token);
2385
+ exchangeSpinner.succeed("Authentication successful!");
2386
+ return {
2387
+ githubToken,
2388
+ copilotToken: copilotTokenData.token,
2389
+ copilotTokenExpiresAt: parsed.expiresAt,
2390
+ apiEndpoint: parsed.apiEndpoint
2391
+ };
2392
+ } catch (error) {
2393
+ exchangeSpinner.fail(
2394
+ `Failed to get Copilot token: ${error instanceof Error ? error.message : "Unknown error"}`
2395
+ );
2396
+ return null;
2397
+ }
2398
+ } catch (error) {
2399
+ logger.error(
2400
+ `GitHub authentication failed: ${error instanceof Error ? error.message : "Unknown error"}`
2401
+ );
2402
+ return null;
2403
+ }
2404
+ }
1137
2405
 
1138
2406
  // src/cli/commands/AuthCommand.ts
1139
2407
  var AuthCommand = class extends Command2 {
@@ -2113,6 +3381,39 @@ var ModelCommand = class extends Command8 {
2113
3381
  });
2114
3382
  break;
2115
3383
  }
3384
+ case "claude-subscription": {
3385
+ newModel = await selectWithEsc({
3386
+ message: "Select new model:",
3387
+ choices: CLAUDE_SUBSCRIPTION_MODELS.map((m) => ({
3388
+ value: m.value,
3389
+ name: m.label
3390
+ })),
3391
+ default: config.model
3392
+ });
3393
+ break;
3394
+ }
3395
+ case "chatgpt-subscription": {
3396
+ newModel = await selectWithEsc({
3397
+ message: "Select new model:",
3398
+ choices: CHATGPT_SUBSCRIPTION_MODELS.map((m) => ({
3399
+ value: m.value,
3400
+ name: m.label
3401
+ })),
3402
+ default: config.model
3403
+ });
3404
+ break;
3405
+ }
3406
+ case "copilot": {
3407
+ newModel = await selectWithEsc({
3408
+ message: "Select new model:",
3409
+ choices: COPILOT_MODELS.map((m) => ({
3410
+ value: m.value,
3411
+ name: m.label
3412
+ })),
3413
+ default: config.model
3414
+ });
3415
+ break;
3416
+ }
2116
3417
  default:
2117
3418
  logger.error(`Unknown provider: ${config.provider}`);
2118
3419
  return 1;
@@ -2544,9 +3845,11 @@ var SuggestShortcutsCommand = class extends Command12 {
2544
3845
  };
2545
3846
 
2546
3847
  // src/cli/index.ts
3848
+ var require2 = createRequire(import.meta.url);
3849
+ var packageJson = require2("../../package.json");
2547
3850
  var pkg = {
2548
- name: "bashio",
2549
- version: "0.7.0"
3851
+ name: packageJson.name,
3852
+ version: packageJson.version
2550
3853
  };
2551
3854
  var notifier = updateNotifier({
2552
3855
  pkg,