summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-07-14 17:55:06 -0400
committerDax Raad <[email protected]>2025-07-14 18:07:55 -0400
commit2cdb37c32b70adbe7f1061ce1e094bd20fa58b49 (patch)
tree21376443a4ca093cb8aa883a96ed3e1cf7a03dd4 /packages
parent535d79b64c2894d3f09cdf60cc4b6f50c057548d (diff)
downloadopencode-2cdb37c32b70adbe7f1061ce1e094bd20fa58b49.tar.gz
opencode-2cdb37c32b70adbe7f1061ce1e094bd20fa58b49.zip
support anthropic console login flow
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/auth/anthropic.ts13
-rw-r--r--packages/opencode/src/cli/cmd/auth.ts75
2 files changed, 74 insertions, 14 deletions
diff --git a/packages/opencode/src/auth/anthropic.ts b/packages/opencode/src/auth/anthropic.ts
index cb38238e9..d3228cb88 100644
--- a/packages/opencode/src/auth/anthropic.ts
+++ b/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() {
diff --git a/packages/opencode/src/cli/cmd/auth.ts b/packages/opencode/src/cli/cmd/auth.ts
index 91ec61ea8..a0bb0fbb6 100644
--- a/packages/opencode/src/cli/cmd/auth.ts
+++ b/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
}