mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-24 19:24:22 +01:00
sync
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
"typescript": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clack/prompts": "0.11.0",
|
||||
"@flystorage/file-storage": "1.1.0",
|
||||
"@flystorage/local-fs": "1.1.0",
|
||||
"@hono/zod-validator": "0.5.0",
|
||||
@@ -32,6 +33,7 @@
|
||||
"env-paths": "3.0.0",
|
||||
"hono": "4.7.10",
|
||||
"hono-openapi": "0.4.8",
|
||||
"open": "10.1.2",
|
||||
"remeda": "2.22.3",
|
||||
"ts-lsp-client": "1.0.3",
|
||||
"turndown": "7.2.0",
|
||||
|
||||
@@ -2,11 +2,12 @@ import { generatePKCE } from "@openauthjs/openauth/pkce"
|
||||
import { Global } from "../global"
|
||||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
import type { BunFile } from "bun"
|
||||
|
||||
export namespace AuthAnthropic {
|
||||
const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"
|
||||
|
||||
const file = Bun.file(path.join(Global.Path.data, "auth", "anthropic.json"))
|
||||
|
||||
export async function authorize() {
|
||||
const pkce = await generatePKCE()
|
||||
const url = new URL("https://claude.ai/oauth/authorize", import.meta.url)
|
||||
@@ -47,13 +48,11 @@ export namespace AuthAnthropic {
|
||||
}),
|
||||
})
|
||||
if (!result.ok) throw new ExchangeFailed()
|
||||
const file = Bun.file(path.join(Global.Path.data, "anthropic.json"))
|
||||
await Bun.write(file, result)
|
||||
await fs.chmod(file.name!, 0o600)
|
||||
}
|
||||
|
||||
export async function access() {
|
||||
const file = Bun.file(path.join(Global.Path.data, "anthropic.json"))
|
||||
if (!(await file.exists())) return
|
||||
const result = await file.json()
|
||||
const refresh = result.refresh_token
|
||||
|
||||
20
packages/opencode/src/auth/keys.ts
Normal file
20
packages/opencode/src/auth/keys.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import path from "path"
|
||||
import { Global } from "../global"
|
||||
import fs from "fs/promises"
|
||||
|
||||
export namespace AuthKeys {
|
||||
const file = Bun.file(path.join(Global.Path.data, "auth", "keys.json"))
|
||||
|
||||
export async function get() {
|
||||
return file
|
||||
.json()
|
||||
.catch(() => ({}))
|
||||
.then((x) => x as Record<string, string>)
|
||||
}
|
||||
|
||||
export async function set(key: string, value: string) {
|
||||
const env = await get()
|
||||
await Bun.write(file, JSON.stringify({ ...env, [key]: value }))
|
||||
await fs.chmod(file.name!, 0o600)
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,16 @@ import { UI } from "../ui"
|
||||
|
||||
// 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
|
||||
|
||||
|
||||
|
||||
export const LoginAnthropicCommand = {
|
||||
command: "anthropic",
|
||||
describe: "Login to Anthropic",
|
||||
handler: async () => {
|
||||
const { url, verifier } = await AuthAnthropic.authorize()
|
||||
|
||||
UI.print("Login to Anthropic")
|
||||
UI.print("Open the following URL in your browser:")
|
||||
UI.print(url)
|
||||
UI.print("")
|
||||
UI.println("Login to Anthropic")
|
||||
UI.println("Open the following URL in your browser:")
|
||||
UI.println(url)
|
||||
UI.println("")
|
||||
|
||||
const code = await UI.input("Paste the authorization code here: ")
|
||||
await AuthAnthropic.exchange(code, verifier)
|
||||
|
||||
129
packages/opencode/src/cli/cmd/provider.ts
Normal file
129
packages/opencode/src/cli/cmd/provider.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { AuthAnthropic } from "../../auth/anthropic"
|
||||
import { AuthKeys } from "../../auth/keys"
|
||||
import { UI } from "../ui"
|
||||
import { cmd } from "./cmd"
|
||||
import * as prompts from "@clack/prompts"
|
||||
import open from "open"
|
||||
|
||||
const OPENCODE = [
|
||||
`█▀▀█ █▀▀█ █▀▀ █▀▀▄ █▀▀ █▀▀█ █▀▀▄ █▀▀`,
|
||||
`█░░█ █░░█ █▀▀ █░░█ █░░ █░░█ █░░█ █▀▀`,
|
||||
`▀▀▀▀ █▀▀▀ ▀▀▀ ▀ ▀ ▀▀▀ ▀▀▀▀ ▀▀▀ ▀▀▀`,
|
||||
]
|
||||
|
||||
export const ProviderCommand = cmd({
|
||||
command: "provider",
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.command(ProviderAddCommand)
|
||||
.command(ProviderListCommand)
|
||||
.demandCommand(),
|
||||
describe: "initialize opencode",
|
||||
async handler() {},
|
||||
})
|
||||
|
||||
export const ProviderListCommand = cmd({
|
||||
command: "list",
|
||||
aliases: ["ls"],
|
||||
describe: "list providers",
|
||||
async handler() {
|
||||
prompts.intro("Configured Providers")
|
||||
const keys = await AuthKeys.get()
|
||||
for (const key of Object.keys(keys)) {
|
||||
prompts.log.success(key)
|
||||
}
|
||||
prompts.outro("3 providers configured")
|
||||
},
|
||||
})
|
||||
|
||||
const ProviderAddCommand = cmd({
|
||||
command: "add",
|
||||
describe: "add credentials for various providers",
|
||||
async handler() {
|
||||
UI.empty()
|
||||
for (const row of OPENCODE) {
|
||||
UI.print(" ")
|
||||
for (let i = 0; i < row.length; i++) {
|
||||
const color =
|
||||
i < 18 ? Bun.color("white", "ansi") : Bun.color("gray", "ansi")
|
||||
const char = row[i]
|
||||
UI.print(color + char)
|
||||
}
|
||||
UI.println()
|
||||
}
|
||||
UI.empty()
|
||||
|
||||
prompts.intro("Setup")
|
||||
const keys = await AuthKeys.get()
|
||||
const provider = await prompts.select({
|
||||
message: "Configure a provider",
|
||||
options: [
|
||||
{
|
||||
label: "Anthropic",
|
||||
value: "anthropic",
|
||||
hint: keys["anthropic"] ? "configured" : "",
|
||||
},
|
||||
{
|
||||
label: "OpenAI",
|
||||
value: "openai",
|
||||
hint: keys["openai"] ? "configured" : "",
|
||||
},
|
||||
{
|
||||
label: "Google",
|
||||
value: "google",
|
||||
hint: keys["google"] ? "configured" : "",
|
||||
},
|
||||
],
|
||||
})
|
||||
if (prompts.isCancel(provider)) return
|
||||
|
||||
if (provider === "anthropic") {
|
||||
const method = await prompts.select({
|
||||
message: "Login method",
|
||||
options: [
|
||||
{
|
||||
label: "Claude Pro/Max",
|
||||
value: "oauth",
|
||||
},
|
||||
{
|
||||
label: "API Key",
|
||||
value: "api",
|
||||
},
|
||||
],
|
||||
})
|
||||
if (prompts.isCancel(method)) return
|
||||
|
||||
if (method === "oauth") {
|
||||
// some weird bug where program exits without this
|
||||
await new Promise((resolve) => setTimeout(resolve, 10))
|
||||
const { url, verifier } = await AuthAnthropic.authorize()
|
||||
prompts.note("Opening browser...")
|
||||
await open(url)
|
||||
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)) return
|
||||
await AuthAnthropic.exchange(code, verifier)
|
||||
.then(() => {
|
||||
prompts.log.success("Login successful")
|
||||
})
|
||||
.catch(() => {
|
||||
prompts.log.error("Invalid code")
|
||||
})
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const key = await prompts.password({
|
||||
message: "Enter your API key",
|
||||
})
|
||||
if (prompts.isCancel(key)) return
|
||||
await AuthKeys.set(provider, key)
|
||||
|
||||
prompts.outro("Done")
|
||||
},
|
||||
})
|
||||
@@ -16,24 +16,30 @@ export namespace UI {
|
||||
TEXT_INFO_BOLD: "\x1b[94m\x1b[1m",
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function print(...message: string[]) {
|
||||
Bun.stderr.write(message.join(" "))
|
||||
export function println(...message: string[]) {
|
||||
print(...message)
|
||||
Bun.stderr.write("\n")
|
||||
}
|
||||
|
||||
export function print(...message: string[]) {
|
||||
blank = false
|
||||
Bun.stderr.write(message.join(" "))
|
||||
}
|
||||
|
||||
let blank = false
|
||||
export function empty() {
|
||||
print("" + Style.TEXT_NORMAL)
|
||||
if (blank) return
|
||||
println("" + Style.TEXT_NORMAL)
|
||||
blank = true
|
||||
}
|
||||
|
||||
export async function input(prompt: string): Promise<string> {
|
||||
const readline = require('readline')
|
||||
const readline = require("readline")
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
output: process.stdout,
|
||||
})
|
||||
|
||||
|
||||
return new Promise((resolve) => {
|
||||
rl.question(prompt, (answer: string) => {
|
||||
rl.close()
|
||||
|
||||
@@ -11,11 +11,11 @@ import { Global } from "./global"
|
||||
import yargs from "yargs"
|
||||
import { hideBin } from "yargs/helpers"
|
||||
import { RunCommand } from "./cli/cmd/run"
|
||||
import { LoginAnthropicCommand } from "./cli/cmd/login-anthropic"
|
||||
import { GenerateCommand } from "./cli/cmd/generate"
|
||||
import { VERSION } from "./cli/version"
|
||||
import { ScrapCommand } from "./cli/cmd/scrap"
|
||||
import { Log } from "./util/log"
|
||||
import { ProviderCommand } from "./cli/cmd/provider"
|
||||
|
||||
await Log.init({ print: process.argv.includes("--print-logs") })
|
||||
|
||||
@@ -70,11 +70,6 @@ yargs(hideBin(process.argv))
|
||||
.command(RunCommand)
|
||||
.command(GenerateCommand)
|
||||
.command(ScrapCommand)
|
||||
.command({
|
||||
command: "login",
|
||||
describe: "generate credentials for various providers",
|
||||
builder: (yargs) => yargs.command(LoginAnthropicCommand).demandCommand(),
|
||||
handler: () => {},
|
||||
})
|
||||
.command(ProviderCommand)
|
||||
.help()
|
||||
.parse()
|
||||
|
||||
@@ -4,13 +4,9 @@ import path from "path"
|
||||
|
||||
export namespace ModelsDev {
|
||||
const log = Log.create({ service: "models.dev" })
|
||||
|
||||
function filepath() {
|
||||
return path.join(Global.Path.data, "models.json")
|
||||
}
|
||||
const file = Bun.file(path.join(Global.Path.cache, "models.json"))
|
||||
|
||||
export async function get() {
|
||||
const file = Bun.file(filepath())
|
||||
if (await file.exists()) {
|
||||
refresh()
|
||||
return file.json()
|
||||
@@ -24,6 +20,6 @@ export namespace ModelsDev {
|
||||
const result = await fetch("https://models.dev/api.json")
|
||||
if (!result.ok)
|
||||
throw new Error(`Failed to fetch models.dev: ${result.statusText}`)
|
||||
await Bun.write(filepath(), result)
|
||||
await Bun.write(file, result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import { LspHoverTool } from "../tool/lsp-hover"
|
||||
import { PatchTool } from "../tool/patch"
|
||||
import { ReadTool } from "../tool/read"
|
||||
import type { Tool } from "../tool/tool"
|
||||
|
||||
import { WriteTool } from "../tool/write"
|
||||
import { TodoReadTool, TodoWriteTool } from "../tool/todo"
|
||||
import { AuthAnthropic } from "../auth/anthropic"
|
||||
|
||||
Reference in New Issue
Block a user