fix: 修正 logo 中 N 和 G 字母造型

N 添加对角线笔画(█▄ █),G 添加内横杠(█ ▀█),
避免与 O 字母造型雷同。同步更新 ui.ts 中的硬编码 wordmark。
This commit is contained in:
airlongdian
2026-06-14 09:48:03 +08:00
commit 9ea05df273
5757 changed files with 1170016 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
import { Effect } from "effect"
import { Fff } from "@opencode-ai/core/filesystem/fff.bun"
import { AppRuntime } from "@/effect/app-runtime"
import { FileSystem } from "@opencode-ai/core/filesystem"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { InstanceStore } from "@/project/instance-store"
const dir = AbsolutePath.make(process.cwd())
const FILE_QUERIES = ["fff", "package.json", "tools/ experiment"]
const GREP_QUERIES = ["FileFinder", "import", "grep", "autocomplete"]
const GLOB_QUERIES = ["**/*.test.ts"]
const FILE_LIMIT = 100
const GREP_LIMIT = 50
const GLOB_LIMIT = 50
const run = <A, R>(effect: Effect.Effect<A, unknown, R>) =>
AppRuntime.runPromise(
InstanceStore.Service.use((store) => store.provide({ directory: dir }, effect as never)),
) as Promise<A>
// --- raw Fff picker ---
const t0 = performance.now()
const made = Fff.create({ basePath: dir, aiMode: true })
if (!made.ok) {
console.error("Fff.create failed:", made.error)
process.exit(1)
}
const picker = made.value
console.log(`picker create: ${(performance.now() - t0).toFixed(1)}ms`)
const tw = performance.now()
await picker.waitForScan(2_500)
console.log(`wait for scan: ${(performance.now() - tw).toFixed(1)}ms`)
// warmup grep to let the content index build
const tWarmup = performance.now()
picker.grep("_warmup_", { mode: "regex", maxMatchesPerFile: 1, timeBudgetMs: 1_500 })
console.log(`grep warmup: ${(performance.now() - tWarmup).toFixed(1)}ms`)
console.log()
console.log("--- raw picker (warm) ---")
for (const q of FILE_QUERIES) {
const t = performance.now()
const r = picker.fileSearch(q, { pageSize: Math.max(FILE_LIMIT, 100) })
const count = r.ok ? r.value.items.length : "err"
console.log(`[picker] fileSearch "${q}": ${(performance.now() - t).toFixed(1)}ms (${count} results)`)
}
for (const q of GREP_QUERIES) {
const t = performance.now()
const r = picker.grep(q, { mode: "regex", pageSize: GREP_LIMIT, timeBudgetMs: 1_500 })
const count = r.ok ? r.value.items.length : "err"
console.log(`[picker] grep "${q}": ${(performance.now() - t).toFixed(1)}ms (${count} matches)`)
}
picker.destroy()
// --- Search service: init breakdown ---
console.log()
// 1) runtime + InstanceState + picker create + scan poll
const tRuntime = performance.now()
console.log(`[Search] init file (runtime + picker + scan): ${(performance.now() - tRuntime).toFixed(1)}ms`)
// 2) grep warmup (content index cold-start inside the Search service picker)
const tGrepWarmup = performance.now()
await run(FileSystem.Service.use((svc) => svc.grep({ pattern: "_warmup_grep_", limit: 1 })))
console.log(`[Search] init grep (content index warmup): ${(performance.now() - tGrepWarmup).toFixed(1)}ms`)
console.log()
console.log("--- Search service (warm) ---")
for (const q of FILE_QUERIES) {
const t = performance.now()
const r = await run(FileSystem.Service.use((svc) => svc.find({ query: q, limit: FILE_LIMIT })))
console.log(`[Search.find] "${q}": ${(performance.now() - t).toFixed(1)}ms (${r.length} results)`)
}
for (const q of GREP_QUERIES) {
const t = performance.now()
const r = await run(FileSystem.Service.use((svc) => svc.grep({ pattern: q, limit: GREP_LIMIT })))
console.log(`[Search.grep] "${q}": ${(performance.now() - t).toFixed(1)}ms (${r.length} matches)`)
}
for (const q of GLOB_QUERIES) {
const t = performance.now()
const r = await run(FileSystem.Service.use((svc) => svc.glob({ pattern: q, limit: GLOB_LIMIT })))
console.log(`[Search.glob] "${q}": ${(performance.now() - t).toFixed(1)}ms (${r.length} files)`)
}
process.exit(0)

View File

@@ -0,0 +1,52 @@
// Full-suite timing harness for the test-speed research in ../../perf/test-suite.md.
// Use this for periodic sanity checks; use profile-test-files.ts for discovery.
// Env: BENCH_WARMUPS=0 BENCH_RUNS=1 bun run bench:test
const warmups = Number(Bun.env.BENCH_WARMUPS ?? 0)
const runs = Number(Bun.env.BENCH_RUNS ?? 1)
const timings: number[] = []
if (!Number.isInteger(warmups) || warmups < 0) {
console.error("BENCH_WARMUPS must be a non-negative integer")
process.exit(1)
}
if (!Number.isInteger(runs) || runs < 1) {
console.error("BENCH_RUNS must be a positive integer")
process.exit(1)
}
for (const index of Array.from({ length: warmups + runs }, (_, index) => index)) {
const measured = index >= warmups
const label = measured ? `run ${index - warmups + 1}/${runs}` : `warmup ${index + 1}/${warmups}`
const start = performance.now()
console.log(`bench:test ${label}`)
const proc = Bun.spawn(["bun", "test", "--timeout", "30000"], {
cwd: import.meta.dir + "/..",
stdout: "inherit",
stderr: "inherit",
env: Bun.env,
})
const exitCode = await proc.exited
if (exitCode !== 0) {
console.error(`bench:test failed during ${label} with exit code ${exitCode}`)
process.exit(exitCode)
}
const seconds = (performance.now() - start) / 1000
console.log(`bench:test ${label} ${seconds.toFixed(3)}s`)
if (measured) timings.push(seconds)
}
const sorted = timings.toSorted((a, b) => a - b)
const median = sorted[Math.floor(sorted.length / 2)]
const mean = timings.reduce((sum, timing) => sum + timing, 0) / timings.length
const best = sorted[0] ?? median
const worst = sorted.at(-1) ?? median
console.log(
`bench:test median=${median.toFixed(3)}s mean=${mean.toFixed(3)}s best=${best.toFixed(3)}s worst=${worst.toFixed(3)}s`,
)
console.log(`METRIC test_suite_seconds=${median.toFixed(3)}`)
console.log(`METRIC test_suite_best_seconds=${best.toFixed(3)}`)
console.log(`METRIC test_suite_worst_seconds=${worst.toFixed(3)}`)

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env bun
import { Script } from "@opencode-ai/script"
import path from "path"
import { fileURLToPath } from "url"
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const dir = path.resolve(__dirname, "..")
process.chdir(dir)
const generated = await import("./generate.ts")
await Bun.build({
target: "node",
entrypoints: ["./src/node.ts"],
outdir: "./dist/node",
format: "esm",
sourcemap: "linked",
external: ["jsonc-parser", "@lydell/node-pty"],
define: {
OPENCODE_MODELS_DEV: generated.modelsData,
OPENCODE_CHANNEL: `'${Script.channel}'`,
},
files: {
"opencode-web-ui.gen.ts": "",
},
})
console.log("Build complete")

243
packages/opencode/script/build.ts Executable file
View File

@@ -0,0 +1,243 @@
#!/usr/bin/env bun
import { $ } from "bun"
import fs from "fs"
import path from "path"
import { fileURLToPath } from "url"
import { createSolidTransformPlugin } from "@opentui/solid/bun-plugin"
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const dir = path.resolve(__dirname, "..")
process.chdir(dir)
const generated = await import("./generate.ts")
import { Script } from "@opencode-ai/script"
import pkg from "../package.json"
const singleFlag = process.argv.includes("--single")
const baselineFlag = process.argv.includes("--baseline")
const skipInstall = process.argv.includes("--skip-install")
const sourcemapsFlag = process.argv.includes("--sourcemaps")
const plugin = createSolidTransformPlugin()
const skipEmbedWebUi = process.argv.includes("--skip-embed-web-ui")
const createEmbeddedWebUIBundle = async () => {
console.log(`Building Web UI to embed in the binary`)
const appDir = path.join(import.meta.dirname, "../../app")
const dist = path.join(appDir, "dist")
await $`OPENCODE_CHANNEL=${Script.channel} bun run --cwd ${appDir} build`
const files = (await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: dist })))
.map((file) => file.replaceAll("\\", "/"))
.filter((file) => !file.endsWith(".map"))
.sort()
const imports = files.map((file, i) => {
const spec = path.relative(dir, path.join(dist, file)).replaceAll("\\", "/")
return `import file_${i} from ${JSON.stringify(spec.startsWith(".") ? spec : `./${spec}`)} with { type: "file" };`
})
const entries = files.map((file, i) => ` ${JSON.stringify(file)}: file_${i},`)
return [
`// Import all files as file_$i with type: "file"`,
...imports,
`// Export with original mappings`,
`export default {`,
...entries,
`}`,
].join("\n")
}
const embeddedFileMap = skipEmbedWebUi ? null : await createEmbeddedWebUIBundle()
const allTargets: {
os: string
arch: "arm64" | "x64"
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: "arm64",
},
{
os: "win32",
arch: "x64",
},
{
os: "win32",
arch: "x64",
avx2: false,
},
]
const targets = singleFlag
? allTargets.filter((item) => {
if (item.os !== process.platform || item.arch !== process.arch) {
return false
}
// When building for the current platform, prefer a single native binary by default.
// Baseline binaries require additional Bun artifacts and can be flaky to download.
if (item.avx2 === false) {
return baselineFlag
}
// also skip abi-specific builds for the same reason
if (item.abi !== undefined) {
return false
}
return true
})
: allTargets
await $`rm -rf dist`
const binaries: Record<string, string> = {}
if (!skipInstall) {
await $`bun install --os="*" --cpu="*" @opentui/core@${pkg.dependencies["@opentui/core"]}`
await $`bun install --os="*" --cpu="*" @parcel/watcher@${pkg.dependencies["@parcel/watcher"]}`
await $`bun install --os="*" --cpu="*" @ff-labs/fff-bun@${pkg.dependencies["@ff-labs/fff-bun"]}`
}
for (const item of targets) {
const name = [
pkg.name,
// changing to win32 flags npm for some reason
item.os === "win32" ? "windows" : item.os,
item.arch,
item.avx2 === false ? "baseline" : undefined,
item.abi === undefined ? undefined : item.abi,
]
.filter(Boolean)
.join("-")
console.log(`building ${name}`)
await $`mkdir -p dist/${name}/bin`
const localPath = path.resolve(dir, "node_modules/@opentui/core/parser.worker.js")
const rootPath = path.resolve(dir, "../../node_modules/@opentui/core/parser.worker.js")
const parserWorker = fs.realpathSync(fs.existsSync(localPath) ? localPath : rootPath)
const workerPath = "./src/cli/tui/worker.ts"
// Use platform-specific bunfs root path based on target OS
const bunfsRoot = item.os === "win32" ? "B:/~BUN/root/" : "/$bunfs/root/"
const workerRelativePath = path.relative(dir, parserWorker).replaceAll("\\", "/")
await Bun.build({
conditions: ["bun", "node"],
tsconfig: "./tsconfig.json",
plugins: [plugin],
external: ["node-gyp"],
format: "esm",
minify: true,
sourcemap: sourcemapsFlag ? "linked" : "none",
splitting: true,
compile: {
autoloadBunfig: false,
autoloadDotenv: false,
autoloadTsconfig: true,
autoloadPackageJson: true,
target: name.replace(pkg.name, "bun") as any,
outfile: `dist/${name}/bin/opencode`,
execArgv: [`--user-agent=opencode/${Script.version}`, "--use-system-ca", "--"],
windows: {},
},
files: embeddedFileMap ? { "opencode-web-ui.gen.ts": embeddedFileMap } : {},
entrypoints: ["./src/index.ts", parserWorker, workerPath, ...(embeddedFileMap ? ["opencode-web-ui.gen.ts"] : [])],
define: {
FFF_LIBC: JSON.stringify(item.abi === "musl" ? "musl" : "gnu"),
OPENCODE_VERSION: `'${Script.version}'`,
OPENCODE_MODELS_DEV: generated.modelsData,
OTUI_TREE_SITTER_WORKER_PATH: bunfsRoot + workerRelativePath,
OPENCODE_WORKER_PATH: workerPath,
OPENCODE_CHANNEL: `'${Script.channel}'`,
OPENCODE_LIBC: item.os === "linux" ? `'${item.abi ?? "glibc"}'` : "",
...(item.os === "linux" ? { "process.env.OPENTUI_LIBC": JSON.stringify(item.abi ?? "glibc") } : {}),
},
})
// Smoke test: only run if binary is for current platform
if (item.os === process.platform && item.arch === process.arch && !item.abi) {
const binaryPath = `dist/${name}/bin/opencode`
console.log(`Running smoke test: ${binaryPath} --version`)
try {
const versionOutput = await $`${binaryPath} --version`.text()
console.log(`Smoke test passed: ${versionOutput.trim()}`)
} catch (e) {
console.error(`Smoke test failed for ${name}:`, e)
process.exit(1)
}
}
await $`rm -rf ./dist/${name}/bin/tui`
await Bun.file(`dist/${name}/package.json`).write(
JSON.stringify(
{
name,
version: Script.version,
preferUnplugged: true,
os: [item.os],
cpu: [item.arch],
...(item.abi ? { libc: [item.abi] } : {}),
},
null,
2,
),
)
binaries[name] = Script.version
}
if (Script.release) {
for (const key of Object.keys(binaries)) {
if (key.includes("linux")) {
await $`tar -czf ../../${key}.tar.gz *`.cwd(`dist/${key}/bin`)
} else {
await $`zip -r ../../${key}.zip *`.cwd(`dist/${key}/bin`)
}
}
await $`gh release upload v${Script.version} ./dist/*.zip ./dist/*.tar.gz --clobber --repo ${process.env.GH_REPO}`
}
export { binaries }

View File

@@ -0,0 +1,14 @@
import path from "path"
import { fileURLToPath } from "url"
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const dir = path.resolve(__dirname, "..")
process.chdir(dir)
const modelsUrl = process.env.OPENCODE_MODELS_URL || "https://models.dev"
export const modelsData = process.env.MODELS_DEV_API_JSON
? await Bun.file(process.env.MODELS_DEV_API_JSON).text()
: await fetch(`${modelsUrl}/api.json`).then((x) => x.text())
console.log("Loaded models.dev snapshot")

View File

@@ -0,0 +1 @@
await import("../test/server/httpapi-exercise/index")

View File

@@ -0,0 +1,189 @@
#!/usr/bin/env node
import childProcess from "child_process"
import fs from "fs"
import os from "os"
import path from "path"
import { createRequire } from "module"
import { fileURLToPath } from "url"
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const require = createRequire(import.meta.url)
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"), "utf8"))
const platformMap = {
darwin: "darwin",
linux: "linux",
win32: "windows",
}
const archMap = {
x64: "x64",
arm64: "arm64",
arm: "arm",
}
const platform = platformMap[os.platform()] ?? os.platform()
const arch = archMap[os.arch()] ?? os.arch()
const base = `opencode-${platform}-${arch}`
const sourceBinary = platform === "windows" ? "opencode.exe" : "opencode"
const targetBinary = path.join(__dirname, "bin", "opencode.exe")
function supportsAvx2() {
if (arch !== "x64") return false
if (platform === "linux") {
try {
return /(^|\s)avx2(\s|$)/i.test(fs.readFileSync("/proc/cpuinfo", "utf8"))
} catch {
return false
}
}
if (platform === "darwin") {
try {
const result = childProcess.spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], {
encoding: "utf8",
timeout: 1500,
})
if (result.status !== 0) return false
return (result.stdout || "").trim() === "1"
} catch {
return false
}
}
if (platform === "windows") {
const command =
'(Add-Type -MemberDefinition "[DllImport(""kernel32.dll"")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)'
for (const executable of ["powershell.exe", "pwsh.exe", "pwsh", "powershell"]) {
try {
const result = childProcess.spawnSync(executable, ["-NoProfile", "-NonInteractive", "-Command", command], {
encoding: "utf8",
timeout: 3000,
windowsHide: true,
})
if (result.status !== 0) continue
const output = (result.stdout || "").trim().toLowerCase()
if (output === "true" || output === "1") return true
if (output === "false" || output === "0") return false
} catch {
continue
}
}
}
return false
}
function isMusl() {
if (platform !== "linux") return false
try {
if (fs.existsSync("/etc/alpine-release")) return true
} catch {
// Ignore filesystem probes that are blocked by the host.
}
try {
const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" })
return `${result.stdout || ""}${result.stderr || ""}`.toLowerCase().includes("musl")
} catch {
return false
}
}
function packageNames() {
const baseline = arch === "x64" && !supportsAvx2()
if (platform === "linux") {
if (isMusl()) {
if (arch === "x64")
return baseline
? [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base]
: [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`]
return [`${base}-musl`, base]
}
if (arch === "x64")
return baseline
? [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`]
: [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`]
return [base, `${base}-musl`]
}
if (arch === "x64") return baseline ? [`${base}-baseline`, base] : [base, `${base}-baseline`]
return [base]
}
function resolveBinary(name) {
const packageJsonPath = require.resolve(`${name}/package.json`)
const binaryPath = path.join(path.dirname(packageJsonPath), "bin", sourceBinary)
if (!fs.existsSync(binaryPath)) throw new Error(`Binary not found at ${binaryPath}`)
return binaryPath
}
function installPackage(name) {
const version = packageJson.optionalDependencies?.[name]
if (!version) return
const temp = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-install-"))
try {
const result = childProcess.spawnSync(
"npm",
["install", "--ignore-scripts", "--no-save", "--loglevel=error", "--prefix", temp, `${name}@${version}`],
{ stdio: "inherit", windowsHide: true },
)
if (result.status !== 0) return
const packageDir = path.join(temp, "node_modules", name)
copyBinary(path.join(packageDir, "bin", sourceBinary), targetBinary)
return true
} finally {
fs.rmSync(temp, { recursive: true, force: true })
}
}
function copyBinary(source, target) {
if (!fs.existsSync(source)) throw new Error(`Binary not found at ${source}`)
fs.mkdirSync(path.dirname(target), { recursive: true })
if (fs.existsSync(target)) fs.unlinkSync(target)
try {
fs.linkSync(source, target)
} catch {
fs.copyFileSync(source, target)
}
fs.chmodSync(target, 0o755)
}
function verifyBinary() {
const result = childProcess.spawnSync(targetBinary, ["--version"], {
encoding: "utf8",
stdio: "ignore",
windowsHide: true,
})
return result.status === 0
}
function main() {
for (const name of packageNames()) {
try {
copyBinary(resolveBinary(name), targetBinary)
if (verifyBinary()) return
} catch {
if (installPackage(name) && verifyBinary()) return
}
}
throw new Error(
`It seems your package manager failed to install the right opencode CLI package. Try manually installing ${packageNames()
.map((name) => JSON.stringify(name))
.join(" or ")}.`,
)
}
try {
main()
} catch (error) {
console.error(error.message)
process.exit(1)
}

View File

@@ -0,0 +1,42 @@
// Per-file profiler for finding candidate test-speed work; see ../../perf/test-suite.md
// for the benchmark notes, kept wins, and discarded experiments.
// Example: TEST_PROFILE_GLOB='test/server/**/*.test.ts' TEST_PROFILE_TOP=15 bun run profile:test
const pattern = Bun.env.TEST_PROFILE_GLOB ?? "test/**/*.test.{ts,tsx}"
const limit = Number(Bun.env.TEST_PROFILE_LIMIT ?? 0)
const timeout = Bun.env.TEST_PROFILE_TIMEOUT ?? "30000"
const files = Array.fromAsync(new Bun.Glob(pattern).scan({ cwd: import.meta.dir + "/..", onlyFiles: true }))
.then((files) => files.toSorted())
.then((files) => (limit > 0 ? files.slice(0, limit) : files))
const results = []
for (const file of await files) {
const start = performance.now()
const proc = Bun.spawn(["bun", "test", "--timeout", timeout, file], {
cwd: import.meta.dir + "/..",
stdout: "pipe",
stderr: "pipe",
env: Bun.env,
})
const [output, error, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
])
const seconds = (performance.now() - start) / 1000
results.push({ file, seconds, exitCode })
console.log(`${exitCode === 0 ? "PASS" : "FAIL"} ${seconds.toFixed(3)}s ${file}`)
if (exitCode !== 0) console.log((output + error).trim())
}
const sorted = results.toSorted((a, b) => b.seconds - a.seconds)
console.log("\nSlowest test files:")
for (const result of sorted.slice(0, Number(Bun.env.TEST_PROFILE_TOP ?? 20))) {
console.log(`${result.seconds.toFixed(3)}s ${result.exitCode === 0 ? "PASS" : "FAIL"} ${result.file}`)
}
if (sorted[0]) {
console.log(`METRIC slowest_test_file_seconds=${sorted[0].seconds.toFixed(3)}`)
console.log(`METRIC profiled_test_files=${results.length}`)
}
if (results.some((result) => result.exitCode !== 0)) process.exit(1)

View File

@@ -0,0 +1,213 @@
#!/usr/bin/env bun
import { $ } from "bun"
import pkg from "../package.json"
import { Script } from "@opencode-ai/script"
import { fileURLToPath } from "url"
const dir = fileURLToPath(new URL("..", import.meta.url))
process.chdir(dir)
async function published(name: string, version: string) {
return (await $`npm view ${name}@${version} version`.nothrow()).exitCode === 0
}
async function publish(dir: string, name: string, version: string) {
// GitHub artifact downloads can drop the executable bit, and Docker uses the
// unpacked dist binaries directly rather than the published tarball.
if (process.platform !== "win32") await $`chmod -R 755 .`.cwd(dir)
if (await published(name, version)) {
console.log(`already published ${name}@${version}`)
return
}
await $`bun pm pack`.cwd(dir)
await $`npm publish *.tgz --access public --tag ${Script.channel}`.cwd(dir)
}
const binaries: Record<string, string> = {}
for (const filepath of new Bun.Glob("*/package.json").scanSync({ cwd: "./dist" })) {
const pkg = await Bun.file(`./dist/${filepath}`).json()
binaries[pkg.name] = pkg.version
}
console.log("binaries", binaries)
const version = Object.values(binaries)[0]
await $`mkdir -p ./dist/${pkg.name}`
await $`mkdir -p ./dist/${pkg.name}/bin`
await $`cp ./script/postinstall.mjs ./dist/${pkg.name}/postinstall.mjs`
await Bun.file(`./dist/${pkg.name}/LICENSE`).write(await Bun.file("../../LICENSE").text())
await Bun.file(`./dist/${pkg.name}/bin/${pkg.name}.exe`).write(
[
`echo "Error: ${pkg.name}-ai's postinstall script was not run." >&2`,
'echo "" >&2',
'echo "This occurs when using --ignore-scripts during installation, or when using a" >&2',
'echo "package manager like pnpm that does not run postinstall scripts by default." >&2',
'echo "" >&2',
'echo "To fix this, run the postinstall script manually:" >&2',
`echo " cd node_modules/${pkg.name}-ai && node postinstall.mjs" >&2`,
'echo "" >&2',
`echo "Or reinstall ${pkg.name}-ai without the --ignore-scripts flag." >&2`,
"exit 1",
"",
].join("\n"),
)
await Bun.file(`./dist/${pkg.name}/package.json`).write(
JSON.stringify(
{
name: pkg.name + "-ai",
bin: {
[pkg.name]: `./bin/${pkg.name}.exe`,
},
scripts: {
postinstall: "node ./postinstall.mjs",
},
version: version,
license: pkg.license,
os: ["darwin", "linux", "win32"],
cpu: ["arm64", "x64"],
optionalDependencies: binaries,
},
null,
2,
),
)
const tasks = Object.entries(binaries).map(async ([name]) => {
await publish(`./dist/${name}`, name, binaries[name])
})
await Promise.all(tasks)
await publish(`./dist/${pkg.name}`, `${pkg.name}-ai`, version)
const image = "ghcr.io/anomalyco/opencode"
const platforms = "linux/amd64,linux/arm64"
const tags = [`${image}:${version}`, `${image}:${Script.channel}`]
const tagFlags = tags.flatMap((t) => ["-t", t])
// registries
if (!Script.preview) {
await $`docker buildx build --platform ${platforms} ${tagFlags} --push .`
// Calculate SHA values
const arm64Sha = await $`sha256sum ./dist/opencode-linux-arm64.tar.gz | cut -d' ' -f1`.text().then((x) => x.trim())
const x64Sha = await $`sha256sum ./dist/opencode-linux-x64.tar.gz | cut -d' ' -f1`.text().then((x) => x.trim())
const macX64Sha = await $`sha256sum ./dist/opencode-darwin-x64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
const macArm64Sha = await $`sha256sum ./dist/opencode-darwin-arm64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
const [pkgver, _subver = ""] = Script.version.split(/(-.*)/, 2)
// arch
const binaryPkgbuild = [
"# Maintainer: dax",
"# Maintainer: adam",
"",
"pkgname='opencode-bin'",
`pkgver=${pkgver}`,
`_subver=${_subver}`,
"options=('!debug' '!strip')",
"pkgrel=1",
"pkgdesc='The AI coding agent built for the terminal.'",
"url='https://github.com/anomalyco/opencode'",
"arch=('aarch64' 'x86_64')",
"license=('MIT')",
"provides=('opencode')",
"conflicts=('opencode')",
"depends=('ripgrep')",
"",
`source_aarch64=("\${pkgname}_\${pkgver}_aarch64.tar.gz::https://github.com/anomalyco/opencode/releases/download/v\${pkgver}\${_subver}/opencode-linux-arm64.tar.gz")`,
`sha256sums_aarch64=('${arm64Sha}')`,
`source_x86_64=("\${pkgname}_\${pkgver}_x86_64.tar.gz::https://github.com/anomalyco/opencode/releases/download/v\${pkgver}\${_subver}/opencode-linux-x64.tar.gz")`,
`sha256sums_x86_64=('${x64Sha}')`,
"",
"package() {",
' install -Dm755 ./opencode "${pkgdir}/usr/bin/opencode"',
"}",
"",
].join("\n")
for (const [pkg, pkgbuild] of [["opencode-bin", binaryPkgbuild]]) {
for (let i = 0; i < 30; i++) {
try {
await $`rm -rf ./dist/aur-${pkg}`
await $`git clone ssh://aur@aur.archlinux.org/${pkg}.git ./dist/aur-${pkg}`
await $`cd ./dist/aur-${pkg} && git checkout master`
await Bun.file(`./dist/aur-${pkg}/PKGBUILD`).write(pkgbuild)
await $`cd ./dist/aur-${pkg} && makepkg --printsrcinfo > .SRCINFO`
await $`cd ./dist/aur-${pkg} && git add PKGBUILD .SRCINFO`
if ((await $`cd ./dist/aur-${pkg} && git diff --cached --quiet`.nothrow()).exitCode === 0) break
await $`cd ./dist/aur-${pkg} && git commit -m "Update to v${Script.version}"`
await $`cd ./dist/aur-${pkg} && git push`
break
} catch {
continue
}
}
}
// Homebrew formula
const homebrewFormula = [
"# typed: false",
"# frozen_string_literal: true",
"",
"# This file was generated by GoReleaser. DO NOT EDIT.",
"class Opencode < Formula",
` desc "The AI coding agent built for the terminal."`,
` homepage "https://github.com/anomalyco/opencode"`,
` version "${Script.version.split("-")[0]}"`,
"",
` depends_on "ripgrep"`,
"",
" on_macos do",
" if Hardware::CPU.intel?",
` url "https://github.com/anomalyco/opencode/releases/download/v${Script.version}/opencode-darwin-x64.zip"`,
` sha256 "${macX64Sha}"`,
"",
" def install",
' bin.install "opencode"',
" end",
" end",
" if Hardware::CPU.arm?",
` url "https://github.com/anomalyco/opencode/releases/download/v${Script.version}/opencode-darwin-arm64.zip"`,
` sha256 "${macArm64Sha}"`,
"",
" def install",
' bin.install "opencode"',
" end",
" end",
" end",
"",
" on_linux do",
" if Hardware::CPU.intel? and Hardware::CPU.is_64_bit?",
` url "https://github.com/anomalyco/opencode/releases/download/v${Script.version}/opencode-linux-x64.tar.gz"`,
` sha256 "${x64Sha}"`,
" def install",
' bin.install "opencode"',
" end",
" end",
" if Hardware::CPU.arm? and Hardware::CPU.is_64_bit?",
` url "https://github.com/anomalyco/opencode/releases/download/v${Script.version}/opencode-linux-arm64.tar.gz"`,
` sha256 "${arm64Sha}"`,
" def install",
' bin.install "opencode"',
" end",
" end",
" end",
"end",
"",
"",
].join("\n")
const token = process.env.GITHUB_TOKEN
if (!token) {
console.error("GITHUB_TOKEN is required to update homebrew tap")
process.exit(1)
}
const tap = `https://x-access-token:${token}@github.com/anomalyco/homebrew-tap.git`
await $`rm -rf ./dist/homebrew-tap`
await $`git clone ${tap} ./dist/homebrew-tap`
await Bun.file("./dist/homebrew-tap/opencode.rb").write(homebrewFormula)
await $`cd ./dist/homebrew-tap && git add opencode.rb`
if ((await $`cd ./dist/homebrew-tap && git diff --cached --quiet`.nothrow()).exitCode !== 0) {
await $`cd ./dist/homebrew-tap && git commit -m "Update to v${Script.version}"`
await $`cd ./dist/homebrew-tap && git push`
}
}

View File

@@ -0,0 +1,106 @@
#!/usr/bin/env bun
// This script runs a separate OpenCode server to be used as a remote
// workspace, simulating a remote environment but all local to make
// debugger easier
//
// *Important*: make sure you add the debug workspace plugin first.
// In `.opencode/opencode.jsonc` in the root of this project add:
//
// "plugin": ["../packages/opencode/src/control-plane/dev/debug-workspace-plugin.ts"]
//
// Afterwards, run `./packages/opencode/script/run-workspace-server`
import { stat } from "node:fs/promises"
import { setTimeout as sleep } from "node:timers/promises"
const DEV_DATA_FILE = "/tmp/opencode-workspace-dev-data.json"
const RESTART_POLL_INTERVAL = 250
async function readData() {
return await Bun.file(DEV_DATA_FILE).json()
}
async function readDataMtime() {
return await stat(DEV_DATA_FILE)
.then((info) => info.mtimeMs)
.catch((error) => {
if (typeof error === "object" && error && "code" in error && error.code === "ENOENT") {
return undefined
}
throw error
})
}
async function readSnapshot() {
while (true) {
try {
const before = await readDataMtime()
if (before === undefined) {
await sleep(RESTART_POLL_INTERVAL)
continue
}
const data = await readData()
const after = await readDataMtime()
if (before === after) {
return { data, mtime: after }
}
} catch (error) {
if (typeof error === "object" && error && "code" in error && error.code === "ENOENT") {
await sleep(RESTART_POLL_INTERVAL)
continue
}
throw error
}
}
}
function startDevServer(data: any) {
const env = Object.fromEntries(Object.entries(data.env ?? {}).filter(([, value]) => value !== undefined))
return Bun.spawn(["bun", "run", "dev", "serve", "--port", String(data.port), "--print-logs"], {
env: {
...process.env,
...env,
XDG_DATA_HOME: "/tmp/data",
},
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
})
}
async function waitForRestartSignal(mtime: number, signal: AbortSignal) {
while (!signal.aborted) {
await sleep(RESTART_POLL_INTERVAL)
if (signal.aborted) return false
if ((await readDataMtime()) !== mtime) return true
}
return false
}
while (true) {
const { data, mtime } = await readSnapshot()
const proc = startDevServer(data)
const restartAbort = new AbortController()
const result = await Promise.race([
proc.exited.then((code) => ({ type: "exit" as const, code })),
waitForRestartSignal(mtime, restartAbort.signal).then((restart) => ({ type: "restart" as const, restart })),
])
restartAbort.abort()
if (result.type === "restart" && result.restart) {
proc.kill()
await proc.exited
continue
}
process.exit(result.code)
}

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env bun
import { Config } from "@/config/config"
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
import { TuiConfig } from "@opencode-ai/tui/config"
import { Schema } from "effect"
type JsonSchema = Record<string, unknown>
const MODEL_REF = "https://models.dev/model-schema.json#/$defs/Model"
function generateEffect(schema: Schema.Top) {
const document = Schema.toJsonSchemaDocument(schema)
const normalized = normalize({
$schema: "https://json-schema.org/draft/2020-12/schema",
...document.schema,
$defs: document.definitions,
})
if (!isRecord(normalized)) throw new Error("schema generator produced a non-object schema")
const restored = restoreModelRefs(normalized)
if (!isRecord(restored)) throw new Error("schema generator produced a non-object schema")
restored.allowComments = true
restored.allowTrailingCommas = true
return restored
}
function normalize(value: unknown): unknown {
if (Array.isArray(value)) return value.map(normalize)
if (!isRecord(value)) return value
const schema = Object.fromEntries(Object.entries(value).map(([key, item]) => [key, normalize(item)]))
if (Array.isArray(schema.anyOf)) {
const anyOf = schema.anyOf.filter((item) => !isRecord(item) || item.type !== "null")
if (anyOf.length !== schema.anyOf.length) {
const { anyOf: _, ...rest } = schema
if (anyOf.length === 1 && isRecord(anyOf[0])) return normalize({ ...anyOf[0], ...rest })
return { ...rest, anyOf }
}
}
if (Array.isArray(schema.allOf) && schema.allOf.length === 1 && isRecord(schema.allOf[0])) {
const { allOf: _, ...rest } = schema
return normalize({ ...schema.allOf[0], ...rest })
}
if (schema.type === "integer" && schema.maximum === undefined) {
return { ...schema, maximum: Number.MAX_SAFE_INTEGER }
}
return schema
}
function restoreModelRefs(value: unknown, key?: string): unknown {
if (Array.isArray(value)) return value.map((item) => restoreModelRefs(item))
if (!isRecord(value)) return value
const schema = Object.fromEntries(Object.entries(value).map(([name, item]) => [name, restoreModelRefs(item, name)]))
if ((key === "model" || key === "small_model") && schema.type === "string") {
return { ...schema, $ref: MODEL_REF }
}
return schema
}
function isRecord(value: unknown): value is JsonSchema {
return typeof value === "object" && value !== null && !Array.isArray(value)
}
const configFile = process.argv[2]
const tuiFile = process.argv[3]
console.log(configFile)
await Bun.write(configFile, JSON.stringify(generateEffect(ConfigV1.Info), null, 2))
if (tuiFile) {
console.log(tuiFile)
await Bun.write(tuiFile, JSON.stringify(generateEffect(TuiConfig.Info), null, 2))
}

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bun
import path from "path"
const toDynamicallyImport = path.join(process.cwd(), process.argv[2])
await import(toDynamicallyImport)
console.log(performance.now())

View File

@@ -0,0 +1,153 @@
#!/usr/bin/env bun
import * as path from "path"
import * as ts from "typescript"
const BASE_DIR = "/home/thdxr/dev/projects/anomalyco/opencode/packages/opencode"
// Get entry file from command line arg or use default
const ENTRY_FILE = process.argv[2] || "src/plugin/tui/runtime.ts"
const visited = new Set<string>()
function resolveImport(importPath: string, fromFile: string): string | null {
if (importPath.startsWith("@/")) {
return path.join(BASE_DIR, "src", importPath.slice(2))
}
if (importPath.startsWith("./") || importPath.startsWith("../")) {
const dir = path.dirname(fromFile)
return path.resolve(dir, importPath)
}
return null
}
function isInternalImport(importPath: string): boolean {
return importPath.startsWith("@/") || importPath.startsWith("./") || importPath.startsWith("../")
}
async function tryExtensions(filePath: string): Promise<string | null> {
const extensions = [".ts", ".tsx", ".js", ".jsx"]
try {
const file = Bun.file(filePath)
const stat = await file.stat()
if (stat?.isDirectory()) {
for (const ext of extensions) {
const indexPath = path.join(filePath, "index" + ext)
const indexFile = Bun.file(indexPath)
if (await indexFile.exists()) return indexPath
}
return null
}
// It's a file
return filePath
} catch {
// Path doesn't exist, try adding extensions
for (const ext of extensions) {
const withExt = filePath + ext
const extFile = Bun.file(withExt)
if (await extFile.exists()) return withExt
}
return null
}
}
function extractImports(sourceFile: ts.SourceFile): string[] {
const imports: string[] = []
function visit(node: ts.Node) {
// import x from "path" or import { x } from "path"
if (ts.isImportDeclaration(node)) {
// Skip type-only imports
if (node.importClause?.isTypeOnly) return
const moduleSpec = node.moduleSpecifier
if (ts.isStringLiteral(moduleSpec)) {
imports.push(moduleSpec.text)
}
}
// export { x } from "path"
if (ts.isExportDeclaration(node) && node.moduleSpecifier) {
if (ts.isStringLiteral(node.moduleSpecifier)) {
imports.push(node.moduleSpecifier.text)
}
}
// Dynamic import: import("path")
if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
const arg = node.arguments[0]
if (arg && ts.isStringLiteral(arg)) {
imports.push(arg.text)
}
}
ts.forEachChild(node, visit)
}
visit(sourceFile)
return imports
}
async function traceFile(filePath: string, depth = 0): Promise<void> {
const normalizedPath = path.relative(BASE_DIR, filePath)
if (visited.has(filePath)) {
return
}
// Only trace TypeScript/JavaScript files
if (!filePath.match(/\.(ts|tsx|js|jsx)$/)) {
return
}
visited.add(filePath)
console.log("\t".repeat(depth) + normalizedPath)
let content: string
try {
content = await Bun.file(filePath).text()
} catch {
return
}
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true)
const imports = extractImports(sourceFile)
const internalImports = imports.filter(isInternalImport)
const externalImports = imports.filter((imp) => !isInternalImport(imp))
// Print external imports
for (const imp of externalImports) {
console.log("\t".repeat(depth + 1) + `[ext] ${imp}`)
}
for (const imp of internalImports) {
const resolved = resolveImport(imp, filePath)
if (!resolved) continue
const actualPath = await tryExtensions(resolved)
if (!actualPath) continue
await traceFile(actualPath, depth + 1)
}
}
async function main() {
const entryPath = path.join(BASE_DIR, ENTRY_FILE)
// Check if file exists
const file = Bun.file(entryPath)
if (!(await file.exists())) {
console.error(`File not found: ${ENTRY_FILE}`)
console.error(`Resolved to: ${entryPath}`)
process.exit(1)
}
await traceFile(entryPath)
}
main().catch(console.error)