Explorar o código

support anthropic console login flow

Dax Raad hai 7 meses
pai
achega
2cdb37c32b

+ 8 - 5
packages/opencode/src/auth/anthropic.ts

@@ -4,9 +4,13 @@ import { Auth } from "./index"
 export namespace AuthAnthropic {
   const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"
 
-  export async function authorize() {
+  export async function authorize(mode: "max" | "console") {
     const pkce = await generatePKCE()
-    const url = new URL("https://claude.ai/oauth/authorize", import.meta.url)
+
+    const url = new URL(
+      `https://${mode === "console" ? "console.anthropic.com" : "claude.ai"}/oauth/authorize`,
+      import.meta.url,
+    )
     url.searchParams.set("code", "true")
     url.searchParams.set("client_id", CLIENT_ID)
     url.searchParams.set("response_type", "code")
@@ -39,12 +43,11 @@ export namespace AuthAnthropic {
     })
     if (!result.ok) throw new ExchangeFailed()
     const json = await result.json()
-    await Auth.set("anthropic", {
-      type: "oauth",
+    return {
       refresh: json.refresh_token as string,
       access: json.access_token as string,
       expires: Date.now() + json.expires_in * 1000,
-    })
+    }
   }
 
   export async function access() {

+ 66 - 9
packages/opencode/src/cli/cmd/auth.ts

@@ -132,20 +132,24 @@ export const AuthLoginCommand = cmd({
         options: [
           {
             label: "Claude Pro/Max",
-            value: "oauth",
+            value: "max",
           },
           {
-            label: "API Key",
+            label: "Create API Key",
+            value: "console",
+          },
+          {
+            label: "Manually enter API Key",
             value: "api",
           },
         ],
       })
       if (prompts.isCancel(method)) throw new UI.CancelledError()
 
-      if (method === "oauth") {
+      if (method === "max") {
         // some weird bug where program exits without this
         await new Promise((resolve) => setTimeout(resolve, 10))
-        const { url, verifier } = await AuthAnthropic.authorize()
+        const { url, verifier } = await AuthAnthropic.authorize("max")
         prompts.note("Trying to open browser...")
         try {
           await open(url)
@@ -162,13 +166,66 @@ export const AuthLoginCommand = cmd({
         })
         if (prompts.isCancel(code)) throw new UI.CancelledError()
 
-        await AuthAnthropic.exchange(code, verifier)
-          .then(() => {
-            prompts.log.success("Login successful")
+        try {
+          const credentials = await AuthAnthropic.exchange(code, verifier)
+          await Auth.set("anthropic", {
+            type: "oauth",
+            refresh: credentials.refresh,
+            access: credentials.access,
+            expires: credentials.expires,
           })
-          .catch(() => {
-            prompts.log.error("Invalid code")
+          prompts.log.success("Login successful")
+        } catch {
+          prompts.log.error("Invalid code")
+        }
+        prompts.outro("Done")
+        return
+      }
+
+      if (method === "console") {
+        // some weird bug where program exits without this
+        await new Promise((resolve) => setTimeout(resolve, 10))
+        const { url, verifier } = await AuthAnthropic.authorize("console")
+        prompts.note("Trying to open browser...")
+        try {
+          await open(url)
+        } catch (e) {
+          prompts.log.error(
+            "Failed to open browser perhaps you are running without a display or X server, please open the following URL in your browser:",
+          )
+        }
+        prompts.log.info(url)
+
+        const code = await prompts.text({
+          message: "Paste the authorization code here: ",
+          validate: (x) => (x.length > 0 ? undefined : "Required"),
+        })
+        if (prompts.isCancel(code)) throw new UI.CancelledError()
+
+        try {
+          const credentials = await AuthAnthropic.exchange(code, verifier)
+          const accessToken = credentials.access
+          const response = await fetch("https://api.anthropic.com/api/oauth/claude_cli/create_api_key", {
+            method: "POST",
+            headers: {
+              Authorization: `Bearer ${accessToken}`,
+              "Content-Type": "application/x-www-form-urlencoded",
+              Accept: "application/json, text/plain, */*",
+            },
           })
+          if (!response.ok) {
+            throw new Error("Failed to create API key")
+          }
+          const json = await response.json()
+          await Auth.set("anthropic", {
+            type: "api",
+            key: json.raw_key,
+          })
+
+          prompts.log.success("Login successful - API key created and saved")
+        } catch (error) {
+          prompts.log.error("Invalid code or failed to create API key")
+        }
         prompts.outro("Done")
         return
       }