Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed a.out
Empty file.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
"typecheck": "bun turbo typecheck",
"prepare": "husky",
"install:local": "bun run --cwd packages/opencode build --single --install",
"random": "echo 'Random script'"
},
"workspaces": {
Expand Down Expand Up @@ -83,4 +84,4 @@
"@types/bun": "catalog:",
"@types/node": "catalog:"
}
}
}
116 changes: 64 additions & 52 deletions packages/opencode/script/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,58 +23,58 @@ const allTargets: {
abi?: "musl"
avx2?: false
}[] = [
{
os: "linux",
arch: "arm64",
},
{
os: "linux",
arch: "x64",
},
{
os: "linux",
arch: "x64",
avx2: false,
},
{
os: "linux",
arch: "arm64",
abi: "musl",
},
{
os: "linux",
arch: "x64",
abi: "musl",
},
{
os: "linux",
arch: "x64",
abi: "musl",
avx2: false,
},
{
os: "darwin",
arch: "arm64",
},
{
os: "darwin",
arch: "x64",
},
{
os: "darwin",
arch: "x64",
avx2: false,
},
{
os: "win32",
arch: "x64",
},
{
os: "win32",
arch: "x64",
avx2: false,
},
]
{
os: "linux",
arch: "arm64",
},
{
os: "linux",
arch: "x64",
},
{
os: "linux",
arch: "x64",
avx2: false,
},
{
os: "linux",
arch: "arm64",
abi: "musl",
},
{
os: "linux",
arch: "x64",
abi: "musl",
},
{
os: "linux",
arch: "x64",
abi: "musl",
avx2: false,
},
{
os: "darwin",
arch: "arm64",
},
{
os: "darwin",
arch: "x64",
},
{
os: "darwin",
arch: "x64",
avx2: false,
},
{
os: "win32",
arch: "x64",
},
{
os: "win32",
arch: "x64",
avx2: false,
},
]

const targets = singleFlag
? allTargets.filter((item) => item.os === process.platform && item.arch === process.arch)
Expand Down Expand Up @@ -138,4 +138,16 @@ for (const item of targets) {
binaries[name] = Script.version
}

if (process.argv.includes("--install")) {
const os = process.platform === "win32" ? "windows" : process.platform
const arch = process.arch === "x64" ? "x64" : "arm64"
const name = [pkg.name, os, arch].join("-")
const binary = os === "windows" ? "opencode.exe" : "opencode"
const installDir = path.join(process.env.HOME || "", ".opencode", "bin")
await $`mkdir -p ${installDir}`
await $`cp dist/${name}/bin/${binary} ${installDir}/${binary}`
if (os !== "windows") await $`chmod +x ${installDir}/${binary}`
console.log(`✅ Installed to ${installDir}/${binary}`)
}

export { binaries }
1 change: 1 addition & 0 deletions packages/opencode/src/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export namespace Auth {
.object({
type: z.literal("api"),
key: z.string(),
organizationId: z.string().optional(),
})
.meta({ ref: "ApiAuth" })

Expand Down
67 changes: 65 additions & 2 deletions packages/opencode/src/cli/cmd/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import os from "os"
import { Global } from "../../global"
import { Plugin } from "../../plugin"
import { Instance } from "../../project/instance"
import open from "open"

export const AuthCommand = cmd({
command: "auth",
describe: "manage credentials",
builder: (yargs) =>
yargs.command(AuthLoginCommand).command(AuthLogoutCommand).command(AuthListCommand).demandCommand(),
async handler() {},
async handler() { },
})

export const AuthListCommand = cmd({
Expand Down Expand Up @@ -102,7 +103,7 @@ export const AuthLoginCommand = cmd({
prompts.outro("Done")
return
}
await ModelsDev.refresh().catch(() => {})
await ModelsDev.refresh().catch(() => { })
const providers = await ModelsDev.get()
const priority: Record<string, number> = {
opencode: 0,
Expand All @@ -112,6 +113,7 @@ export const AuthLoginCommand = cmd({
google: 4,
openrouter: 5,
vercel: 6,
kilocode: 7,
}
let provider = await prompts.autocomplete({
message: "Select provider",
Expand Down Expand Up @@ -139,6 +141,67 @@ export const AuthLoginCommand = cmd({

if (prompts.isCancel(provider)) throw new UI.CancelledError()

if (provider === "kilocode") {
const spinner = prompts.spinner()
spinner.start("Initiating Kilo Code authorization...")

try {
const response = await fetch("https://api.kilo.ai/api/device-auth/codes", {
method: "POST",
headers: { "Content-Type": "application/json" },
})

if (!response.ok) throw new Error(`Failed to initiate auth: ${response.status}`)

const { code, verificationUrl, expiresIn } = (await response.json()) as any
spinner.stop(`Code: ${UI.Style.TEXT_NORMAL_BOLD}${code}`)

prompts.log.info(`Go to: ${UI.Style.TEXT_NORMAL_BOLD}${verificationUrl}`)
await open(verificationUrl)

const pollSpinner = prompts.spinner()
pollSpinner.start("Waiting for authorization...")

const start = Date.now()
while (Date.now() - start < expiresIn * 1000) {
const pollResponse = await fetch(`https://api.kilo.ai/api/device-auth/codes/${code}`)

if (pollResponse.status === 200) {
const data = (await pollResponse.json()) as any
if (data.status === "approved" && data.token) {
await Auth.set("kilocode", {
type: "api",
key: data.token,
})
pollSpinner.stop(`Login successful as ${data.userEmail}`)
prompts.outro("Done")
return
}
}

if (pollResponse.status === 403) {
pollSpinner.stop("Authorization denied", 1)
prompts.outro("Done")
return
}

if (pollResponse.status === 410) {
pollSpinner.stop("Authorization expired", 1)
prompts.outro("Done")
return
}

await new Promise((resolve) => setTimeout(resolve, 3000))
}

pollSpinner.stop("Authorization timed out", 1)
} catch (e) {
spinner.stop(`Error: ${e instanceof Error ? e.message : String(e)}`, 1)
}
prompts.outro("Done")
return
}

const plugin = await Plugin.list().then((x) => x.find((x) => x.auth?.provider === provider))
if (plugin && plugin.auth) {
let index = 0
Expand Down
17 changes: 16 additions & 1 deletion packages/opencode/src/cli/cmd/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { select } from "@clack/prompts"
import { createOpencodeClient, type OpencodeClient } from "@opencode-ai/sdk"
import { Server } from "../../server/server"
import { Provider } from "../../provider/provider"
import { Auth } from "../../auth"

const TOOL: Record<string, [string, string]> = {
todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
Expand Down Expand Up @@ -88,6 +89,20 @@ export const RunCommand = cmd({
})
},
handler: async (args) => {
const { isJwtExpired } = await import("../../util/jwt")
const auth = await Auth.get("kilocode")
const defaultModel = await Provider.defaultModel().catch(() => undefined)
const isUsingKilocode =
args.model?.includes("kilocode") ||
(!args.model && defaultModel?.providerID === "kilocode")

if (isUsingKilocode && auth && auth.type === "api") {
if (isJwtExpired(auth.key)) {
UI.error("Kilo Code token has expired. Please run 'opencode auth login kilocode' to refresh it.")
process.exit(1)
}
}

let message = args.message.join(" ")

const fileParts: any[] = []
Expand All @@ -97,7 +112,7 @@ export const RunCommand = cmd({
for (const filePath of files) {
const resolvedPath = path.resolve(process.cwd(), filePath)
const file = Bun.file(resolvedPath)
const stats = await file.stat().catch(() => {})
const stats = await file.stat().catch(() => { })
if (!stats) {
UI.error(`File not found: ${filePath}`)
process.exit(1)
Expand Down
Loading