Skip to content

Commit 1ff1910

Browse files
authored
feat(core): add command registry (anomalyco#30624)
1 parent 70bb710 commit 1ff1910

150 files changed

Lines changed: 4642 additions & 2546 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/publish.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ jobs:
9090
id: build
9191
run: |
9292
./packages/opencode/script/build.ts ${{ (github.ref_name == 'beta' && '--sourcemaps') || '' }}
93+
./packages/cli/script/build.ts ${{ (github.ref_name == 'beta' && '--sourcemaps') || '' }}
9394
env:
9495
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
9596
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
@@ -107,6 +108,12 @@ jobs:
107108
with:
108109
name: opencode-cli-windows
109110
path: packages/opencode/dist/opencode-windows*
111+
112+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
113+
with:
114+
name: opencode-preview-cli
115+
path: packages/cli/dist/lildax-*
116+
110117
outputs:
111118
version: ${{ needs.version.outputs.version }}
112119

@@ -446,6 +453,11 @@ jobs:
446453
name: opencode-cli-signed-windows
447454
path: packages/opencode/dist
448455

456+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
457+
with:
458+
name: opencode-preview-cli
459+
path: packages/cli/dist
460+
449461
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
450462
if: needs.version.outputs.release
451463
with:

bun.lock

Lines changed: 22 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/bin/lildax.cjs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env node
2+
3+
const childProcess = require("child_process")
4+
const fs = require("fs")
5+
const path = require("path")
6+
const os = require("os")
7+
8+
const forwardedSignals = ["SIGINT", "SIGTERM", "SIGHUP"]
9+
10+
function run(target) {
11+
const child = childProcess.spawn(target, process.argv.slice(2), { stdio: "inherit" })
12+
child.on("error", (error) => {
13+
console.error(error.message)
14+
process.exit(1)
15+
})
16+
const forwarders = {}
17+
for (const signal of forwardedSignals) {
18+
forwarders[signal] = () => {
19+
try {
20+
child.kill(signal)
21+
} catch {}
22+
}
23+
process.on(signal, forwarders[signal])
24+
}
25+
child.on("exit", (code, signal) => {
26+
for (const forwardedSignal of forwardedSignals) process.removeListener(forwardedSignal, forwarders[forwardedSignal])
27+
if (signal) return process.kill(process.pid, signal)
28+
process.exit(typeof code === "number" ? code : 0)
29+
})
30+
}
31+
32+
const envPath = process.env.OPENCODE_BIN_PATH
33+
const scriptDir = path.dirname(fs.realpathSync(__filename))
34+
const cached = path.join(scriptDir, ".lildax")
35+
const platform = { darwin: "darwin", linux: "linux", win32: "windows" }[os.platform()] || os.platform()
36+
const arch = { x64: "x64", arm64: "arm64", arm: "arm" }[os.arch()] || os.arch()
37+
const base = "@opencode-ai/lildax-" + platform + "-" + arch
38+
const binary = platform === "windows" ? "lildax.exe" : "lildax"
39+
40+
function supportsAvx2() {
41+
if (arch !== "x64") return false
42+
if (platform === "linux") {
43+
try {
44+
return /(^|\s)avx2(\s|$)/i.test(fs.readFileSync("/proc/cpuinfo", "utf8"))
45+
} catch {
46+
return false
47+
}
48+
}
49+
if (platform === "darwin") {
50+
try {
51+
const result = childProcess.spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], { encoding: "utf8", timeout: 1500 })
52+
return result.status === 0 && (result.stdout || "").trim() === "1"
53+
} catch {
54+
return false
55+
}
56+
}
57+
if (platform === "windows") {
58+
const command =
59+
'(Add-Type -MemberDefinition "[DllImport(""kernel32.dll"")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)'
60+
for (const executable of ["powershell.exe", "pwsh.exe", "pwsh", "powershell"]) {
61+
try {
62+
const result = childProcess.spawnSync(executable, ["-NoProfile", "-NonInteractive", "-Command", command], {
63+
encoding: "utf8",
64+
timeout: 3000,
65+
windowsHide: true,
66+
})
67+
if (result.status !== 0) continue
68+
const output = (result.stdout || "").trim().toLowerCase()
69+
if (output === "true" || output === "1") return true
70+
if (output === "false" || output === "0") return false
71+
} catch {
72+
continue
73+
}
74+
}
75+
}
76+
return false
77+
}
78+
79+
const names = (() => {
80+
const baseline = arch === "x64" && !supportsAvx2()
81+
if (platform === "linux") {
82+
const musl = (() => {
83+
try {
84+
if (fs.existsSync("/etc/alpine-release")) return true
85+
const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" })
86+
return ((result.stdout || "") + (result.stderr || "")).toLowerCase().includes("musl")
87+
} catch {
88+
return false
89+
}
90+
})()
91+
if (musl) return arch === "x64" ? (baseline ? [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base] : [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`]) : [`${base}-musl`, base]
92+
return arch === "x64" ? (baseline ? [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`] : [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`]) : [base, `${base}-musl`]
93+
}
94+
return arch === "x64" ? (baseline ? [`${base}-baseline`, base] : [base, `${base}-baseline`]) : [base]
95+
})()
96+
97+
function findBinary(startDir) {
98+
let current = startDir
99+
for (;;) {
100+
const modules = path.join(current, "node_modules")
101+
if (fs.existsSync(modules)) for (const name of names) {
102+
const candidate = path.join(modules, name, "bin", binary)
103+
if (fs.existsSync(candidate)) return candidate
104+
}
105+
const parent = path.dirname(current)
106+
if (parent === current) return
107+
current = parent
108+
}
109+
}
110+
111+
const resolved = envPath || (fs.existsSync(cached) ? cached : findBinary(scriptDir))
112+
if (!resolved) {
113+
console.error("It seems that your package manager failed to install the right lildax CLI package. Try manually installing " + names.map((name) => `"${name}"`).join(" or ") + " package")
114+
process.exit(1)
115+
}
116+
run(resolved)

packages/cli/package.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
"$schema": "https://json.schemastore.org/package.json",
33
"name": "@opencode-ai/cli",
44
"version": "1.15.13",
5-
"private": true,
65
"type": "module",
76
"license": "MIT",
87
"bin": {
9-
"opencode": "./src/index.ts"
8+
"lildax": "./bin/lildax.cjs"
109
},
10+
"files": [
11+
"bin"
12+
],
1113
"scripts": {
1214
"build": "bun run script/build.ts",
1315
"dev": "bun run src/index.ts",
@@ -16,9 +18,13 @@
1618
"dependencies": {
1719
"@effect/platform-node": "catalog:",
1820
"@opencode-ai/core": "workspace:*",
21+
"@opencode-ai/sdk": "workspace:*",
22+
"@opencode-ai/server": "workspace:*",
23+
"@parcel/watcher": "2.5.1",
1924
"effect": "catalog:"
2025
},
2126
"devDependencies": {
27+
"@opencode-ai/script": "workspace:*",
2228
"@tsconfig/bun": "catalog:",
2329
"@types/bun": "catalog:",
2430
"@typescript/native-preview": "catalog:"

packages/cli/script/build.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#!/usr/bin/env bun
2+
3+
import { rm } from "fs/promises"
4+
import path from "path"
5+
import { Script } from "@opencode-ai/script"
6+
import { modelsData } from "./generate"
7+
8+
const dir = path.resolve(import.meta.dirname, "..")
9+
const binary = "lildax"
10+
process.chdir(dir)
11+
12+
await rm("dist", { recursive: true, force: true })
13+
14+
const singleFlag = process.argv.includes("--single")
15+
const baselineFlag = process.argv.includes("--baseline")
16+
const sourcemapsFlag = process.argv.includes("--sourcemaps")
17+
18+
const allTargets: {
19+
os: string
20+
arch: "arm64" | "x64"
21+
abi?: "musl"
22+
avx2?: false
23+
}[] = [
24+
{ os: "linux", arch: "arm64" },
25+
{ os: "linux", arch: "x64" },
26+
{ os: "linux", arch: "x64", avx2: false },
27+
{ os: "linux", arch: "arm64", abi: "musl" },
28+
{ os: "linux", arch: "x64", abi: "musl" },
29+
{ os: "linux", arch: "x64", abi: "musl", avx2: false },
30+
{ os: "darwin", arch: "arm64" },
31+
{ os: "darwin", arch: "x64" },
32+
{ os: "darwin", arch: "x64", avx2: false },
33+
{ os: "win32", arch: "arm64" },
34+
{ os: "win32", arch: "x64" },
35+
{ os: "win32", arch: "x64", avx2: false },
36+
]
37+
38+
const targets = singleFlag
39+
? allTargets.filter((item) => {
40+
if (item.os !== process.platform || item.arch !== process.arch) return false
41+
if (item.avx2 === false) return baselineFlag
42+
return item.abi === undefined
43+
})
44+
: allTargets
45+
46+
for (const item of targets) {
47+
const name = [
48+
binary,
49+
item.os === "win32" ? "windows" : item.os,
50+
item.arch,
51+
item.avx2 === false ? "baseline" : undefined,
52+
item.abi,
53+
]
54+
.filter(Boolean)
55+
.join("-")
56+
console.log(`building ${name}`)
57+
const result = await Bun.build({
58+
entrypoints: ["./src/index.ts"],
59+
tsconfig: "./tsconfig.json",
60+
external: ["node-gyp"],
61+
format: "esm",
62+
minify: true,
63+
sourcemap: sourcemapsFlag ? "linked" : "none",
64+
splitting: true,
65+
compile: {
66+
autoloadBunfig: false,
67+
autoloadDotenv: false,
68+
autoloadTsconfig: true,
69+
autoloadPackageJson: true,
70+
target: name.replace(binary, "bun") as Bun.Build.CompileTarget,
71+
outfile: `./dist/${name}/bin/${binary}`,
72+
execArgv: [`--user-agent=${binary}/${Script.version}`, "--use-system-ca", "--"],
73+
windows: {},
74+
},
75+
define: {
76+
OPENCODE_VERSION: `'${Script.version}'`,
77+
OPENCODE_CLI_NAME: `'${binary}'`,
78+
OPENCODE_MODELS_DEV: modelsData,
79+
OPENCODE_CHANNEL: `'${Script.channel}'`,
80+
OPENCODE_LIBC: item.os === "linux" ? `'${item.abi ?? "glibc"}'` : "undefined",
81+
},
82+
})
83+
84+
if (!result.success) {
85+
for (const log of result.logs) console.error(log)
86+
process.exit(1)
87+
}
88+
89+
await Bun.write(
90+
`./dist/${name}/package.json`,
91+
JSON.stringify(
92+
{
93+
name: `@opencode-ai/${name}`,
94+
version: Script.version,
95+
license: "MIT",
96+
os: [item.os],
97+
cpu: [item.arch],
98+
},
99+
null,
100+
2,
101+
),
102+
)
103+
}

packages/cli/script/generate.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const modelsUrl = process.env.OPENCODE_MODELS_URL || "https://models.dev"
2+
3+
export const modelsData = process.env.MODELS_DEV_API_JSON
4+
? await Bun.file(process.env.MODELS_DEV_API_JSON).text()
5+
: await fetch(`${modelsUrl}/api.json`).then((response) => response.text())
6+
7+
console.log("Loaded models.dev snapshot")

packages/cli/script/publish.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env bun
2+
import { $ } from "bun"
3+
import pkg from "../package.json"
4+
import { Script } from "@opencode-ai/script"
5+
import { fileURLToPath } from "url"
6+
7+
const dir = fileURLToPath(new URL("..", import.meta.url))
8+
process.chdir(dir)
9+
10+
async function published(name: string, version: string) {
11+
return (await $`npm view ${name}@${version} version`.nothrow()).exitCode === 0
12+
}
13+
14+
async function publish(dir: string, name: string, version: string) {
15+
if (process.platform !== "win32") await $`chmod -R 755 .`.cwd(dir)
16+
if (await published(name, version)) return console.log(`already published ${name}@${version}`)
17+
await $`bun pm pack`.cwd(dir)
18+
await $`npm publish *.tgz --access public --tag ${Script.channel}`.cwd(dir)
19+
}
20+
21+
const binaries: Record<string, string> = {}
22+
for (const filepath of new Bun.Glob("*/package.json").scanSync({ cwd: "./dist" })) {
23+
const item = await Bun.file(`./dist/${filepath}`).json()
24+
binaries[item.name] = item.version
25+
}
26+
console.log("binaries", binaries)
27+
const version = Object.values(binaries)[0]
28+
29+
await $`mkdir -p ./dist/${pkg.name}/bin`
30+
await $`cp ./bin/lildax.cjs ./dist/${pkg.name}/bin/lildax`
31+
await Bun.file(`./dist/${pkg.name}/package.json`).write(
32+
JSON.stringify(
33+
{
34+
name: pkg.name,
35+
bin: { lildax: "./bin/lildax" },
36+
version,
37+
license: pkg.license,
38+
os: ["darwin", "linux", "win32"],
39+
cpu: ["arm64", "x64"],
40+
optionalDependencies: binaries,
41+
},
42+
null,
43+
2,
44+
),
45+
)
46+
47+
await Promise.all(Object.entries(binaries).map(([name, version]) => publish(`./dist/${name.replace("@opencode-ai/", "")}`, name, version)))
48+
await publish(`./dist/${pkg.name}`, pkg.name, version)

0 commit comments

Comments
 (0)