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

@@ -129,7 +129,7 @@ export const layer = Layer.effect(
const global = yield* Global.Service
const location = yield* Location.Service
const policy = yield* Policy.Service
const names = ["config.json", "opencode.json", "opencode.jsonc"]
const names = ["config.json", "zmo.json", "zmo.jsonc", "opencode.json", "opencode.jsonc"]
const loadFile = Effect.fnUntraced(function* (filepath: string) {
const text = yield* fs.readFileStringSafe(filepath)

View File

@@ -1,14 +1,25 @@
import { Config } from "effect"
// zmo: 双读兼容 —— 优先读 ZMO_* 前缀,回退到原 OPENCODE_* 前缀。
// 这样旧的 OPENCODE_* 环境变量仍然有效,同时支持新的 ZMO_* 前缀。
function envOf(key: string): string | undefined {
if (key.startsWith("OPENCODE_")) {
const zmoKey = "ZMO_" + key.slice("OPENCODE_".length)
const zmoValue = process.env[zmoKey]
if (zmoValue !== undefined) return zmoValue
}
return process.env[key]
}
export function truthy(key: string) {
const value = process.env[key]?.toLowerCase()
const value = envOf(key)?.toLowerCase()
return value === "true" || value === "1"
}
const copy = process.env["OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT"]
const copy = envOf("OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT")
function enabledByExperimental(key: string) {
return process.env[key] === undefined ? truthy("OPENCODE_EXPERIMENTAL") : truthy(key)
return envOf(key) === undefined ? truthy("OPENCODE_EXPERIMENTAL") : truthy(key)
}
export const Flag = {
@@ -16,9 +27,9 @@ export const Flag = {
OTEL_EXPORTER_OTLP_HEADERS: process.env["OTEL_EXPORTER_OTLP_HEADERS"],
OPENCODE_AUTO_HEAP_SNAPSHOT: truthy("OPENCODE_AUTO_HEAP_SNAPSHOT"),
OPENCODE_GIT_BASH_PATH: process.env["OPENCODE_GIT_BASH_PATH"],
OPENCODE_CONFIG: process.env["OPENCODE_CONFIG"],
OPENCODE_CONFIG_CONTENT: process.env["OPENCODE_CONFIG_CONTENT"],
OPENCODE_GIT_BASH_PATH: envOf("OPENCODE_GIT_BASH_PATH"),
OPENCODE_CONFIG: envOf("OPENCODE_CONFIG"),
OPENCODE_CONFIG_CONTENT: envOf("OPENCODE_CONFIG_CONTENT"),
OPENCODE_DISABLE_AUTOUPDATE: truthy("OPENCODE_DISABLE_AUTOUPDATE"),
OPENCODE_ALWAYS_NOTIFY_UPDATE: truthy("OPENCODE_ALWAYS_NOTIFY_UPDATE"),
OPENCODE_DISABLE_PRUNE: truthy("OPENCODE_DISABLE_PRUNE"),
@@ -27,9 +38,9 @@ export const Flag = {
OPENCODE_DISABLE_AUTOCOMPACT: truthy("OPENCODE_DISABLE_AUTOCOMPACT"),
OPENCODE_DISABLE_MODELS_FETCH: truthy("OPENCODE_DISABLE_MODELS_FETCH"),
OPENCODE_DISABLE_MOUSE: truthy("OPENCODE_DISABLE_MOUSE"),
OPENCODE_FAKE_VCS: process.env["OPENCODE_FAKE_VCS"],
OPENCODE_SERVER_PASSWORD: process.env["OPENCODE_SERVER_PASSWORD"],
OPENCODE_SERVER_USERNAME: process.env["OPENCODE_SERVER_USERNAME"],
OPENCODE_FAKE_VCS: envOf("OPENCODE_FAKE_VCS"),
OPENCODE_SERVER_PASSWORD: envOf("OPENCODE_SERVER_PASSWORD"),
OPENCODE_SERVER_USERNAME: envOf("OPENCODE_SERVER_USERNAME"),
// Experimental
OPENCODE_EXPERIMENTAL_FILEWATCHER: Config.boolean("OPENCODE_EXPERIMENTAL_FILEWATCHER").pipe(
@@ -40,11 +51,11 @@ export const Flag = {
),
OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT:
copy === undefined ? process.platform === "win32" : truthy("OPENCODE_EXPERIMENTAL_DISABLE_COPY_ON_SELECT"),
OPENCODE_MODELS_URL: process.env["OPENCODE_MODELS_URL"],
OPENCODE_MODELS_PATH: process.env["OPENCODE_MODELS_PATH"],
OPENCODE_DB: process.env["OPENCODE_DB"],
OPENCODE_MODELS_URL: envOf("OPENCODE_MODELS_URL"),
OPENCODE_MODELS_PATH: envOf("OPENCODE_MODELS_PATH"),
OPENCODE_DB: envOf("OPENCODE_DB"),
OPENCODE_WORKSPACE_ID: process.env["OPENCODE_WORKSPACE_ID"],
OPENCODE_WORKSPACE_ID: envOf("OPENCODE_WORKSPACE_ID"),
OPENCODE_EXPERIMENTAL_WORKSPACES: enabledByExperimental("OPENCODE_EXPERIMENTAL_WORKSPACES"),
OPENCODE_EXPERIMENTAL_SESSION_SWITCHER: enabledByExperimental("OPENCODE_EXPERIMENTAL_SESSION_SWITCHER"),
@@ -57,21 +68,21 @@ export const Flag = {
return enabledByExperimental("OPENCODE_EXPERIMENTAL_REFERENCES")
},
get OPENCODE_TUI_CONFIG() {
return process.env["OPENCODE_TUI_CONFIG"]
return envOf("OPENCODE_TUI_CONFIG")
},
get OPENCODE_CONFIG_DIR() {
return process.env["OPENCODE_CONFIG_DIR"]
return envOf("OPENCODE_CONFIG_DIR")
},
get OPENCODE_PURE() {
return truthy("OPENCODE_PURE")
},
get OPENCODE_PERMISSION() {
return process.env["OPENCODE_PERMISSION"]
return envOf("OPENCODE_PERMISSION")
},
get OPENCODE_PLUGIN_META_FILE() {
return process.env["OPENCODE_PLUGIN_META_FILE"]
return envOf("OPENCODE_PLUGIN_META_FILE")
},
get OPENCODE_CLIENT() {
return process.env["OPENCODE_CLIENT"] ?? "cli"
return envOf("OPENCODE_CLIENT") ?? "cli"
},
}

View File

@@ -6,7 +6,7 @@ import { Context, Effect, Layer } from "effect"
import { Flock } from "./util/flock"
import { Flag } from "./flag/flag"
const app = "opencode"
const app = "zmo"
const data = path.join(xdgData!, app)
const cache = path.join(xdgCache!, app)
const config = path.join(xdgConfig!, app)
@@ -15,7 +15,7 @@ const tmp = path.join(os.tmpdir(), app)
const paths = {
get home() {
return process.env.OPENCODE_TEST_HOME ?? os.homedir()
return process.env.ZMO_TEST_HOME ?? process.env.OPENCODE_TEST_HOME ?? os.homedir()
},
data,
bin: path.join(cache, "bin"),