Compare commits
9
Commits
0f474f9f27
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccd881c689 | ||
|
|
698ce2fa31 | ||
|
|
6953fce7c6 | ||
|
|
52eb90e7c3 | ||
|
|
324549475b | ||
|
|
c034a178b9 | ||
|
|
5bd92a3535 | ||
|
|
9970e6c1ad | ||
|
|
daa3ecb842 |
+58
-80
@@ -1,105 +1,57 @@
|
||||
<p align="center">
|
||||
<a href="https://opencode.ai">
|
||||
<picture>
|
||||
<source srcset="packages/console/app/src/asset/logo-ornate-dark.svg" media="(prefers-color-scheme: dark)">
|
||||
<source srcset="packages/console/app/src/asset/logo-ornate-light.svg" media="(prefers-color-scheme: light)">
|
||||
<img src="packages/console/app/src/asset/logo-ornate-light.svg" alt="OpenCode logo">
|
||||
</picture>
|
||||
<a href="https://git.chatbot.js.cn/lmin/zmo-cli">
|
||||
<img src="https://img.shields.io/badge/zmo-CLI-blue?style=for-the-badge" alt="zmo logo">
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">开源的 AI Coding Agent。</p>
|
||||
<p align="center">开源的 AI Coding Agent,运行在你的终端里。</p>
|
||||
<p align="center">
|
||||
<a href="https://opencode.ai/discord"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
|
||||
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
|
||||
<a href="https://github.com/anomalyco/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/anomalyco/opencode/publish.yml?style=flat-square&branch=dev" /></a>
|
||||
<a href="https://git.chatbot.js.cn/lmin/zmo-cli"><img alt="repo" src="https://img.shields.io/badge/repo-zmo--cli-green?style=flat-square" /></a>
|
||||
<a href="LICENSE"><img alt="license" src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" /></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="README.md">English</a> |
|
||||
<a href="README.zh.md">简体中文</a> |
|
||||
<a href="README.zht.md">繁體中文</a> |
|
||||
<a href="README.ko.md">한국어</a> |
|
||||
<a href="README.de.md">Deutsch</a> |
|
||||
<a href="README.es.md">Español</a> |
|
||||
<a href="README.fr.md">Français</a> |
|
||||
<a href="README.it.md">Italiano</a> |
|
||||
<a href="README.da.md">Dansk</a> |
|
||||
<a href="README.ja.md">日本語</a> |
|
||||
<a href="README.pl.md">Polski</a> |
|
||||
<a href="README.ru.md">Русский</a> |
|
||||
<a href="README.bs.md">Bosanski</a> |
|
||||
<a href="README.ar.md">العربية</a> |
|
||||
<a href="README.no.md">Norsk</a> |
|
||||
<a href="README.br.md">Português (Brasil)</a> |
|
||||
<a href="README.th.md">ไทย</a> |
|
||||
<a href="README.tr.md">Türkçe</a> |
|
||||
<a href="README.uk.md">Українська</a> |
|
||||
<a href="README.bn.md">বাংলা</a> |
|
||||
<a href="README.gr.md">Ελληνικά</a> |
|
||||
<a href="README.vi.md">Tiếng Việt</a>
|
||||
<a href="README.zh.md">简体中文</a>
|
||||
</p>
|
||||
|
||||
[](https://opencode.ai)
|
||||
---
|
||||
|
||||
> **zmo** 是一个基于开源项目 [OpenCode](https://github.com/anomalyco/opencode)(MIT 许可)二次开发的、聚焦终端的 AI 编码助手 CLI。它保留了 OpenCode 的核心 Agent 能力(自主任务、工具调用、LSP、MCP、权限系统、多模型适配),并精简为纯命令行体验。
|
||||
|
||||
---
|
||||
|
||||
### 安装
|
||||
|
||||
> [!NOTE]
|
||||
> 以下软件包管理器渠道为**正式发布后**的目标安装方式。在正式发布前,请使用文末「从源码构建」一节的方法。
|
||||
|
||||
```bash
|
||||
# 直接安装 (YOLO)
|
||||
curl -fsSL https://opencode.ai/install | bash
|
||||
curl -fsSL https://git.chatbot.js.cn/lmin/zmo-cli/install | bash
|
||||
|
||||
# 软件包管理器
|
||||
npm i -g opencode-ai@latest # 也可使用 bun/pnpm/yarn
|
||||
scoop install opencode # Windows
|
||||
choco install opencode # Windows
|
||||
brew install anomalyco/tap/opencode # macOS 和 Linux(推荐,始终保持最新)
|
||||
brew install opencode # macOS 和 Linux(官方 brew formula,更新频率较低)
|
||||
sudo pacman -S opencode # Arch Linux (Stable)
|
||||
paru -S opencode-bin # Arch Linux (Latest from AUR)
|
||||
mise use -g opencode # 任意系统
|
||||
nix run nixpkgs#opencode # 或用 github:anomalyco/opencode 获取最新 dev 分支
|
||||
npm i -g zmo@latest # 也可使用 bun/pnpm/yarn
|
||||
scoop install zmo # Windows
|
||||
choco install zmo # Windows
|
||||
brew install zmo # macOS 和 Linux
|
||||
mise use -g zmo # 任意系统
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> 安装前请先移除 0.1.x 之前的旧版本。
|
||||
|
||||
### 桌面应用程序 (BETA)
|
||||
|
||||
OpenCode 也提供桌面版应用。可直接从 [发布页 (releases page)](https://github.com/anomalyco/opencode/releases) 或 [opencode.ai/download](https://opencode.ai/download) 下载。
|
||||
|
||||
| 平台 | 下载文件 |
|
||||
| --------------------- | ---------------------------------- |
|
||||
| macOS (Apple Silicon) | `opencode-desktop-mac-arm64.dmg` |
|
||||
| macOS (Intel) | `opencode-desktop-mac-x64.dmg` |
|
||||
| Windows | `opencode-desktop-windows-x64.exe` |
|
||||
| Linux | `.deb`、`.rpm` 或 AppImage |
|
||||
|
||||
```bash
|
||||
# macOS (Homebrew Cask)
|
||||
brew install --cask opencode-desktop
|
||||
# Windows (Scoop)
|
||||
scoop bucket add extras; scoop install extras/opencode-desktop
|
||||
```
|
||||
> 安装后,在任意项目目录运行 `zmo` 即可启动交互式 TUI,或运行 `zmo run "你的需求"` 直接执行单次任务。
|
||||
|
||||
#### 安装目录
|
||||
|
||||
安装脚本按照以下优先级决定安装路径:
|
||||
|
||||
1. `$OPENCODE_INSTALL_DIR` - 自定义安装目录
|
||||
1. `$ZMO_INSTALL_DIR` - 自定义安装目录
|
||||
2. `$XDG_BIN_DIR` - 符合 XDG 基础目录规范的路径
|
||||
3. `$HOME/bin` - 如果存在或可创建的用户二进制目录
|
||||
4. `$HOME/.opencode/bin` - 默认备用路径
|
||||
|
||||
```bash
|
||||
# 示例
|
||||
OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
|
||||
XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
|
||||
```
|
||||
4. `$HOME/.zmo/bin` - 默认备用路径
|
||||
|
||||
### Agents
|
||||
|
||||
OpenCode 内置两种 Agent,可用 `Tab` 键快速切换:
|
||||
zmo 内置两种 Agent,可用 `Tab` 键快速切换:
|
||||
|
||||
- **build** - 默认模式,具备完整权限,适合开发工作
|
||||
- **plan** - 只读模式,适合代码分析与探索
|
||||
@@ -109,20 +61,46 @@ OpenCode 内置两种 Agent,可用 `Tab` 键快速切换:
|
||||
|
||||
另外还包含一个 **general** 子 Agent,用于复杂搜索和多步任务,内部使用,也可在消息中输入 `@general` 调用。
|
||||
|
||||
了解更多 [Agents](https://opencode.ai/docs/agents) 相关信息。
|
||||
### 配置
|
||||
|
||||
### 文档
|
||||
zmo 在项目根目录或 `~/.config/zmo/` 下读取配置文件,支持以下文件名(按优先级):
|
||||
|
||||
更多配置说明请查看我们的 [**官方文档**](https://opencode.ai/docs)。
|
||||
- `config.json`
|
||||
- `zmo.json` / `zmo.jsonc`
|
||||
- `opencode.json` / `opencode.jsonc`(向下兼容)
|
||||
|
||||
环境变量同时支持 `ZMO_*`(优先)与 `OPENCODE_*`(向下兼容)两种前缀。
|
||||
|
||||
### 从源码构建(当前推荐)
|
||||
|
||||
在正式发布渠道上线前,可通过源码自行构建单文件可执行程序:
|
||||
|
||||
```bash
|
||||
# 1. 克隆仓库
|
||||
git clone https://git.chatbot.js.cn/lmin/zmo-cli.git
|
||||
cd zmo-cli
|
||||
|
||||
# 2. 安装依赖(需 Bun 1.3+)
|
||||
bun install
|
||||
|
||||
# 3. 构建当前平台的单二进制
|
||||
cd packages/opencode
|
||||
bun run build --single --skip-embed-web-ui
|
||||
|
||||
# 4. 产物位于 packages/opencode/dist/<平台>/bin/zmo
|
||||
# 将其加入 PATH 即可全局使用 zmo 命令
|
||||
```
|
||||
|
||||
也可在不构建的情况下直接运行(开发模式):
|
||||
|
||||
```bash
|
||||
bun run dev # 在仓库根目录
|
||||
```
|
||||
|
||||
### 参与贡献
|
||||
|
||||
如有兴趣贡献代码,请在提交 PR 前阅读 [贡献指南 (Contributing Docs)](./CONTRIBUTING.md)。
|
||||
如有兴趣贡献代码,请在提交 PR 前阅读 [贡献指南](./CONTRIBUTING.md)。
|
||||
|
||||
### 基于 OpenCode 进行开发
|
||||
### 致谢
|
||||
|
||||
如果你在项目名中使用了 “opencode”(如 “opencode-dashboard” 或 “opencode-mobile”),请在 README 里注明该项目不是 OpenCode 团队官方开发,且不存在隶属关系。
|
||||
|
||||
---
|
||||
|
||||
**加入我们的社区** [飞书](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=738j8655-cd59-4633-a30a-1124e0096789&qr_code=true) | [X.com](https://x.com/opencode)
|
||||
zmo 基于 [OpenCode](https://github.com/anomalyco/opencode)(MIT License)二次开发,感谢原项目作者与社区。本项目与 OpenCode 官方团队无隶属关系。
|
||||
|
||||
@@ -10,7 +10,7 @@ export const CerebrasPlugin = PluginV2.define({
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/cerebras") continue
|
||||
ctx.provider.update(item.provider.id, (provider) => {
|
||||
provider.request.headers["X-Cerebras-3rd-Party-Integration"] = "opencode"
|
||||
provider.request.headers["X-Cerebras-3rd-Party-Integration"] = "zmo"
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -71,7 +71,7 @@ function gatewayOptions(options: Record<string, unknown>, metadata: unknown) {
|
||||
skipCache: options.skipCache,
|
||||
collectLog: options.collectLog,
|
||||
headers: {
|
||||
"User-Agent": `opencode/${InstallationVersion} cloudflare-ai-gateway (${os.platform()} ${os.release()}; ${os.arch()})`,
|
||||
"User-Agent": `zmo/${InstallationVersion} cloudflare-ai-gateway (${os.platform()} ${os.release()}; ${os.arch()})`,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ function sdkOptions(options: Record<string, any>) {
|
||||
baseURL: expandAccountId(options.baseURL),
|
||||
apiKey: process.env.CLOUDFLARE_API_KEY ?? options.apiKey,
|
||||
headers: {
|
||||
"User-Agent": `opencode/${InstallationVersion} cloudflare-workers-ai (${os.platform()} ${os.release()}; ${os.arch()})`,
|
||||
"User-Agent": `zmo/${InstallationVersion} cloudflare-workers-ai (${os.platform()} ${os.release()}; ${os.arch()})`,
|
||||
...options.headers,
|
||||
},
|
||||
name: providerID,
|
||||
|
||||
@@ -19,7 +19,7 @@ export const GitLabPlugin = PluginV2.define({
|
||||
: (process.env.GITLAB_INSTANCE_URL ?? "https://gitlab.com"),
|
||||
apiKey: typeof evt.options.apiKey === "string" ? evt.options.apiKey : process.env.GITLAB_TOKEN,
|
||||
aiGatewayHeaders: {
|
||||
"User-Agent": `opencode/${InstallationVersion} gitlab-ai-provider/${mod.VERSION} (${os.platform()} ${os.release()}; ${os.arch()})`,
|
||||
"User-Agent": `zmo/${InstallationVersion} gitlab-ai-provider/${mod.VERSION} (${os.platform()} ${os.release()}; ${os.arch()})`,
|
||||
"anthropic-beta": "context-1m-2025-08-07",
|
||||
...evt.options.aiGatewayHeaders,
|
||||
},
|
||||
|
||||
@@ -11,8 +11,8 @@ export const KiloPlugin = PluginV2.define({
|
||||
if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.api.url !== "https://api.kilo.ai/api/gateway") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||
provider.request.headers["X-Title"] = "opencode"
|
||||
provider.request.headers["HTTP-Referer"] = "https://github.com/helpLogin/zmo-cli"
|
||||
provider.request.headers["X-Title"] = "zmo"
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -12,9 +12,9 @@ export const LLMGatewayPlugin = PluginV2.define({
|
||||
if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.api.url !== "https://api.llmgateway.io/v1") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||
provider.request.headers["X-Title"] = "opencode"
|
||||
provider.request.headers["X-Source"] = "opencode"
|
||||
provider.request.headers["HTTP-Referer"] = "https://github.com/helpLogin/zmo-cli"
|
||||
provider.request.headers["X-Title"] = "zmo"
|
||||
provider.request.headers["X-Source"] = "zmo"
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -11,9 +11,9 @@ export const NvidiaPlugin = PluginV2.define({
|
||||
if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.api.url !== "https://integrate.api.nvidia.com/v1") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||
provider.request.headers["X-Title"] = "opencode"
|
||||
provider.request.headers["X-BILLING-INVOKE-ORIGIN"] ??= "OpenCode"
|
||||
provider.request.headers["HTTP-Referer"] = "https://github.com/helpLogin/zmo-cli"
|
||||
provider.request.headers["X-Title"] = "zmo"
|
||||
provider.request.headers["X-BILLING-INVOKE-ORIGIN"] ??= "zmo"
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -11,8 +11,8 @@ export const OpenRouterPlugin = PluginV2.define({
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@openrouter/ai-sdk-provider") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
|
||||
provider.request.headers["X-Title"] = "opencode"
|
||||
provider.request.headers["HTTP-Referer"] = "https://github.com/helpLogin/zmo-cli"
|
||||
provider.request.headers["X-Title"] = "zmo"
|
||||
})
|
||||
for (const modelID of [ModelV2.ID.make("gpt-5-chat-latest"), ModelV2.ID.make("openai/gpt-5-chat")]) {
|
||||
if (!item.models.has(modelID)) continue
|
||||
|
||||
@@ -10,8 +10,8 @@ export const VercelPlugin = PluginV2.define({
|
||||
if (item.provider.api.type !== "aisdk") continue
|
||||
if (item.provider.api.package !== "@ai-sdk/vercel") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.request.headers["http-referer"] = "https://opencode.ai/"
|
||||
provider.request.headers["x-title"] = "opencode"
|
||||
provider.request.headers["http-referer"] = "https://github.com/helpLogin/zmo-cli"
|
||||
provider.request.headers["x-title"] = "zmo"
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -11,8 +11,8 @@ export const ZenmuxPlugin = PluginV2.define({
|
||||
if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.api.url !== "https://zenmux.ai/api/v1") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.request.headers["HTTP-Referer"] ??= "https://opencode.ai/"
|
||||
provider.request.headers["X-Title"] ??= "opencode"
|
||||
provider.request.headers["HTTP-Referer"] ??= "https://github.com/helpLogin/zmo-cli"
|
||||
provider.request.headers["X-Title"] ??= "zmo"
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -87,8 +87,8 @@
|
||||
"@opencode-ai/llm": "workspace:*",
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
"@opencode-ai/server": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/server": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@openrouter/ai-sdk-provider": "2.9.0",
|
||||
"@opentelemetry/api": "1.9.0",
|
||||
|
||||
@@ -56,34 +56,13 @@ const allTargets: {
|
||||
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",
|
||||
@@ -93,24 +72,10 @@ const allTargets: {
|
||||
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
|
||||
|
||||
@@ -93,17 +93,17 @@ export function make(input: {
|
||||
const initialize = Effect.fn("ACP.initialize")(function* (params: InitializeRequest) {
|
||||
const started = performance.now()
|
||||
const authMethod: AuthMethod = {
|
||||
description: "Run `opencode auth login` in the terminal",
|
||||
name: "Login with opencode",
|
||||
description: "Run `zmo auth login` in the terminal",
|
||||
name: "Login with zmo",
|
||||
id: AuthMethodID,
|
||||
}
|
||||
|
||||
if (params.clientCapabilities?._meta?.["terminal-auth"] === true) {
|
||||
authMethod._meta = {
|
||||
"terminal-auth": {
|
||||
command: "opencode",
|
||||
command: "zmo",
|
||||
args: ["auth", "login"],
|
||||
label: "OpenCode Login",
|
||||
label: "zmo Login",
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,7 @@ export function make(input: {
|
||||
},
|
||||
authMethods: [authMethod],
|
||||
agentInfo: {
|
||||
name: "OpenCode",
|
||||
name: "zmo",
|
||||
version: InstallationVersion,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ModelsDev } from "@opencode-ai/core/models-dev"
|
||||
import { map, pipe, sortBy, values } from "remeda"
|
||||
import path from "path"
|
||||
import os from "os"
|
||||
import fsNode from "node:fs"
|
||||
import { Config } from "@/config/config"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Plugin } from "../../plugin"
|
||||
@@ -301,7 +302,7 @@ export const ProvidersLoginCommand = effectCmd({
|
||||
builder: (yargs) =>
|
||||
yargs
|
||||
.positional("url", {
|
||||
describe: "opencode auth provider",
|
||||
describe: "zmo auth provider",
|
||||
type: "string",
|
||||
})
|
||||
.option("provider", {
|
||||
@@ -481,6 +482,38 @@ export const ProvidersLoginCommand = effectCmd({
|
||||
const apiKey = yield* promptValue(key)
|
||||
yield* Effect.orDie(authSvc.set(provider, { type: "api", key: apiKey }))
|
||||
|
||||
// zmo: 询问自定义 base URL(中转地址),留空则用官方默认。
|
||||
// baseURL 不存 auth.json,写入 config 文件的 provider.<id>.options.baseURL。
|
||||
const baseURLInput = yield* Prompt.text({
|
||||
message: "Base URL (中转地址,留空使用官方默认)",
|
||||
placeholder: "https://your-proxy.example.com/v1",
|
||||
})
|
||||
const baseURL = (yield* promptValue(baseURLInput))?.trim()
|
||||
if (baseURL) {
|
||||
try {
|
||||
const configDir = Global.Path.config
|
||||
fsNode.mkdirSync(configDir, { recursive: true })
|
||||
const file = path.join(configDir, "zmo.json")
|
||||
let data: any = {}
|
||||
if (fsNode.existsSync(file)) {
|
||||
try {
|
||||
data = JSON.parse(fsNode.readFileSync(file, "utf8")) ?? {}
|
||||
} catch {
|
||||
data = {}
|
||||
}
|
||||
}
|
||||
if (!data.$schema) data.$schema = "https://zmo.dev/config.json"
|
||||
data.provider ??= {}
|
||||
data.provider[provider] ??= {}
|
||||
data.provider[provider].options ??= {}
|
||||
data.provider[provider].options.baseURL = baseURL
|
||||
fsNode.writeFileSync(file, JSON.stringify(data, null, 2))
|
||||
yield* Prompt.log.info(`Base URL 已保存到 ${file}`)
|
||||
} catch {
|
||||
yield* Prompt.log.warn("Base URL 保存失败,请手动写入 zmo.json")
|
||||
}
|
||||
}
|
||||
|
||||
yield* Prompt.outro("Done")
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -263,7 +263,7 @@ function build(input: SplashWriterInput, kind: "entry" | "exit", ctx: Scrollback
|
||||
lines,
|
||||
body_left + label.length,
|
||||
top + 1,
|
||||
`opencode run -i -s ${meta.session_id}`,
|
||||
`zmo run -i -s ${meta.session_id}`,
|
||||
right,
|
||||
undefined,
|
||||
TextAttributes.BOLD,
|
||||
|
||||
@@ -456,24 +456,24 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
|
||||
if (!terminalTitleEnabled() || Flag.OPENCODE_DISABLE_TERMINAL_TITLE) return
|
||||
|
||||
if (route.data.type === "home") {
|
||||
renderer.setTerminalTitle("OpenCode")
|
||||
renderer.setTerminalTitle("zmo")
|
||||
return
|
||||
}
|
||||
|
||||
if (route.data.type === "session") {
|
||||
const session = sync.session.get(route.data.sessionID)
|
||||
if (!session || SessionApi.isDefaultTitle(session.title)) {
|
||||
renderer.setTerminalTitle("OpenCode")
|
||||
renderer.setTerminalTitle("zmo")
|
||||
return
|
||||
}
|
||||
|
||||
const title = session.title.length > 40 ? session.title.slice(0, 37) + "..." : session.title
|
||||
renderer.setTerminalTitle(`OC | ${title}`)
|
||||
renderer.setTerminalTitle(`zmo | ${title}`)
|
||||
return
|
||||
}
|
||||
|
||||
if (route.data.type === "plugin") {
|
||||
renderer.setTerminalTitle(`OC | ${route.data.id}`)
|
||||
renderer.setTerminalTitle(`zmo | ${route.data.id}`)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -809,7 +809,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
|
||||
name: "docs.open",
|
||||
title: "Open docs",
|
||||
run: () => {
|
||||
open("https://opencode.ai/docs").catch(() => {})
|
||||
open("https://github.com/helpLogin/zmo-cli").catch(() => {})
|
||||
dialog.clear()
|
||||
},
|
||||
category: "System",
|
||||
@@ -1053,7 +1053,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
|
||||
await DialogAlert.show(
|
||||
dialog,
|
||||
"Update Complete",
|
||||
`Successfully updated to OpenCode v${result.data.version}. Please restart the application.`,
|
||||
`Successfully updated to zmo v${result.data.version}. Please restart the application.`,
|
||||
)
|
||||
|
||||
void exit()
|
||||
|
||||
@@ -40,14 +40,14 @@ type TuiAttentionHost = TuiAttention & {
|
||||
|
||||
const log = Log.create({ service: "tui.attention" })
|
||||
|
||||
const DEFAULT_TITLE = "opencode"
|
||||
const DEFAULT_TITLE = "zmo"
|
||||
const DEFAULT_PACK_ID = "opencode.default"
|
||||
const KV_SOUND_PACK = "attention_sound_pack"
|
||||
const TITLE_LIMIT = 80
|
||||
const MESSAGE_LIMIT = 240
|
||||
const BUILTIN_PACK: RegisteredSoundPack = {
|
||||
id: DEFAULT_PACK_ID,
|
||||
name: "OpenCode Default",
|
||||
name: "zmo Default",
|
||||
builtin: true,
|
||||
sounds: {
|
||||
default: defaultSoundPath,
|
||||
|
||||
@@ -365,22 +365,14 @@ function ApiMethod(props: ApiMethodProps) {
|
||||
opencode: (
|
||||
<box gap={1}>
|
||||
<text fg={theme.textMuted}>
|
||||
OpenCode Zen gives you access to all the best coding models at the cheapest prices with a single API
|
||||
key.
|
||||
</text>
|
||||
<text fg={theme.text}>
|
||||
Go to <span style={{ fg: theme.primary }}>https://opencode.ai/zen</span> to get a key
|
||||
Enter your API key to connect this provider.
|
||||
</text>
|
||||
</box>
|
||||
),
|
||||
"opencode-go": (
|
||||
<box gap={1}>
|
||||
<text fg={theme.textMuted}>
|
||||
OpenCode Go is a $10 per month subscription that provides reliable access to popular open coding models
|
||||
with generous usage limits.
|
||||
</text>
|
||||
<text fg={theme.text}>
|
||||
Go to <span style={{ fg: theme.primary }}>https://opencode.ai/go</span> and enable OpenCode Go
|
||||
Enter your API key to connect this provider.
|
||||
</text>
|
||||
</box>
|
||||
),
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Link } from "@tui/ui/link"
|
||||
import { BgPulse } from "./bg-pulse"
|
||||
import { useBindings } from "../keymap"
|
||||
|
||||
const GO_URL = "https://opencode.ai/go"
|
||||
const GO_URL = "https://github.com/helpLogin/zmo-cli"
|
||||
const PAD_X = 3
|
||||
const PAD_TOP_OUTER = 1
|
||||
const FOREGROUND_ALPHA = 186
|
||||
|
||||
@@ -80,7 +80,7 @@ export function DialogStatus() {
|
||||
<Match when={item.status === "failed" && item}>{(val) => val().error}</Match>
|
||||
<Match when={item.status === "disabled"}>Disabled in configuration</Match>
|
||||
<Match when={(item.status as string) === "needs_auth"}>
|
||||
Needs authentication (run: opencode mcp auth {key})
|
||||
Needs authentication (run: zmo mcp auth {key})
|
||||
</Match>
|
||||
<Match when={(item.status as string) === "needs_client_registration" && item}>
|
||||
{(val) => (val() as { error: string }).error}
|
||||
|
||||
@@ -20,7 +20,7 @@ export function ErrorComponent(props: {
|
||||
})
|
||||
const [copied, setCopied] = createSignal(false)
|
||||
|
||||
const issueURL = new URL("https://github.com/anomalyco/opencode/issues/new?template=bug-report.yml")
|
||||
const issueURL = new URL("https://github.com/helpLogin/zmo-cli/issues/new")
|
||||
|
||||
// Choose safe fallback colors per mode since theme context may not be available
|
||||
const isLight = props.mode === "light"
|
||||
|
||||
@@ -11,7 +11,7 @@ import * as ConfigPaths from "@/config/paths"
|
||||
|
||||
const log = Log.create({ service: "tui.migrate" })
|
||||
|
||||
const TUI_SCHEMA_URL = "https://opencode.ai/tui.json"
|
||||
const TUI_SCHEMA_URL = "https://zmo.dev/tui.json"
|
||||
|
||||
const decodeTheme = Schema.decodeUnknownOption(Schema.String)
|
||||
const decodeRecord = Schema.decodeUnknownOption(Schema.Record(Schema.String, Schema.Unknown))
|
||||
|
||||
@@ -167,7 +167,7 @@ const TIPS: Tip[] = [
|
||||
(shortcuts) => press(shortcuts.agentCycle(), "to cycle between Build and Plan agents"),
|
||||
"Use {highlight}/undo{/highlight} to revert the last message and file changes",
|
||||
"Use {highlight}/redo{/highlight} to restore previously undone messages and file changes",
|
||||
"Run {highlight}/share{/highlight} to create a public link to your conversation at opencode.ai",
|
||||
"Run {highlight}/share{/highlight} to create a public link to your conversation",
|
||||
"Drag and drop images or PDFs into the terminal to add them as context",
|
||||
(shortcuts) => press(shortcuts.inputPaste(), "to paste images from your clipboard into the prompt"),
|
||||
(shortcuts) => `Use ${commandText("/editor", shortcuts.editorOpen())} to compose messages in your external editor`,
|
||||
@@ -210,17 +210,17 @@ const TIPS: Tip[] = [
|
||||
if (!items.length) return undefined
|
||||
return `Use ${items.map(shortcutText).join(" / ")} to move between parent and child sessions`
|
||||
},
|
||||
"Create {highlight}opencode.json{/highlight} for server settings and {highlight}tui.json{/highlight} for TUI settings",
|
||||
"Place TUI settings in {highlight}~/.config/opencode/tui.json{/highlight} for global config",
|
||||
"Create {highlight}zmo.json{/highlight} for server settings and {highlight}tui.json{/highlight} for TUI settings",
|
||||
"Place TUI settings in {highlight}~/.config/zmo/tui.json{/highlight} for global config",
|
||||
"Add {highlight}$schema{/highlight} to your config for autocomplete in your editor",
|
||||
"Configure {highlight}model{/highlight} in config to set your default model",
|
||||
"Override any keybind in {highlight}tui.json{/highlight} via the {highlight}keybinds{/highlight} section",
|
||||
"Set any keybind to {highlight}none{/highlight} to disable it completely",
|
||||
"Configure local or remote MCP servers in the {highlight}mcp{/highlight} config section",
|
||||
"Add {highlight}.md{/highlight} files to {highlight}.opencode/commands/{/highlight} to define reusable custom prompts",
|
||||
"Add {highlight}.md{/highlight} files to {highlight}.zmo/commands/{/highlight} to define reusable custom prompts",
|
||||
"Use {highlight}$ARGUMENTS{/highlight}, {highlight}$1{/highlight}, {highlight}$2{/highlight} in custom commands for dynamic input",
|
||||
"Use backticks in commands to inject shell output (e.g., {highlight}`git status`{/highlight})",
|
||||
"Add {highlight}.md{/highlight} files to {highlight}.opencode/agents/{/highlight} for specialized AI personas",
|
||||
"Add {highlight}.md{/highlight} files to {highlight}.zmo/agents/{/highlight} for specialized AI personas",
|
||||
"Configure per-agent permissions for {highlight}edit{/highlight}, {highlight}bash{/highlight}, and {highlight}webfetch{/highlight} tools",
|
||||
'Use patterns like {highlight}"git *": "allow"{/highlight} for granular bash permissions',
|
||||
'Set {highlight}"rm -rf *": "deny"{/highlight} to block destructive commands',
|
||||
@@ -229,26 +229,25 @@ const TIPS: Tip[] = [
|
||||
'Set {highlight}"formatter": false{/highlight} in config to disable formatters enabled by another config layer',
|
||||
"Define custom formatter commands with file extensions in config",
|
||||
'Set {highlight}"lsp": true{/highlight} in config to enable built-in LSP servers for code analysis',
|
||||
"Create {highlight}.ts{/highlight} files in {highlight}.opencode/tools/{/highlight} to define new LLM tools",
|
||||
"Create {highlight}.ts{/highlight} files in {highlight}.zmo/tools/{/highlight} to define new LLM tools",
|
||||
"Tool definitions can invoke scripts written in Python, Go, etc",
|
||||
"Add {highlight}.ts{/highlight} files to {highlight}.opencode/plugins/{/highlight} for event hooks",
|
||||
"Add {highlight}.ts{/highlight} files to {highlight}.zmo/plugins/{/highlight} for event hooks",
|
||||
"Use plugins to send OS notifications when sessions complete",
|
||||
"Create a plugin to prevent OpenCode from reading sensitive files",
|
||||
"Use {highlight}opencode run{/highlight} for non-interactive scripting",
|
||||
"Use {highlight}opencode --continue{/highlight} to resume the last session",
|
||||
"Use {highlight}opencode run -f file.ts{/highlight} to attach files via CLI",
|
||||
"Create a plugin to prevent zmo from reading sensitive files",
|
||||
"Use {highlight}zmo run{/highlight} for non-interactive scripting",
|
||||
"Use {highlight}zmo --continue{/highlight} to resume the last session",
|
||||
"Use {highlight}zmo run -f file.ts{/highlight} to attach files via CLI",
|
||||
"Use {highlight}--format json{/highlight} for machine-readable output in scripts",
|
||||
"Run {highlight}opencode serve{/highlight} for headless API access to OpenCode",
|
||||
"Use {highlight}opencode run --attach{/highlight} to connect to a running server",
|
||||
"Run {highlight}opencode upgrade{/highlight} to update to the latest version",
|
||||
"Run {highlight}opencode auth list{/highlight} to see all configured providers",
|
||||
"Run {highlight}opencode agent create{/highlight} for guided agent creation",
|
||||
"Use {highlight}/opencode{/highlight} in GitHub issues/PRs to trigger AI actions",
|
||||
"Run {highlight}opencode github install{/highlight} to set up the GitHub workflow",
|
||||
"Comment {highlight}/opencode fix this{/highlight} on issues to auto-create PRs",
|
||||
"Run {highlight}zmo serve{/highlight} for headless API access to zmo",
|
||||
"Use {highlight}zmo run --attach{/highlight} to connect to a running server",
|
||||
"Run {highlight}zmo upgrade{/highlight} to update to the latest version",
|
||||
"Run {highlight}zmo auth list{/highlight} to see all configured providers",
|
||||
"Run {highlight}zmo agent create{/highlight} for guided agent creation",
|
||||
"Use {highlight}--format json{/highlight} to integrate zmo into your scripts",
|
||||
"Use {highlight}zmo run --continue{/highlight} to continue the last session non-interactively",
|
||||
"Comment {highlight}/oc{/highlight} on PR code lines for targeted code reviews",
|
||||
'Use {highlight}"theme": "system"{/highlight} to match your terminal\'s colors',
|
||||
"Create JSON theme files in {highlight}.opencode/themes/{/highlight} directory",
|
||||
"Create JSON theme files in {highlight}.zmo/themes/{/highlight} directory",
|
||||
"Themes support dark/light variants for both modes",
|
||||
"Use numeric xterm color codes 0-255 in custom theme JSON",
|
||||
"Use {highlight}{env:VAR_NAME}{/highlight} syntax to reference environment variables in config",
|
||||
@@ -264,7 +263,7 @@ const TIPS: Tip[] = [
|
||||
"Run {highlight}/unshare{/highlight} to remove a session from public access",
|
||||
"Permission {highlight}doom_loop{/highlight} prevents infinite tool call loops",
|
||||
"Permission {highlight}external_directory{/highlight} protects files outside project",
|
||||
"Run {highlight}opencode debug config{/highlight} to troubleshoot configuration",
|
||||
"Run {highlight}zmo debug config{/highlight} to troubleshoot configuration",
|
||||
"Use {highlight}--print-logs{/highlight} flag to see detailed logs in stderr",
|
||||
(shortcuts) => `Use ${commandText("/timeline", shortcuts.sessionTimeline())} to jump to specific messages`,
|
||||
(shortcuts) => press(shortcuts.messagesToggleConceal(), "to toggle code block visibility in messages"),
|
||||
@@ -274,8 +273,8 @@ const TIPS: Tip[] = [
|
||||
shortcuts.commandList()
|
||||
? `Toggle username display in chat via the command palette (${shortcutText(shortcuts.commandList())})`
|
||||
: "Toggle username display in chat via the command palette",
|
||||
"Run {highlight}docker run -it --rm ghcr.io/anomalyco/opencode{/highlight} for containerized use",
|
||||
"Use {highlight}/connect{/highlight} with OpenCode Zen for curated, tested models",
|
||||
"Switch between models mid-session to use the best tool for each task",
|
||||
"Configure any OpenAI-compatible provider via {highlight}baseURL{/highlight} in your config",
|
||||
"Commit your project's {highlight}AGENTS.md{/highlight} file to Git for team sharing",
|
||||
"Use {highlight}/review{/highlight} to review uncommitted changes, branches, or PRs",
|
||||
(shortcuts) => `Use ${commandText("/help", shortcuts.helpShow())} to show the help dialog`,
|
||||
|
||||
@@ -51,7 +51,7 @@ function View(props: { api: TuiPluginApi; sessionID: string }) {
|
||||
✕
|
||||
</text>
|
||||
</box>
|
||||
<text fg={theme().textMuted}>OpenCode includes free models so you can start immediately.</text>
|
||||
<text fg={theme().textMuted}>zmo works with any model provider — connect your API key to start.</text>
|
||||
<text fg={theme().textMuted}>
|
||||
Connect from 75+ providers to use other models, including Claude, GPT, Gemini etc
|
||||
</text>
|
||||
|
||||
@@ -28,7 +28,7 @@ export const UpgradeCommand = {
|
||||
const detectedMethod = await Installation.method()
|
||||
const method = (args.method as Installation.Method) ?? detectedMethod
|
||||
if (method === "unknown") {
|
||||
prompts.log.error(`opencode is installed to ${process.execPath} and may be managed by a package manager`)
|
||||
prompts.log.error(`zmo is installed to ${process.execPath} and may be managed by a package manager`)
|
||||
const install = await prompts.select({
|
||||
message: "Install anyways?",
|
||||
options: [
|
||||
@@ -46,7 +46,7 @@ export const UpgradeCommand = {
|
||||
const target = args.target ? args.target.replace(/^v/, "") : await Installation.latest()
|
||||
|
||||
if (InstallationVersion === target) {
|
||||
prompts.log.warn(`opencode upgrade skipped: ${target} is already installed`)
|
||||
prompts.log.warn(`zmo upgrade skipped: ${target} is already installed`)
|
||||
prompts.outro("Done")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Co
|
||||
export const use = serviceUse(Service)
|
||||
|
||||
function globalConfigFile() {
|
||||
const candidates = ["opencode.jsonc", "opencode.json", "config.json"].map((file) =>
|
||||
const candidates = ["zmo.jsonc", "zmo.json", "opencode.jsonc", "opencode.json", "config.json"].map((file) =>
|
||||
path.join(Global.Path.config, file),
|
||||
)
|
||||
for (const file of candidates) {
|
||||
@@ -221,8 +221,8 @@ export const layer = Layer.effect(
|
||||
|
||||
yield* Effect.promise(() => resolveLoadedPlugins(data, options.path))
|
||||
if (!data.$schema) {
|
||||
data.$schema = "https://opencode.ai/config.json"
|
||||
const updated = text.replace(/^\s*\{/, '{\n "$schema": "https://opencode.ai/config.json",')
|
||||
data.$schema = "https://zmo.dev/config.json"
|
||||
const updated = text.replace(/^\s*\{/, '{\n "$schema": "https://zmo.dev/config.json",')
|
||||
yield* fs.writeFileString(options.path, updated).pipe(Effect.catch(() => Effect.void))
|
||||
}
|
||||
return data
|
||||
@@ -243,13 +243,15 @@ export const layer = Layer.effect(
|
||||
const file = globalConfigFile()
|
||||
if (!existsSync(file)) {
|
||||
yield* fs
|
||||
.writeWithDirs(file, JSON.stringify({ $schema: "https://opencode.ai/config.json" }, null, 2))
|
||||
.writeWithDirs(file, JSON.stringify({ $schema: "https://zmo.dev/config.json" }, null, 2))
|
||||
.pipe(Effect.catch(() => Effect.void))
|
||||
}
|
||||
}
|
||||
result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "config.json"), env))
|
||||
result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "opencode.json"), env))
|
||||
result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "opencode.jsonc"), env))
|
||||
result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "zmo.json"), env))
|
||||
result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "zmo.jsonc"), env))
|
||||
|
||||
const legacy = path.join(Global.Path.config, "config")
|
||||
if (existsSync(legacy)) {
|
||||
@@ -258,7 +260,7 @@ export const layer = Layer.effect(
|
||||
.then(async (mod) => {
|
||||
const { provider, model, ...rest } = mod.default
|
||||
if (provider && model) result.model = `${provider}/${model}`
|
||||
result["$schema"] = "https://opencode.ai/config.json"
|
||||
result["$schema"] = "https://zmo.dev/config.json"
|
||||
result = mergeConfig(result, rest)
|
||||
await fsNode.writeFile(path.join(Global.Path.config, "config.json"), JSON.stringify(result, null, 2))
|
||||
await fsNode.unlink(legacy)
|
||||
@@ -371,7 +373,7 @@ export const layer = Layer.effect(
|
||||
})
|
||||
: {}
|
||||
const remoteConfig = mergeConfig(isRecord(wellknown.config) ? wellknown.config : {}, fetchedConfig)
|
||||
if (!remoteConfig.$schema) remoteConfig.$schema = "https://opencode.ai/config.json"
|
||||
if (!remoteConfig.$schema) remoteConfig.$schema = "https://zmo.dev/config.json"
|
||||
const source = wellknownURL
|
||||
const next = yield* loadConfig(
|
||||
JSON.stringify(remoteConfig),
|
||||
@@ -414,7 +416,7 @@ export const layer = Layer.effect(
|
||||
|
||||
for (const dir of directories) {
|
||||
if (dir.endsWith(".opencode") || dir === Flag.OPENCODE_CONFIG_DIR) {
|
||||
for (const file of ["opencode.json", "opencode.jsonc"]) {
|
||||
for (const file of ["zmo.json", "zmo.jsonc", "opencode.json", "opencode.jsonc"]) {
|
||||
const source = path.join(dir, file)
|
||||
log.debug(`loading config from ${source}`)
|
||||
yield* merge(source, yield* loadFile(source, authEnv))
|
||||
@@ -509,7 +511,7 @@ export const layer = Layer.effect(
|
||||
|
||||
const managedDir = ConfigManaged.managedConfigDir()
|
||||
if (existsSync(managedDir)) {
|
||||
for (const file of ["opencode.json", "opencode.jsonc"]) {
|
||||
for (const file of ["zmo.json", "zmo.jsonc", "opencode.json", "opencode.jsonc"]) {
|
||||
const source = path.join(managedDir, file)
|
||||
yield* merge(source, yield* loadFile(source), "global")
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import { errorMessage } from "@/util/error"
|
||||
import { ChildProcess } from "effect/unstable/process"
|
||||
import { AppProcess } from "@opencode-ai/core/process"
|
||||
import path from "path"
|
||||
import os from "os"
|
||||
import fs from "fs"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { makeRuntime } from "@opencode-ai/core/effect/runtime"
|
||||
@@ -52,7 +54,7 @@ export const Info = Schema.Struct({
|
||||
export type Info = Schema.Schema.Type<typeof Info>
|
||||
|
||||
export function userAgent(client = "cli") {
|
||||
return `opencode/${InstallationChannel}/${InstallationVersion}/${client}`
|
||||
return `zmo/${InstallationChannel}/${InstallationVersion}/${client}`
|
||||
}
|
||||
|
||||
export const USER_AGENT = userAgent()
|
||||
@@ -155,9 +157,59 @@ export const layer: Layer.Layer<Service, never, HttpClient.HttpClient | AppProce
|
||||
return "sh"
|
||||
})
|
||||
|
||||
const upgradeWindows = Effect.fnUntraced(function* (target: string) {
|
||||
// Windows 自更新:运行中的 exe 无法直接覆盖自身,
|
||||
// 因此下载新 zip → 解压 → 写一个 .cmd 在本进程退出后替换 exe。
|
||||
const exePath = process.execPath
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "zmo-upgrade-"))
|
||||
const zipPath = path.join(tmpDir, "zmo-windows-x64.zip")
|
||||
const url = `https://github.com/helpLogin/zmo-cli/releases/download/v${target}/zmo-windows-x64.zip`
|
||||
|
||||
// 1. 下载新版 zip
|
||||
const res = yield* httpOk.execute(HttpClientRequest.get(url))
|
||||
const buf = yield* res.arrayBuffer
|
||||
fs.writeFileSync(zipPath, Buffer.from(buf))
|
||||
|
||||
// 2. 用 PowerShell 解压(Win10+ 自带 Expand-Archive)
|
||||
const unzip = yield* run([
|
||||
"powershell",
|
||||
"-NoProfile",
|
||||
"-Command",
|
||||
`Expand-Archive -Path '${zipPath}' -DestinationPath '${tmpDir}' -Force`,
|
||||
])
|
||||
if (unzip.code !== 0) return unzip
|
||||
const newExe = path.join(tmpDir, "zmo.exe")
|
||||
if (!fs.existsSync(newExe)) {
|
||||
return { code: 1, stdout: "", stderr: "downloaded archive did not contain zmo.exe" }
|
||||
}
|
||||
|
||||
// 3. 写 .cmd:等待本进程退出 → 覆盖旧 exe → 清理临时目录
|
||||
const cmdPath = path.join(tmpDir, "zmo-replace.cmd")
|
||||
const script = [
|
||||
"@echo off",
|
||||
"ping 127.0.0.1 -n 3 >nul",
|
||||
`:retry`,
|
||||
`move /Y "${newExe}" "${exePath}" >nul 2>&1`,
|
||||
`if errorlevel 1 (`,
|
||||
` ping 127.0.0.1 -n 2 >nul`,
|
||||
` goto retry`,
|
||||
`)`,
|
||||
`rmdir /S /Q "${tmpDir}" >nul 2>&1`,
|
||||
].join("\r\n")
|
||||
fs.writeFileSync(cmdPath, script)
|
||||
|
||||
// 4. detached 启动 cmd,本进程随后正常退出,让 cmd 完成替换
|
||||
Bun.spawn(["cmd", "/c", cmdPath], { stdio: ["ignore", "ignore", "ignore"] }).unref()
|
||||
|
||||
return { code: 0, stdout: `zmo will finish updating to v${target} after exit. Please restart zmo.`, stderr: "" }
|
||||
}, Effect.catch((err) => Effect.succeed({ code: 1, stdout: "", stderr: errorMessage(err) })))
|
||||
|
||||
const upgradeCurl = Effect.fnUntraced(
|
||||
function* (target: string) {
|
||||
const response = yield* httpOk.execute(HttpClientRequest.get("https://opencode.ai/install"))
|
||||
if (process.platform === "win32") {
|
||||
return yield* upgradeWindows(target)
|
||||
}
|
||||
const response = yield* httpOk.execute(HttpClientRequest.get("https://raw.githubusercontent.com/helpLogin/zmo-cli/main/install"))
|
||||
const body = yield* response.text
|
||||
const bodyBytes = new TextEncoder().encode(body)
|
||||
const shell = yield* upgradeScriptShell()
|
||||
@@ -185,7 +237,7 @@ export const layer: Layer.Layer<Service, never, HttpClient.HttpClient | AppProce
|
||||
}
|
||||
}),
|
||||
method: Effect.fn("Installation.method")(function* () {
|
||||
if (process.execPath.includes(path.join(".opencode", "bin"))) return "curl" as Method
|
||||
if (process.execPath.includes(path.join(".zmo", "bin"))) return "curl" as Method
|
||||
if (process.execPath.includes(path.join(".local", "bin"))) return "curl" as Method
|
||||
const exec = process.execPath.toLowerCase()
|
||||
|
||||
@@ -216,6 +268,9 @@ export const layer: Layer.Layer<Service, never, HttpClient.HttpClient | AppProce
|
||||
}
|
||||
}
|
||||
|
||||
// Windows 上手动下载的 exe 不在包管理器里,但我们的 Windows 升级路径
|
||||
// 可自我替换任意位置的 exe,所以兜底按 curl 处理以启用自动更新。
|
||||
if (process.platform === "win32") return "curl" as Method
|
||||
return "unknown" as Method
|
||||
}),
|
||||
latest: Effect.fn("Installation.latest")(function* (installMethod?: Method) {
|
||||
@@ -268,7 +323,7 @@ export const layer: Layer.Layer<Service, never, HttpClient.HttpClient | AppProce
|
||||
}
|
||||
|
||||
const response = yield* httpOk.execute(
|
||||
HttpClientRequest.get("https://api.github.com/repos/anomalyco/opencode/releases/latest").pipe(
|
||||
HttpClientRequest.get("https://api.github.com/repos/helpLogin/zmo-cli/releases/latest").pipe(
|
||||
HttpClientRequest.acceptJson,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -87,7 +87,7 @@ function buildAuthorizeUrl(redirectUri: string, pkce: PkceCodes, state: string):
|
||||
id_token_add_organizations: "true",
|
||||
codex_cli_simplified_flow: "true",
|
||||
state,
|
||||
originator: "opencode",
|
||||
originator: "zmo",
|
||||
})
|
||||
return `${ISSUER}/oauth/authorize?${params.toString()}`
|
||||
}
|
||||
@@ -547,7 +547,7 @@ export async function CodexAuthPlugin(input: PluginInput, options: CodexAuthPlug
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": `opencode/${InstallationVersion}`,
|
||||
"User-Agent": `zmo/${InstallationVersion}`,
|
||||
},
|
||||
body: JSON.stringify({ client_id: CLIENT_ID }),
|
||||
})
|
||||
@@ -571,7 +571,7 @@ export async function CodexAuthPlugin(input: PluginInput, options: CodexAuthPlug
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": `opencode/${InstallationVersion}`,
|
||||
"User-Agent": `zmo/${InstallationVersion}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
device_auth_id: deviceData.device_auth_id,
|
||||
@@ -630,8 +630,8 @@ export async function CodexAuthPlugin(input: PluginInput, options: CodexAuthPlug
|
||||
},
|
||||
"chat.headers": async (input, output) => {
|
||||
if (input.model.providerID !== "openai") return
|
||||
output.headers.originator = "opencode"
|
||||
output.headers["User-Agent"] = `opencode/${InstallationVersion} (${os.platform()} ${os.release()}; ${os.arch()})`
|
||||
output.headers.originator = "zmo"
|
||||
output.headers["User-Agent"] = `zmo/${InstallationVersion} (${os.platform()} ${os.release()}; ${os.arch()})`
|
||||
output.headers["session-id"] = input.sessionID
|
||||
// Temporary fetch-layer hack: title generation currently shares the conversation
|
||||
// session ID, so the OpenAI plugin marks it for HTTP fallback until transport
|
||||
|
||||
@@ -109,7 +109,7 @@ function authHeaders() {
|
||||
return {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Accept: "application/json",
|
||||
"User-Agent": `opencode/${InstallationVersion}`,
|
||||
"User-Agent": `zmo/${InstallationVersion}`,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ export function buildAuthorizeUrl(
|
||||
state,
|
||||
nonce,
|
||||
plan: "generic",
|
||||
referrer: "opencode",
|
||||
referrer: "zmo",
|
||||
})
|
||||
return `${options.authorizeUrl ?? AUTHORIZE_URL}?${params.toString()}`
|
||||
}
|
||||
@@ -655,7 +655,7 @@ export async function XaiAuthPlugin(input: PluginInput, options: XaiAuthPluginOp
|
||||
}
|
||||
}
|
||||
headers.set("authorization", `Bearer ${currentAuth.access}`)
|
||||
headers.set("User-Agent", `opencode/${InstallationVersion}`)
|
||||
headers.set("User-Agent", `zmo/${InstallationVersion}`)
|
||||
|
||||
return fetch(requestInput, { ...init, headers })
|
||||
},
|
||||
|
||||
@@ -435,9 +435,9 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
autoload: false,
|
||||
options: {
|
||||
headers: {
|
||||
"HTTP-Referer": "https://opencode.ai/",
|
||||
"X-Title": "opencode",
|
||||
"X-Source": "opencode",
|
||||
"HTTP-Referer": "https://github.com/helpLogin/zmo-cli",
|
||||
"X-Title": "zmo",
|
||||
"X-Source": "zmo",
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -446,8 +446,8 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
autoload: false,
|
||||
options: {
|
||||
headers: {
|
||||
"HTTP-Referer": "https://opencode.ai/",
|
||||
"X-Title": "opencode",
|
||||
"HTTP-Referer": "https://github.com/helpLogin/zmo-cli",
|
||||
"X-Title": "zmo",
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -456,9 +456,9 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
autoload: provider.source === "config",
|
||||
options: {
|
||||
headers: {
|
||||
"HTTP-Referer": "https://opencode.ai/",
|
||||
"X-Title": "opencode",
|
||||
"X-BILLING-INVOKE-ORIGIN": "OpenCode",
|
||||
"HTTP-Referer": "https://github.com/helpLogin/zmo-cli",
|
||||
"X-Title": "zmo",
|
||||
"X-BILLING-INVOKE-ORIGIN": "zmo",
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -467,8 +467,8 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
autoload: false,
|
||||
options: {
|
||||
headers: {
|
||||
"http-referer": "https://opencode.ai/",
|
||||
"x-title": "opencode",
|
||||
"http-referer": "https://github.com/helpLogin/zmo-cli",
|
||||
"x-title": "zmo",
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -573,8 +573,8 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
autoload: false,
|
||||
options: {
|
||||
headers: {
|
||||
"HTTP-Referer": "https://opencode.ai/",
|
||||
"X-Title": "opencode",
|
||||
"HTTP-Referer": "https://github.com/helpLogin/zmo-cli",
|
||||
"X-Title": "zmo",
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -595,7 +595,7 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
const directory = yield* InstanceState.directory
|
||||
|
||||
const aiGatewayHeaders = {
|
||||
"User-Agent": `opencode/${InstallationVersion} gitlab-ai-provider/${GITLAB_PROVIDER_VERSION} (${os.platform()} ${os.release()}; ${os.arch()})`,
|
||||
"User-Agent": `zmo/${InstallationVersion} gitlab-ai-provider/${GITLAB_PROVIDER_VERSION} (${os.platform()} ${os.release()}; ${os.arch()})`,
|
||||
"anthropic-beta": "context-1m-2025-08-07",
|
||||
...providerConfig?.options?.aiGatewayHeaders,
|
||||
}
|
||||
@@ -743,7 +743,7 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
options: {
|
||||
apiKey,
|
||||
headers: {
|
||||
"User-Agent": `opencode/${InstallationVersion} cloudflare-workers-ai (${os.platform()} ${os.release()}; ${os.arch()})`,
|
||||
"User-Agent": `zmo/${InstallationVersion} cloudflare-workers-ai (${os.platform()} ${os.release()}; ${os.arch()})`,
|
||||
},
|
||||
},
|
||||
async getModel(sdk: any, modelID: string) {
|
||||
@@ -788,7 +788,7 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
if (!apiToken) {
|
||||
throw new Error(
|
||||
"CLOUDFLARE_API_TOKEN (or CF_AIG_TOKEN) is required for Cloudflare AI Gateway. " +
|
||||
"Set it via environment variable or run `opencode auth cloudflare-ai-gateway`.",
|
||||
"Set it via environment variable or run `zmo auth cloudflare-ai-gateway`.",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -811,7 +811,7 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
skipCache: input.options?.skipCache,
|
||||
collectLog: input.options?.collectLog,
|
||||
headers: {
|
||||
"User-Agent": `opencode/${InstallationVersion} cloudflare-ai-gateway (${os.platform()} ${os.release()}; ${os.arch()})`,
|
||||
"User-Agent": `zmo/${InstallationVersion} cloudflare-ai-gateway (${os.platform()} ${os.release()}; ${os.arch()})`,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -837,7 +837,7 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
autoload: false,
|
||||
options: {
|
||||
headers: {
|
||||
"X-Cerebras-3rd-Party-Integration": "opencode",
|
||||
"X-Cerebras-3rd-Party-Integration": "zmo",
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -846,8 +846,8 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
autoload: false,
|
||||
options: {
|
||||
headers: {
|
||||
"HTTP-Referer": "https://opencode.ai/",
|
||||
"X-Title": "opencode",
|
||||
"HTTP-Referer": "https://github.com/helpLogin/zmo-cli",
|
||||
"X-Title": "zmo",
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -1151,7 +1151,7 @@ export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
|
||||
return {
|
||||
id: ProviderV2.ID.make(provider.id),
|
||||
source: "custom",
|
||||
name: provider.name,
|
||||
name: provider.id === "opencode" ? "Zmo" : provider.name.replace(/OpenCode/gi, "Zmo"),
|
||||
env: [...(provider.env ?? [])],
|
||||
options: {},
|
||||
models,
|
||||
@@ -1540,6 +1540,17 @@ export const layer = Layer.effect(
|
||||
const provider = s.providers[model.providerID]
|
||||
const options = { ...provider.options }
|
||||
|
||||
// zmo: 支持用环境变量配 baseURL(所有 provider 通用)。
|
||||
// 优先级:config options.baseURL > 专属 <PROVIDER>_BASE_URL > 通用 ZMO_BASE_URL。
|
||||
// 仅当 config 未显式设置 baseURL 时才用环境变量回退。
|
||||
if (typeof options["baseURL"] !== "string" || options["baseURL"] === "") {
|
||||
const envName = model.providerID.toUpperCase().replace(/-/g, "_") + "_BASE_URL"
|
||||
const envBaseURL = envs[envName] ?? envs["ZMO_BASE_URL"]
|
||||
if (typeof envBaseURL === "string" && envBaseURL !== "") {
|
||||
options["baseURL"] = envBaseURL
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
model.providerID === "google-vertex" &&
|
||||
model.api.npm === "@ai-sdk/google-vertex/anthropic" &&
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
You are OpenCode, the best coding agent on the planet.
|
||||
You are Zmo, the best coding agent on the planet.
|
||||
|
||||
You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
|
||||
|
||||
@@ -7,9 +7,9 @@ IMPORTANT: You must NEVER generate or guess URLs for the user unless you are con
|
||||
If the user asks for help or wants to give feedback inform them of the following:
|
||||
- ctrl+p to list available actions
|
||||
- To give feedback, users should report the issue at
|
||||
https://github.com/anomalyco/opencode
|
||||
https://github.com/helpLogin/zmo-cli
|
||||
|
||||
When the user directly asks about OpenCode (eg. "can OpenCode do...", "does OpenCode have..."), or asks in second person (eg. "are you able...", "can you do..."), or asks how to use a specific OpenCode feature (eg. implement a hook, write a slash command, or install an MCP server), use the WebFetch tool to gather information to answer the question from OpenCode docs. The list of available docs is available at https://opencode.ai/docs
|
||||
When the user directly asks about Zmo (eg. "can Zmo do...", "does Zmo have..."), or asks in second person (eg. "are you able...", "can you do..."), or asks how to use a specific Zmo feature (eg. implement a hook, write a slash command, or install an MCP server), use the WebFetch tool to gather information to answer the question.
|
||||
|
||||
# Tone and style
|
||||
- Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked.
|
||||
@@ -18,7 +18,7 @@ When the user directly asks about OpenCode (eg. "can OpenCode do...", "does Open
|
||||
- NEVER create files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one. This includes markdown files.
|
||||
|
||||
# Professional objectivity
|
||||
Prioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without any unnecessary superlatives, praise, or emotional validation. It is best for the user if OpenCode honestly applies the same rigorous standards to all ideas and disagrees when necessary, even if it may not be what the user wants to hear. Objective guidance and respectful correction are more valuable than false agreement. Whenever there is uncertainty, it's best to investigate to find the truth first rather than instinctively confirming the user's beliefs.
|
||||
Prioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without any unnecessary superlatives, praise, or emotional validation. It is best for the user if Zmo honestly applies the same rigorous standards to all ideas and disagrees when necessary, even if it may not be what the user wants to hear. Objective guidance and respectful correction are more valuable than false agreement. Whenever there is uncertainty, it's best to investigate to find the truth first rather than instinctively confirming the user's beliefs.
|
||||
|
||||
# Task Management
|
||||
You have access to the TodoWrite tools to help you manage and plan tasks. Use these tools VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
You are opencode, an agent - please keep going until the user’s query is completely resolved, before ending your turn and yielding back to the user.
|
||||
You are zmo, an agent - please keep going until the user’s query is completely resolved, before ending your turn and yielding back to the user.
|
||||
|
||||
Your thinking should be thorough and so it's fine if it's very long. However, avoid unnecessary repetition and verbosity. You should be concise, but thorough.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
You are OpenCode, the best coding agent on the planet.
|
||||
You are Zmo, the best coding agent on the planet.
|
||||
|
||||
You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
You are an expert AI programming assistant
|
||||
Your name is opencode
|
||||
Your name is zmo
|
||||
Keep your answers short and impersonal.
|
||||
<gptAgentInstructions>
|
||||
You are a highly sophisticated coding agent with expert-level knowledge across programming languages and frameworks.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
You are opencode, an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
|
||||
You are zmo, an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
|
||||
|
||||
IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.
|
||||
|
||||
If the user asks for help or wants to give feedback inform them of the following:
|
||||
- /help: Get help with using opencode
|
||||
- To give feedback, users should report the issue at https://github.com/anomalyco/opencode/issues
|
||||
- /help: Get help with using zmo
|
||||
- To give feedback, users should report the issue at https://github.com/helpLogin/zmo-cli/issues
|
||||
|
||||
When the user directly asks about opencode (eg 'can opencode do...', 'does opencode have...') or asks in second person (eg 'are you able...', 'can you do...'), first use the WebFetch tool to gather information to answer the question from opencode docs at https://opencode.ai
|
||||
When the user directly asks about zmo (eg 'can zmo do...', 'does zmo have...') or asks in second person (eg 'are you able...', 'can you do...'), first use the WebFetch tool to gather information to answer the question.
|
||||
|
||||
# Tone and style
|
||||
You should be concise, direct, and to the point. When you run a non-trivial bash command, you should explain what the command does and why you are running it, to make sure the user understands what you are doing (this is especially important when you are running a command that will make changes to the user's system).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
You are opencode, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and efficiently, adhering strictly to the following instructions and utilizing your available tools.
|
||||
You are zmo, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and efficiently, adhering strictly to the following instructions and utilizing your available tools.
|
||||
|
||||
# Core Mandates
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
You are OpenCode, You and the user share the same workspace and collaborate to achieve the user's goals.
|
||||
You are Zmo, You and the user share the same workspace and collaborate to achieve the user's goals.
|
||||
|
||||
You are a deeply pragmatic, effective software engineer. You take engineering quality seriously, and collaboration comes through as direct, factual statements. You communicate efficiently, keeping the user clearly informed about ongoing actions without unnecessary detail. You build context by examining the codebase first without making assumptions or jumping to conclusions. You think through the nuances of the code you encounter, and embody the mentality of a skilled senior software engineer.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
You are OpenCode, an interactive general AI agent running on a user's computer.
|
||||
You are Zmo, an interactive general AI agent running on a user's computer.
|
||||
|
||||
Your primary goal is to help users with software engineering tasks by taking action — use the tools available to you to make real changes on the user's system. You should also answer questions when asked. Always adhere strictly to the following system instructions and the user's requirements.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
You are opencode, an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
|
||||
You are zmo, an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
|
||||
|
||||
# Tone and style
|
||||
You should be concise, direct, and to the point. When you run a non-trivial bash command, you should explain what the command does and why you are running it, to make sure the user understands what you are doing (this is especially important when you are running a command that will make changes to the user's system).
|
||||
|
||||
@@ -49,7 +49,7 @@ export const layer = Layer.effect(
|
||||
const ctx = yield* InstanceState.context
|
||||
return [
|
||||
[
|
||||
`You are powered by the model named ${model.api.id}. The exact model ID is ${model.providerID}/${model.api.id}`,
|
||||
`You are zmo, an AI coding assistant running in the terminal.`,
|
||||
`Here is some useful information about the environment you are running in:`,
|
||||
`<env>`,
|
||||
` Working directory: ${ctx.directory}`,
|
||||
|
||||
Reference in New Issue
Block a user