Skip to content

Commit ef7f1f0

Browse files
committed
sync
1 parent 96b5a07 commit ef7f1f0

10 files changed

Lines changed: 191 additions & 29 deletions

File tree

bun.lock

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/opencode/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"typescript": "catalog:"
2222
},
2323
"dependencies": {
24+
"@clack/prompts": "0.11.0",
2425
"@flystorage/file-storage": "1.1.0",
2526
"@flystorage/local-fs": "1.1.0",
2627
"@hono/zod-validator": "0.5.0",
@@ -32,6 +33,7 @@
3233
"env-paths": "3.0.0",
3334
"hono": "4.7.10",
3435
"hono-openapi": "0.4.8",
36+
"open": "10.1.2",
3537
"remeda": "2.22.3",
3638
"ts-lsp-client": "1.0.3",
3739
"turndown": "7.2.0",

packages/opencode/src/auth/anthropic.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import { generatePKCE } from "@openauthjs/openauth/pkce"
22
import { Global } from "../global"
33
import path from "path"
44
import fs from "fs/promises"
5-
import type { BunFile } from "bun"
65

76
export namespace AuthAnthropic {
87
const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"
98

9+
const file = Bun.file(path.join(Global.Path.data, "auth", "anthropic.json"))
10+
1011
export async function authorize() {
1112
const pkce = await generatePKCE()
1213
const url = new URL("https://claude.ai/oauth/authorize", import.meta.url)
@@ -47,13 +48,11 @@ export namespace AuthAnthropic {
4748
}),
4849
})
4950
if (!result.ok) throw new ExchangeFailed()
50-
const file = Bun.file(path.join(Global.Path.data, "anthropic.json"))
5151
await Bun.write(file, result)
5252
await fs.chmod(file.name!, 0o600)
5353
}
5454

5555
export async function access() {
56-
const file = Bun.file(path.join(Global.Path.data, "anthropic.json"))
5756
if (!(await file.exists())) return
5857
const result = await file.json()
5958
const refresh = result.refresh_token

packages/opencode/src/auth/keys.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import path from "path"
2+
import { Global } from "../global"
3+
import fs from "fs/promises"
4+
5+
export namespace AuthKeys {
6+
const file = Bun.file(path.join(Global.Path.data, "auth", "keys.json"))
7+
8+
export async function get() {
9+
return file
10+
.json()
11+
.catch(() => ({}))
12+
.then((x) => x as Record<string, string>)
13+
}
14+
15+
export async function set(key: string, value: string) {
16+
const env = await get()
17+
await Bun.write(file, JSON.stringify({ ...env, [key]: value }))
18+
await fs.chmod(file.name!, 0o600)
19+
}
20+
}

packages/opencode/src/cli/cmd/login-anthropic.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,16 @@ import { UI } from "../ui"
33

44
// Example: https://claude.ai/oauth/authorize?code=true&client_id=9d1c250a-e61b-44d9-88ed-5944d1962f5e&response_type=code&redirect_uri=https%3A%2F%2Fconsole.anthropic.com%2Foauth%2Fcode%2Fcallback&scope=org%3Acreate_api_key+user%3Aprofile+user%3Ainference&code_challenge=MdFtFgFap23AWDSN0oa3-eaKjQRFE4CaEhXx8M9fHZg&code_challenge_method=S256&state=rKLtaDzm88GSwekyEqdi0wXX-YqIr13tSzYymSzpvfs
55

6-
7-
86
export const LoginAnthropicCommand = {
97
command: "anthropic",
108
describe: "Login to Anthropic",
119
handler: async () => {
1210
const { url, verifier } = await AuthAnthropic.authorize()
1311

14-
UI.print("Login to Anthropic")
15-
UI.print("Open the following URL in your browser:")
16-
UI.print(url)
17-
UI.print("")
12+
UI.println("Login to Anthropic")
13+
UI.println("Open the following URL in your browser:")
14+
UI.println(url)
15+
UI.println("")
1816

1917
const code = await UI.input("Paste the authorization code here: ")
2018
await AuthAnthropic.exchange(code, verifier)
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { AuthAnthropic } from "../../auth/anthropic"
2+
import { AuthKeys } from "../../auth/keys"
3+
import { UI } from "../ui"
4+
import { cmd } from "./cmd"
5+
import * as prompts from "@clack/prompts"
6+
import open from "open"
7+
8+
const OPENCODE = [
9+
`█▀▀█ █▀▀█ █▀▀ █▀▀▄ █▀▀ █▀▀█ █▀▀▄ █▀▀`,
10+
`█░░█ █░░█ █▀▀ █░░█ █░░ █░░█ █░░█ █▀▀`,
11+
`▀▀▀▀ █▀▀▀ ▀▀▀ ▀ ▀ ▀▀▀ ▀▀▀▀ ▀▀▀ ▀▀▀`,
12+
]
13+
14+
export const ProviderCommand = cmd({
15+
command: "provider",
16+
builder: (yargs) =>
17+
yargs
18+
.command(ProviderAddCommand)
19+
.command(ProviderListCommand)
20+
.demandCommand(),
21+
describe: "initialize opencode",
22+
async handler() {},
23+
})
24+
25+
export const ProviderListCommand = cmd({
26+
command: "list",
27+
aliases: ["ls"],
28+
describe: "list providers",
29+
async handler() {
30+
prompts.intro("Configured Providers")
31+
const keys = await AuthKeys.get()
32+
for (const key of Object.keys(keys)) {
33+
prompts.log.success(key)
34+
}
35+
prompts.outro("3 providers configured")
36+
},
37+
})
38+
39+
const ProviderAddCommand = cmd({
40+
command: "add",
41+
describe: "add credentials for various providers",
42+
async handler() {
43+
UI.empty()
44+
for (const row of OPENCODE) {
45+
UI.print(" ")
46+
for (let i = 0; i < row.length; i++) {
47+
const color =
48+
i < 18 ? Bun.color("white", "ansi") : Bun.color("gray", "ansi")
49+
const char = row[i]
50+
UI.print(color + char)
51+
}
52+
UI.println()
53+
}
54+
UI.empty()
55+
56+
prompts.intro("Setup")
57+
const keys = await AuthKeys.get()
58+
const provider = await prompts.select({
59+
message: "Configure a provider",
60+
options: [
61+
{
62+
label: "Anthropic",
63+
value: "anthropic",
64+
hint: keys["anthropic"] ? "configured" : "",
65+
},
66+
{
67+
label: "OpenAI",
68+
value: "openai",
69+
hint: keys["openai"] ? "configured" : "",
70+
},
71+
{
72+
label: "Google",
73+
value: "google",
74+
hint: keys["google"] ? "configured" : "",
75+
},
76+
],
77+
})
78+
if (prompts.isCancel(provider)) return
79+
80+
if (provider === "anthropic") {
81+
const method = await prompts.select({
82+
message: "Login method",
83+
options: [
84+
{
85+
label: "Claude Pro/Max",
86+
value: "oauth",
87+
},
88+
{
89+
label: "API Key",
90+
value: "api",
91+
},
92+
],
93+
})
94+
if (prompts.isCancel(method)) return
95+
96+
if (method === "oauth") {
97+
// some weird bug where program exits without this
98+
await new Promise((resolve) => setTimeout(resolve, 10))
99+
const { url, verifier } = await AuthAnthropic.authorize()
100+
prompts.note("Opening browser...")
101+
await open(url)
102+
prompts.log.info(url)
103+
104+
const code = await prompts.text({
105+
message: "Paste the authorization code here: ",
106+
validate: (x) => (x.length > 0 ? undefined : "Required"),
107+
})
108+
if (prompts.isCancel(code)) return
109+
await AuthAnthropic.exchange(code, verifier)
110+
.then(() => {
111+
prompts.log.success("Login successful")
112+
})
113+
.catch(() => {
114+
prompts.log.error("Invalid code")
115+
})
116+
prompts.outro("Done")
117+
return
118+
}
119+
}
120+
121+
const key = await prompts.password({
122+
message: "Enter your API key",
123+
})
124+
if (prompts.isCancel(key)) return
125+
await AuthKeys.set(provider, key)
126+
127+
prompts.outro("Done")
128+
},
129+
})

packages/opencode/src/cli/ui.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,30 @@ export namespace UI {
1616
TEXT_INFO_BOLD: "\x1b[94m\x1b[1m",
1717
}
1818

19-
19+
export function println(...message: string[]) {
20+
print(...message)
21+
Bun.stderr.write("\n")
22+
}
2023

2124
export function print(...message: string[]) {
25+
blank = false
2226
Bun.stderr.write(message.join(" "))
23-
Bun.stderr.write("\n")
2427
}
2528

29+
let blank = false
2630
export function empty() {
27-
print("" + Style.TEXT_NORMAL)
31+
if (blank) return
32+
println("" + Style.TEXT_NORMAL)
33+
blank = true
2834
}
2935

3036
export async function input(prompt: string): Promise<string> {
31-
const readline = require('readline')
37+
const readline = require("readline")
3238
const rl = readline.createInterface({
3339
input: process.stdin,
34-
output: process.stdout
40+
output: process.stdout,
3541
})
36-
42+
3743
return new Promise((resolve) => {
3844
rl.question(prompt, (answer: string) => {
3945
rl.close()

packages/opencode/src/index.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import { Global } from "./global"
1111
import yargs from "yargs"
1212
import { hideBin } from "yargs/helpers"
1313
import { RunCommand } from "./cli/cmd/run"
14-
import { LoginAnthropicCommand } from "./cli/cmd/login-anthropic"
1514
import { GenerateCommand } from "./cli/cmd/generate"
1615
import { VERSION } from "./cli/version"
1716
import { ScrapCommand } from "./cli/cmd/scrap"
1817
import { Log } from "./util/log"
18+
import { ProviderCommand } from "./cli/cmd/provider"
1919

2020
await Log.init({ print: process.argv.includes("--print-logs") })
2121

@@ -70,11 +70,6 @@ yargs(hideBin(process.argv))
7070
.command(RunCommand)
7171
.command(GenerateCommand)
7272
.command(ScrapCommand)
73-
.command({
74-
command: "login",
75-
describe: "generate credentials for various providers",
76-
builder: (yargs) => yargs.command(LoginAnthropicCommand).demandCommand(),
77-
handler: () => {},
78-
})
73+
.command(ProviderCommand)
7974
.help()
8075
.parse()

packages/opencode/src/provider/models.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,9 @@ import path from "path"
44

55
export namespace ModelsDev {
66
const log = Log.create({ service: "models.dev" })
7-
8-
function filepath() {
9-
return path.join(Global.Path.data, "models.json")
10-
}
7+
const file = Bun.file(path.join(Global.Path.cache, "models.json"))
118

129
export async function get() {
13-
const file = Bun.file(filepath())
1410
if (await file.exists()) {
1511
refresh()
1612
return file.json()
@@ -24,6 +20,6 @@ export namespace ModelsDev {
2420
const result = await fetch("https://models.dev/api.json")
2521
if (!result.ok)
2622
throw new Error(`Failed to fetch models.dev: ${result.statusText}`)
27-
await Bun.write(filepath(), result)
23+
await Bun.write(file, result)
2824
}
2925
}

packages/opencode/src/provider/provider.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import { LspHoverTool } from "../tool/lsp-hover"
1818
import { PatchTool } from "../tool/patch"
1919
import { ReadTool } from "../tool/read"
2020
import type { Tool } from "../tool/tool"
21-
2221
import { WriteTool } from "../tool/write"
2322
import { TodoReadTool, TodoWriteTool } from "../tool/todo"
2423
import { AuthAnthropic } from "../auth/anthropic"

0 commit comments

Comments
 (0)