为打造自有纯 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>
104 lines
2.8 KiB
TypeScript
104 lines
2.8 KiB
TypeScript
import { cmd } from "../cmd"
|
|
import { UI } from "@/cli/ui"
|
|
import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"
|
|
import { errorMessage } from "@/util/error"
|
|
import { validateSession } from "./validate-session"
|
|
import { ServerAuth } from "@/server/auth"
|
|
|
|
export const AttachCommand = cmd({
|
|
command: "attach <url>",
|
|
describe: "attach to a running zmo server",
|
|
builder: (yargs) =>
|
|
yargs
|
|
.positional("url", {
|
|
type: "string",
|
|
describe: "http://localhost:4096",
|
|
demandOption: true,
|
|
})
|
|
.option("dir", {
|
|
type: "string",
|
|
description: "directory to run in",
|
|
})
|
|
.option("continue", {
|
|
alias: ["c"],
|
|
describe: "continue the last session",
|
|
type: "boolean",
|
|
})
|
|
.option("session", {
|
|
alias: ["s"],
|
|
type: "string",
|
|
describe: "session id to continue",
|
|
})
|
|
.option("fork", {
|
|
type: "boolean",
|
|
describe: "fork the session when continuing (use with --continue or --session)",
|
|
})
|
|
.option("password", {
|
|
alias: ["p"],
|
|
type: "string",
|
|
describe: "basic auth password (defaults to OPENCODE_SERVER_PASSWORD)",
|
|
})
|
|
.option("username", {
|
|
alias: ["u"],
|
|
type: "string",
|
|
describe: "basic auth username (defaults to OPENCODE_SERVER_USERNAME or 'opencode')",
|
|
}),
|
|
handler: async (args) => {
|
|
const { TuiConfig } = await import("@/cli/cmd/tui/config/tui")
|
|
const unguard = win32InstallCtrlCGuard()
|
|
try {
|
|
win32DisableProcessedInput()
|
|
|
|
if (args.fork && !args.continue && !args.session) {
|
|
UI.error("--fork requires --continue or --session")
|
|
process.exitCode = 1
|
|
return
|
|
}
|
|
|
|
const directory = (() => {
|
|
if (!args.dir) return undefined
|
|
try {
|
|
process.chdir(args.dir)
|
|
return process.cwd()
|
|
} catch {
|
|
// If the directory doesn't exist locally (remote attach), pass it through.
|
|
return args.dir
|
|
}
|
|
})()
|
|
const headers = ServerAuth.headers({ password: args.password, username: args.username })
|
|
const config = await TuiConfig.get()
|
|
|
|
try {
|
|
await validateSession({
|
|
url: args.url,
|
|
sessionID: args.session,
|
|
directory,
|
|
headers,
|
|
})
|
|
} catch (error) {
|
|
UI.error(errorMessage(error))
|
|
process.exitCode = 1
|
|
return
|
|
}
|
|
|
|
const { createTuiRenderer, tui } = await import("./app")
|
|
const renderer = await createTuiRenderer(config)
|
|
const handle = tui({
|
|
url: args.url,
|
|
config,
|
|
renderer,
|
|
args: {
|
|
continue: args.continue,
|
|
sessionID: args.session,
|
|
fork: args.fork,
|
|
},
|
|
directory,
|
|
headers,
|
|
})
|
|
await handle.done
|
|
} finally {
|
|
unguard?.()
|
|
}
|
|
},
|
|
})
|