rebrand: 将 opencode 改造为 zmo CLI 并隐藏非 CLI 命令

为打造自有纯 CLI 产品 zmo(类 Claude Code),在保留上游同步能力的前提下做套壳改造:
- 入口隐藏 9 个非 CLI 命令(console/web/github/pr/stats/export/import/db/debug)
- 全面 rebrand:scriptName、Logo、配置目录(~/.config/zmo)、bin 名、构建产物、可见文案
- env 双读兼容(ZMO_* 优先,回退 OPENCODE_*),配置文件名新增 zmo.json
- 刻意保留 @opencode-ai/* 内部包名与 opencode provider,避免破坏功能与上游同步

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 04:33:26 +08:00
parent cc9b73b0bd
commit b345dfcc50
16 changed files with 68 additions and 64 deletions

View File

@@ -17,7 +17,7 @@
"dev:temporary": "bun run --conditions=browser ./src/temporary.ts"
},
"bin": {
"opencode": "./bin/opencode"
"zmo": "./bin/opencode"
},
"exports": {
"./*": "./src/*.ts"

View File

@@ -179,8 +179,8 @@ for (const item of targets) {
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", "--"],
outfile: `dist/${name}/bin/zmo`,
execArgv: [`--user-agent=zmo/${Script.version}`, "--use-system-ca", "--"],
windows: {},
},
files: embeddedFileMap ? { "opencode-web-ui.gen.ts": embeddedFileMap } : {},
@@ -197,7 +197,7 @@ for (const item of targets) {
// 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`
const binaryPath = `dist/${name}/bin/zmo`
console.log(`Running smoke test: ${binaryPath} --version`)
try {
const versionOutput = await $`${binaryPath} --version`.text()

View File

@@ -121,7 +121,7 @@ async function toolError(part: ToolPart) {
export const RunCommand = effectCmd({
command: "run [message..]",
describe: "run opencode with a message",
describe: "run zmo with a message",
// --attach connects to a remote server (no local instance needed); the
// default path runs an in-process server and needs the project instance.
instance: (args) => !args.attach,
@@ -185,7 +185,7 @@ export const RunCommand = effectCmd({
})
.option("attach", {
type: "string",
describe: "attach to a running opencode server (e.g., http://localhost:4096)",
describe: "attach to a running zmo server (e.g., http://localhost:4096)",
})
.option("password", {
alias: ["p"],

View File

@@ -6,7 +6,7 @@ import { Flag } from "@opencode-ai/core/flag/flag"
export const ServeCommand = effectCmd({
command: "serve",
builder: (yargs) => withNetworkOptions(yargs),
describe: "starts a headless opencode server",
describe: "starts a headless zmo server",
// Server loads instances per-request via x-opencode-directory header — no
// need for an ambient project InstanceContext at startup.
instance: false,
@@ -17,7 +17,7 @@ export const ServeCommand = effectCmd({
}
const opts = yield* resolveNetworkOptions(args)
const server = yield* Effect.promise(() => Server.listen(opts))
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
console.log(`zmo server listening on http://${server.hostname}:${server.port}`)
yield* Effect.never
}),

View File

@@ -7,7 +7,7 @@ import { ServerAuth } from "@/server/auth"
export const AttachCommand = cmd({
command: "attach <url>",
describe: "attach to a running opencode server",
describe: "attach to a running zmo server",
builder: (yargs) =>
yargs
.positional("url", {

View File

@@ -77,12 +77,12 @@ export function resolveThreadDirectory(project?: string, envPWD = process.env.PW
export const TuiThreadCommand = cmd({
command: "$0 [project]",
describe: "start opencode tui",
describe: "start zmo tui",
builder: (yargs) =>
withNetworkOptions(yargs)
.positional("project", {
type: "string",
describe: "path to start opencode in",
describe: "path to start zmo in",
})
.option("model", {
type: "string",

View File

@@ -24,7 +24,7 @@ interface RemovalTargets {
export const UninstallCommand = {
command: "uninstall",
describe: "uninstall opencode and remove all related files",
describe: "uninstall zmo and remove all related files",
builder: (yargs: Argv) =>
yargs
.option("keep-config", {

View File

@@ -6,7 +6,7 @@ import { InstallationVersion } from "@opencode-ai/core/installation/version"
export const UpgradeCommand = {
command: "upgrade [target]",
describe: "upgrade opencode to the latest or a specific version",
describe: "upgrade zmo to the latest or a specific version",
builder: (yargs: Argv) => {
return yargs
.positional("target", {

View File

@@ -47,7 +47,7 @@ export function FormatError(input: unknown): string | undefined {
// MCPFailed: { name: string }
if (NamedError.hasName(input, "MCPFailed")) {
const data = isRecord(input) && isRecord(input.data) ? stringField(input.data, "name") : undefined
return `MCP server "${data}" failed. Note, opencode does not support MCP authentication yet.`
return `MCP server "${data}" failed. Note, zmo does not support MCP authentication yet.`
}
// AccountServiceError, AccountTransportError: TaggedErrorClass
@@ -64,8 +64,8 @@ export function FormatError(input: unknown): string | undefined {
return [
`Model not found: ${stringField(providerModelNotFound, "providerID")}/${stringField(providerModelNotFound, "modelID")}`,
...(suggestions.length ? ["Did you mean: " + suggestions.join(", ")] : []),
`Try: \`opencode models\` to list available models`,
`Or check your config (opencode.json) provider/model names`,
`Try: \`zmo models\` to list available models`,
`Or check your config (zmo.json) provider/model names`,
].join("\n")
}

View File

@@ -1,6 +1,6 @@
export const logo = {
left: [" ", "█▀▀█ █▀▀█ █▀▀█ █▀▀▄", "█__█ █__█ █^^^ █__█", "▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀~~▀"],
right: ["", "█▀▀▀ █▀▀█ █▀▀█ █▀▀█", "█___ █__█ █__█ █^^^", "▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀"],
left: ["▀▀▀▀", " ▄▀", "▄▀ ", "▄▄▄▄"],
right: ["█▄ ▄█ ▄▀▀▄", "█ ▀ █ █ █", "█ █ █ █", "█ █ ▀▄▄▀"],
}
export const go = {

View File

@@ -3,10 +3,10 @@ import { Schema } from "effect"
import { logo as glyphs } from "./logo"
const wordmark = [
``,
`█▀▀█ █▀▀█ █▀▀█ █▀▀▄ █▀▀▀ █▀▀█ █▀▀█ █▀▀`,
`█ █ █ █ █▀▀▀ █ █ █ █ █ █ █ █▀▀▀`,
`▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀`,
`▀▀▀▀ █▄ ▄█ ▄▀▀▄`,
` ▄▀ █ ▀ █ █ `,
`▄▀ █ █ █ █`,
`▄▄▄▄ █ █ ▀▄▄`,
]
export class CancelledError extends Schema.TaggedErrorClass<CancelledError>()("UICancelledError", {}) {}

View File

@@ -3,7 +3,7 @@ import { hideBin } from "yargs/helpers"
import { RunCommand } from "./cli/cmd/run"
import { GenerateCommand } from "./cli/cmd/generate"
import * as Log from "@opencode-ai/core/util/log"
import { ConsoleCommand } from "./cli/cmd/account"
// import { ConsoleCommand } from "./cli/cmd/account" // zmo: 隐藏云账户命令
import { ProvidersCommand } from "./cli/cmd/providers"
import { AgentCommand } from "./cli/cmd/agent"
import { UpgradeCommand } from "./cli/cmd/upgrade"
@@ -15,20 +15,20 @@ import { InstallationVersion } from "@opencode-ai/core/installation/version"
import { NamedError } from "@opencode-ai/core/util/error"
import { FormatError } from "./cli/error"
import { ServeCommand } from "./cli/cmd/serve"
import { DebugCommand } from "./cli/cmd/debug"
import { StatsCommand } from "./cli/cmd/stats"
// import { DebugCommand } from "./cli/cmd/debug" // zmo: 隐藏 debug 命令
// import { StatsCommand } from "./cli/cmd/stats" // zmo: 隐藏统计命令
import { McpCommand } from "./cli/cmd/mcp"
import { GithubCommand } from "./cli/cmd/github"
import { ExportCommand } from "./cli/cmd/export"
import { ImportCommand } from "./cli/cmd/import"
// import { GithubCommand } from "./cli/cmd/github" // zmo: 隐藏 github 命令
// import { ExportCommand } from "./cli/cmd/export" // zmo: 隐藏导出命令
// import { ImportCommand } from "./cli/cmd/import" // zmo: 隐藏导入命令
import { AttachCommand } from "./cli/cmd/tui/attach"
import { TuiThreadCommand } from "./cli/cmd/tui/thread"
import { AcpCommand } from "./cli/cmd/acp"
import { EOL } from "os"
import { WebCommand } from "./cli/cmd/web"
import { PrCommand } from "./cli/cmd/pr"
// import { WebCommand } from "./cli/cmd/web" // zmo: 隐藏 web 命令
// import { PrCommand } from "./cli/cmd/pr" // zmo: 隐藏 pr 命令
import { SessionCommand } from "./cli/cmd/session"
import { DbCommand } from "./cli/cmd/db"
// import { DbCommand } from "./cli/cmd/db" // zmo: 隐藏 db 命令
import { errorMessage } from "./util/error"
import { PluginCommand } from "./cli/cmd/plug"
import { Heap } from "./cli/heap"
@@ -53,7 +53,7 @@ const args = hideBin(process.argv)
function show(out: string) {
const text = out.trimStart()
if (!text.startsWith("opencode ")) {
if (!text.startsWith("zmo ")) {
process.stderr.write(UI.logo() + EOL + EOL)
process.stderr.write(text)
return
@@ -63,7 +63,7 @@ function show(out: string) {
const cli = yargs(args)
.parserConfiguration({ "populate--": true })
.scriptName("opencode")
.scriptName("zmo")
.wrap(100)
.help("help", "show help")
.alias("help", "h")
@@ -102,6 +102,8 @@ const cli = yargs(args)
process.env.AGENT = "1"
process.env.OPENCODE = "1"
process.env.OPENCODE_PID = String(process.pid)
process.env.ZMO = "1"
process.env.ZMO_PID = String(process.pid)
Log.Default.info("opencode", {
version: InstallationVersion,
@@ -118,23 +120,14 @@ const cli = yargs(args)
.command(AttachCommand)
.command(RunCommand)
.command(GenerateCommand)
.command(DebugCommand)
.command(ConsoleCommand)
.command(ProvidersCommand)
.command(AgentCommand)
.command(UpgradeCommand)
.command(UninstallCommand)
.command(ServeCommand)
.command(WebCommand)
.command(ModelsCommand)
.command(StatsCommand)
.command(ExportCommand)
.command(ImportCommand)
.command(GithubCommand)
.command(PrCommand)
.command(SessionCommand)
.command(PluginCommand)
.command(DbCommand)
.fail((msg, err) => {
if (
msg?.startsWith("Unknown argument") ||