Merge pull request #8 from kevinWangSheng/feat/opencode-agent

feat(agent): add OpenCode support
This commit is contained in:
cg33 authored and GitHub committed 2026-03-04 10:08:11 +08:00
commit 33dcbbabf3
6 files changed
+635 -13

No files matched your search

+22 -6
View File
@@ -20,15 +20,15 @@ cc-connect bridges AI coding assistants running on your dev machine to the messa
└────────────┘
┌─────┼─────┐
▼ ▼ ▼
Claude Gemini Codex ...5 agents
Code CLI
Claude Gemini Codex ...6 agents
Code CLI OpenCode
```
### Why cc-connect?
> Time to uninstall OpenClaw — cc-connect gives you access to the most powerful coding agents available, not just one.
- **5 AI Agents** — Claude Code, Codex, Cursor Agent, Qoder CLI, Gemini CLI. Use whichever fits your workflow, or all of them at once.
- **6 AI Agents** — Claude Code, Codex, Cursor Agent, Qoder CLI, Gemini CLI, OpenCode. Use whichever fits your workflow, or all of them at once.
- **8 Chat Platforms** — Feishu, DingTalk, Slack, Telegram, Discord, WeChat Work, LINE, QQ. Most need zero public IP.
- **Full Control from Chat** — Switch models (`/model`), change permission modes (`/mode`), manage sessions, all via slash commands.
- **Agent Memory** — Read and write agent instruction files (`/memory`) without touching the terminal.
@@ -49,7 +49,8 @@ cc-connect bridges AI coding assistants running on your dev machine to the messa
| Agent | Cursor Agent | ✅ Supported |
| Agent | Gemini CLI (Google) | ✅ Supported |
| Agent | Qoder CLI | ✅ Supported |
| Agent | Crush / OpenCode | 🔜 Planned |
| Agent | OpenCode | ✅ Supported |
| Agent | Crush | 🔜 Planned |
| Agent | Goose (Block) | 🔜 Planned |
| Agent | Aider | 🔜 Planned |
| Agent | Kimi Code (Moonshot) | 🔭 Exploring |
@@ -81,7 +82,8 @@ cc-connect bridges AI coding assistants running on your dev machine to the messa
- **Codex**: [Codex CLI](https://github.com/openai/codex) installed (`npm install -g @openai/codex`), OR
- **Cursor Agent**: [Cursor Agent CLI](https://docs.cursor.com/agent) installed (`agent --version` to verify), OR
- **Gemini CLI**: [Gemini CLI](https://github.com/google-gemini/gemini-cli) installed (`npm install -g @google/gemini-cli`), OR
- **Qoder CLI**: [Qoder CLI](https://qoder.com) installed (`curl -fsSL https://qoder.com/install | bash`)
- **Qoder CLI**: [Qoder CLI](https://qoder.com) installed (`curl -fsSL https://qoder.com/install | bash`), OR
- **OpenCode**: [OpenCode](https://github.com/opencode-ai/opencode) installed (`opencode --version` to verify)
### Install & Configure via AI Agent (Recommended)
@@ -274,6 +276,13 @@ All agents support permission modes switchable at runtime via `/mode`.
| **Default** | `default` | Standard permissions; prompt for approval. |
| **YOLO** | `yolo` | Skip all permission checks, auto-approve. |
**OpenCode** modes:
| Mode | Config Value | Behavior |
|------|-------------|----------|
| **Default** | `default` | Standard mode. |
| **YOLO** | `yolo` | Auto-approve all tool calls. |
```toml
# Claude Code
[projects.agent.options]
@@ -296,6 +305,10 @@ mode = "default"
# Qoder CLI
[projects.agent.options]
mode = "default"
# OpenCode
[projects.agent.options]
mode = "default"
```
Switch mode at runtime from the chat:
@@ -375,6 +388,7 @@ Adding, removing, and switching providers all persist to `config.toml` automatic
| Claude Code | `ANTHROPIC_API_KEY` | `ANTHROPIC_BASE_URL` |
| Codex | `OPENAI_API_KEY` | `OPENAI_BASE_URL` |
| Gemini CLI | `GEMINI_API_KEY` | — (use `env` map) |
| OpenCode | `ANTHROPIC_API_KEY` | — (use `env` map) |
The `env` map in provider config lets you set arbitrary environment variables for any setup (Bedrock, Vertex, Azure, custom proxies, etc.).
@@ -473,6 +487,7 @@ Claude Code will automatically translate your request into a `cc-connect cron ad
| Cursor | `.cursorrules` |
| Qoder CLI | `AGENTS.md` (project), `~/.qoder/AGENTS.md` (global) |
| Gemini CLI | `GEMINI.md` |
| OpenCode | `OPENCODE.md` |
**Content to add:**
@@ -649,7 +664,8 @@ cc-connect/
│ ├── codex/ # OpenAI Codex CLI (exec --json)
│ ├── cursor/ # Cursor Agent CLI (--print stream-json)
│ ├── qoder/ # Qoder CLI (-p -f stream-json)
│ └── gemini/ # Gemini CLI (-p --output-format stream-json)
│ ├── gemini/ # Gemini CLI (-p --output-format stream-json)
│ └── opencode/ # OpenCode (run --format json)
├── docs/ # Platform setup guides
├── config.example.toml # Config template
├── INSTALL.md # AI-agent-friendly install guide
+22 -6
View File
@@ -20,15 +20,15 @@ cc-connect 把运行在你开发机上的 AI 编程助手桥接到你日常使
└────────────┘
┌─────┼─────┐
▼ ▼ ▼
Claude Gemini Codex ...5 个 Agent
Code CLI
Claude Gemini Codex ...6 个 Agent
Code CLI OpenCode
```
### 核心亮点
> 是时候卸载 OpenClaw 了 — cc-connect 让你同时拥有最强的那几个 AI 编程助手,而不只是一个。
- **5 大 AI Agent** — Claude Code、Codex、Cursor Agent、Qoder CLI、Gemini CLI,按需选用,也可以同时使用
- **6 大 AI Agent** — Claude Code、Codex、Cursor Agent、Qoder CLI、Gemini CLI、OpenCode,按需选用,也可以同时使用
- **8 大聊天平台** — 飞书、钉钉、Slack、Telegram、Discord、企业微信、LINE、QQ,大部分无需公网 IP
- **聊天即控制** — 切换模型 `/model`、切换权限 `/mode`、管理会话,全部通过斜杠命令完成
- **Agent 记忆** — 在聊天中直接读写 Agent 指令文件 `/memory`,无需回到终端
@@ -54,7 +54,8 @@ cc-connect 把运行在你开发机上的 AI 编程助手桥接到你日常使
| Agent | Cursor Agent | ✅ 已支持 |
| Agent | Gemini CLI (Google) | ✅ 已支持 |
| Agent | Qoder CLI | ✅ 已支持 |
| Agent | Crush / OpenCode | 🔜 计划中 |
| Agent | OpenCode | ✅ 已支持 |
| Agent | Crush | 🔜 计划中 |
| Agent | Goose (Block) | 🔜 计划中 |
| Agent | Aider | 🔜 计划中 |
| Agent | Kimi Code (月之暗面) | 🔭 探索中 |
@@ -86,7 +87,8 @@ cc-connect 把运行在你开发机上的 AI 编程助手桥接到你日常使
- **Codex**: [Codex CLI](https://github.com/openai/codex) 已安装(`npm install -g @openai/codex`),或
- **Cursor Agent**: [Cursor Agent CLI](https://docs.cursor.com/agent) 已安装(`agent --version` 验证),或
- **Gemini CLI**: [Gemini CLI](https://github.com/google-gemini/gemini-cli) 已安装(`npm install -g @google/gemini-cli`),或
- **Qoder CLI**: [Qoder CLI](https://qoder.com) 已安装(`curl -fsSL https://qoder.com/install | bash`)
- **Qoder CLI**: [Qoder CLI](https://qoder.com) 已安装(`curl -fsSL https://qoder.com/install | bash`),或
- **OpenCode**: [OpenCode](https://github.com/opencode-ai/opencode) 已安装(`opencode --version` 验证)
### 通过 AI Agent 安装配置(推荐)
@@ -278,6 +280,13 @@ allow_from = "*" # 允许的 QQ 号,如 "12345,67890","*" 表示所有
| **标准权限** | `default` | 标准权限,每次工具调用需确认。 |
| **YOLO 模式** | `yolo` | 跳过所有权限检查,自动批准。 |
**OpenCode** 模式:
| 模式 | 配置值 | 行为 |
|------|--------|------|
| **默认** | `default` | 标准模式。 |
| **全自动** | `yolo` | 自动批准所有工具调用。 |
```toml
# Claude Code
[projects.agent.options]
@@ -300,6 +309,10 @@ mode = "default"
# Qoder CLI
[projects.agent.options]
mode = "default"
# OpenCode
[projects.agent.options]
mode = "default"
```
在聊天中切换模式:
@@ -379,6 +392,7 @@ cc-connect provider import --db-path ~/.cc-switch/cc-switch.db # 指定数据
| Claude Code | `ANTHROPIC_API_KEY` | `ANTHROPIC_BASE_URL` |
| Codex | `OPENAI_API_KEY` | `OPENAI_BASE_URL` |
| Gemini CLI | `GEMINI_API_KEY` | —(使用 `env` 字段)|
| OpenCode | `ANTHROPIC_API_KEY` | —(使用 `env` 字段)|
Provider 配置中的 `env` 字段支持设置任意环境变量,可用于 Bedrock、Vertex、Azure、自定义代理等各种场景。
@@ -477,6 +491,7 @@ Claude Code 会通过 `--append-system-prompt` 自动将你的请求转为 `cc-c
| Cursor | `.cursorrules` |
| Qoder CLI | `AGENTS.md`(项目级)、`~/.qoder/AGENTS.md`(全局) |
| Gemini CLI | `GEMINI.md` |
| OpenCode | `OPENCODE.md` |
**需要添加的内容:**
@@ -653,7 +668,8 @@ cc-connect/
│ ├── codex/ # OpenAI Codex CLI(exec --json)
│ ├── cursor/ # Cursor Agent CLI(--print stream-json)
│ ├── qoder/ # Qoder CLI(-p -f stream-json)
│ └── gemini/ # Gemini CLI(-p --output-format stream-json)
│ ├── gemini/ # Gemini CLI(-p --output-format stream-json)
│ └── opencode/ # OpenCode(run --format json)
├── docs/ # 平台接入指南
├── config.example.toml # 配置模板
├── INSTALL.md # AI agent 友好的安装配置指南
+257
View File
@@ -0,0 +1,257 @@
package opencode
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"github.com/chenhg5/cc-connect/core"
)
func init() {
core.RegisterAgent("opencode", New)
}
// Agent drives the OpenCode CLI in headless mode using `opencode run --format json`.
//
// Modes:
// - "default": standard mode
// - "yolo": auto mode (opencode run is auto by default in non-interactive mode)
type Agent struct {
workDir string
model string
mode string
cmd string // CLI binary name, default "opencode"
providers []core.ProviderConfig
activeIdx int
sessionEnv []string
mu sync.Mutex
}
func New(opts map[string]any) (core.Agent, error) {
workDir, _ := opts["work_dir"].(string)
if workDir == "" {
workDir = "."
}
model, _ := opts["model"].(string)
mode, _ := opts["mode"].(string)
mode = normalizeMode(mode)
cmd, _ := opts["cmd"].(string)
if cmd == "" {
cmd = "opencode"
}
if _, err := exec.LookPath(cmd); err != nil {
return nil, fmt.Errorf("opencode: %q CLI not found in PATH, install from: https://github.com/opencode-ai/opencode", cmd)
}
return &Agent{
workDir: workDir,
model: model,
mode: mode,
cmd: cmd,
activeIdx: -1,
}, nil
}
func normalizeMode(raw string) string {
switch strings.ToLower(strings.TrimSpace(raw)) {
case "yolo", "auto", "force", "bypasspermissions":
return "yolo"
default:
return "default"
}
}
func (a *Agent) Name() string { return "opencode" }
func (a *Agent) SetModel(model string) {
a.mu.Lock()
defer a.mu.Unlock()
a.model = model
slog.Info("opencode: model changed", "model", model)
}
func (a *Agent) GetModel() string {
a.mu.Lock()
defer a.mu.Unlock()
return a.model
}
func (a *Agent) AvailableModels(_ context.Context) []core.ModelOption {
return []core.ModelOption{
{Name: "anthropic/claude-sonnet-4-20250514", Desc: "Claude Sonnet 4 (default)"},
{Name: "anthropic/claude-opus-4-20250514", Desc: "Claude Opus 4"},
{Name: "openai/gpt-4o", Desc: "GPT-4o"},
{Name: "openai/o3", Desc: "OpenAI o3"},
}
}
func (a *Agent) SetSessionEnv(env []string) {
a.mu.Lock()
defer a.mu.Unlock()
a.sessionEnv = env
}
func (a *Agent) StartSession(ctx context.Context, sessionID string) (core.AgentSession, error) {
a.mu.Lock()
model := a.model
mode := a.mode
cmd := a.cmd
workDir := a.workDir
extraEnv := a.providerEnvLocked()
extraEnv = append(extraEnv, a.sessionEnv...)
if a.activeIdx >= 0 && a.activeIdx < len(a.providers) {
if m := a.providers[a.activeIdx].Model; m != "" {
model = m
}
}
a.mu.Unlock()
return newOpencodeSession(ctx, cmd, workDir, model, mode, sessionID, extraEnv)
}
// ListSessions runs `opencode session list` and parses the JSON output.
func (a *Agent) ListSessions(_ context.Context) ([]core.AgentSessionInfo, error) {
return listOpencodeSessions(a.cmd, a.workDir)
}
func (a *Agent) Stop() error { return nil }
// -- ModeSwitcher --
func (a *Agent) SetMode(mode string) {
a.mu.Lock()
defer a.mu.Unlock()
a.mode = normalizeMode(mode)
slog.Info("opencode: mode changed", "mode", a.mode)
}
func (a *Agent) GetMode() string {
a.mu.Lock()
defer a.mu.Unlock()
return a.mode
}
func (a *Agent) PermissionModes() []core.PermissionModeInfo {
return []core.PermissionModeInfo{
{Key: "default", Name: "Default", NameZh: "默认", Desc: "Standard mode", DescZh: "标准模式"},
{Key: "yolo", Name: "YOLO", NameZh: "全自动", Desc: "Auto-approve all tool calls", DescZh: "自动批准所有工具调用"},
}
}
// -- ContextCompressor --
func (a *Agent) CompressCommand() string { return "/compact" }
// -- MemoryFileProvider --
func (a *Agent) ProjectMemoryFile() string {
absDir, err := filepath.Abs(a.workDir)
if err != nil {
absDir = a.workDir
}
return filepath.Join(absDir, "OPENCODE.md")
}
func (a *Agent) GlobalMemoryFile() string {
homeDir, err := os.UserHomeDir()
if err != nil {
return ""
}
return filepath.Join(homeDir, ".opencode", "OPENCODE.md")
}
// -- ProviderSwitcher --
func (a *Agent) SetProviders(providers []core.ProviderConfig) {
a.mu.Lock()
defer a.mu.Unlock()
a.providers = providers
}
func (a *Agent) SetActiveProvider(name string) bool {
a.mu.Lock()
defer a.mu.Unlock()
for i, p := range a.providers {
if p.Name == name {
a.activeIdx = i
slog.Info("opencode: provider switched", "provider", name)
return true
}
}
return false
}
func (a *Agent) GetActiveProvider() *core.ProviderConfig {
a.mu.Lock()
defer a.mu.Unlock()
if a.activeIdx < 0 || a.activeIdx >= len(a.providers) {
return nil
}
p := a.providers[a.activeIdx]
return &p
}
func (a *Agent) ListProviders() []core.ProviderConfig {
a.mu.Lock()
defer a.mu.Unlock()
result := make([]core.ProviderConfig, len(a.providers))
copy(result, a.providers)
return result
}
func (a *Agent) providerEnvLocked() []string {
if a.activeIdx < 0 || a.activeIdx >= len(a.providers) {
return nil
}
p := a.providers[a.activeIdx]
var env []string
if p.APIKey != "" {
env = append(env, "ANTHROPIC_API_KEY="+p.APIKey)
}
for k, v := range p.Env {
env = append(env, k+"="+v)
}
return env
}
// -- Session listing --
// opencodeSessionEntry represents a session from `opencode session list` output.
type opencodeSessionEntry struct {
ID string `json:"id"`
Title string `json:"title"`
Updated string `json:"updated"`
}
func listOpencodeSessions(cmd, workDir string) ([]core.AgentSessionInfo, error) {
c := exec.Command(cmd, "session", "list", "--format", "json")
c.Dir = workDir
out, err := c.Output()
if err != nil {
return nil, fmt.Errorf("opencode: session list: %w", err)
}
var entries []opencodeSessionEntry
if err := json.Unmarshal(out, &entries); err != nil {
return nil, fmt.Errorf("opencode: parse session list: %w", err)
}
var sessions []core.AgentSessionInfo
for _, e := range entries {
sessions = append(sessions, core.AgentSessionInfo{
ID: e.ID,
Summary: e.Title,
})
}
return sessions, nil
}
+301
View File
@@ -0,0 +1,301 @@
package opencode
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log/slog"
"os"
"os/exec"
"sync"
"sync/atomic"
"unicode/utf8"
"github.com/chenhg5/cc-connect/core"
)
// opencodeSession manages multi-turn conversations with the OpenCode CLI.
// Each Send() launches a new `opencode run --format json` process
// with --session for conversation continuity.
type opencodeSession struct {
cmd string
workDir string
model string
mode string
extraEnv []string
events chan core.Event
chatID atomic.Value // stores string — OpenCode session ID
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
alive atomic.Bool
}
func newOpencodeSession(ctx context.Context, cmd, workDir, model, mode, resumeID string, extraEnv []string) (*opencodeSession, error) {
sessionCtx, cancel := context.WithCancel(ctx)
s := &opencodeSession{
cmd: cmd,
workDir: workDir,
model: model,
mode: mode,
extraEnv: extraEnv,
events: make(chan core.Event, 64),
ctx: sessionCtx,
cancel: cancel,
}
s.alive.Store(true)
if resumeID != "" {
s.chatID.Store(resumeID)
}
return s, nil
}
func (s *opencodeSession) Send(prompt string, images []core.ImageAttachment) error {
if !s.alive.Load() {
return fmt.Errorf("session is closed")
}
chatID := s.CurrentSessionID()
isResume := chatID != ""
args := []string{"run", "--format", "json"}
if isResume {
args = append(args, "--session", chatID)
}
if s.model != "" {
args = append(args, "--model", s.model)
}
if s.workDir != "" {
args = append(args, "--dir", s.workDir)
}
// Enable thinking blocks
args = append(args, "--thinking")
// Append prompt as positional arg
args = append(args, prompt)
slog.Debug("opencodeSession: launching", "resume", isResume, "args", args)
cmd := exec.CommandContext(s.ctx, s.cmd, args...)
cmd.Dir = s.workDir
env := os.Environ()
if len(s.extraEnv) > 0 {
env = core.MergeEnv(env, s.extraEnv)
}
cmd.Env = env
stdout, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("opencodeSession: stdout pipe: %w", err)
}
var stderrBuf bytes.Buffer
cmd.Stderr = &stderrBuf
if err := cmd.Start(); err != nil {
return fmt.Errorf("opencodeSession: start: %w", err)
}
s.wg.Add(1)
go s.readLoop(cmd, stdout, &stderrBuf)
return nil
}
func (s *opencodeSession) readLoop(cmd *exec.Cmd, stdout io.ReadCloser, stderrBuf *bytes.Buffer) {
defer s.wg.Done()
defer func() {
if err := cmd.Wait(); err != nil {
stderrMsg := stderrBuf.String()
if stderrMsg != "" {
slog.Error("opencodeSession: process failed", "error", err, "stderr", stderrMsg)
s.events <- core.Event{Type: core.EventError, Error: fmt.Errorf("%s", stderrMsg)}
}
}
}()
scanner := bufio.NewScanner(stdout)
scanner.Buffer(make([]byte, 1024*1024), 1024*1024)
for scanner.Scan() {
line := scanner.Text()
if line == "" {
continue
}
var raw map[string]any
if err := json.Unmarshal([]byte(line), &raw); err != nil {
slog.Debug("opencodeSession: non-JSON line", "line", line)
continue
}
s.handleEvent(raw)
}
if err := scanner.Err(); err != nil {
slog.Error("opencodeSession: scanner error", "error", err)
s.events <- core.Event{Type: core.EventError, Error: fmt.Errorf("read stdout: %w", err)}
}
}
// OpenCode NDJSON event structure:
//
// { "type": "text|tool_use|reasoning|step_start|step_finish",
// "part": { "type": "text|tool|reasoning|step-start|step-finish", ... } }
func (s *opencodeSession) handleEvent(raw map[string]any) {
eventType, _ := raw["type"].(string)
switch eventType {
case "text":
s.handleText(raw)
case "tool_use":
s.handleToolUse(raw)
case "reasoning":
s.handleReasoning(raw)
case "step_start":
s.handleStepStart(raw)
case "step_finish":
s.handleStepFinish(raw)
default:
slog.Debug("opencodeSession: unhandled event", "type", eventType)
}
}
func (s *opencodeSession) handleText(raw map[string]any) {
part, _ := raw["part"].(map[string]any)
if part == nil {
return
}
text, _ := part["text"].(string)
if text != "" {
s.events <- core.Event{
Type: core.EventText,
Content: text,
}
}
}
func (s *opencodeSession) handleToolUse(raw map[string]any) {
part, _ := raw["part"].(map[string]any)
if part == nil {
return
}
toolName, _ := part["tool"].(string)
// Check state for status
state, _ := part["state"].(map[string]any)
status := ""
if state != nil {
status, _ = state["status"].(string)
}
if status == "completed" {
// Tool result
output := ""
if state != nil {
output, _ = state["output"].(string)
}
s.events <- core.Event{
Type: core.EventToolResult,
ToolName: toolName,
Content: truncate(output, 500),
}
} else {
// Tool use (running or starting)
input := ""
if state != nil {
inputVal, _ := state["input"].(string)
if inputVal != "" {
input = inputVal
} else {
// Try to marshal input as JSON summary
if inputMap, ok := state["input"].(map[string]any); ok {
b, _ := json.Marshal(inputMap)
input = truncate(string(b), 200)
}
}
}
s.events <- core.Event{
Type: core.EventToolUse,
ToolName: toolName,
ToolInput: input,
}
}
}
func (s *opencodeSession) handleReasoning(raw map[string]any) {
part, _ := raw["part"].(map[string]any)
if part == nil {
return
}
text, _ := part["text"].(string)
if text != "" {
s.events <- core.Event{
Type: core.EventThinking,
Content: text,
}
}
}
func (s *opencodeSession) handleStepStart(raw map[string]any) {
part, _ := raw["part"].(map[string]any)
if part == nil {
return
}
sessionID, _ := part["sessionID"].(string)
if sessionID != "" {
s.chatID.Store(sessionID)
slog.Debug("opencodeSession: session started", "session_id", sessionID)
}
}
func (s *opencodeSession) handleStepFinish(_ map[string]any) {
sid := s.CurrentSessionID()
s.events <- core.Event{
Type: core.EventResult,
SessionID: sid,
Done: true,
}
}
// RespondPermission is a no-op — OpenCode handles permissions internally.
func (s *opencodeSession) RespondPermission(_ string, _ core.PermissionResult) error {
return nil
}
func (s *opencodeSession) Events() <-chan core.Event {
return s.events
}
func (s *opencodeSession) CurrentSessionID() string {
v, _ := s.chatID.Load().(string)
return v
}
func (s *opencodeSession) Alive() bool {
return s.alive.Load()
}
func (s *opencodeSession) Close() error {
s.alive.Store(false)
s.cancel()
s.wg.Wait()
close(s.events)
return nil
}
func truncate(s string, maxRunes int) string {
if utf8.RuneCountInString(s) <= maxRunes {
return s
}
return string([]rune(s)[:maxRunes]) + "..."
}
+1
View File
@@ -22,6 +22,7 @@ import (
_ "github.com/chenhg5/cc-connect/agent/codex"
_ "github.com/chenhg5/cc-connect/agent/cursor"
_ "github.com/chenhg5/cc-connect/agent/gemini"
_ "github.com/chenhg5/cc-connect/agent/opencode"
_ "github.com/chenhg5/cc-connect/agent/qoder"
_ "github.com/chenhg5/cc-connect/platform/dingtalk"
+32 -1
View File
@@ -509,7 +509,38 @@ app_secret = "your-feishu-app-secret"
# token = "your-telegram-bot-token"
# =============================================================================
# Project 6: Another example / 项目 6:更多示例 (uncomment to enable / 取消注释以启用)
# Project 6: Using OpenCode / 项目 6:使用 OpenCode (uncomment to enable / 取消注释以启用)
# =============================================================================
# Requires: OpenCode CLI — https://github.com/opencode-ai/opencode
# 需要安装:OpenCode CLI
# Uses `opencode run --format json` under the hood.
# 底层使用 `opencode run --format json` 命令。
# [[projects]]
# name = "my-opencode-project"
#
# [projects.agent]
# type = "opencode"
#
# [projects.agent.options]
# work_dir = "/path/to/project"
# mode = "default" # "default" | "yolo"
#
# Mode options / 模式说明:
# - "default": Standard mode / 标准模式
# - "yolo": Auto-approve all tool calls / 自动批准所有工具调用
#
# Optional: specify a model (provider/model format) / 可选:指定模型
# model = "anthropic/claude-sonnet-4-20250514"
#
# [[projects.platforms]]
# type = "telegram"
#
# [projects.platforms.options]
# token = "your-telegram-bot-token"
# =============================================================================
# Project 7: Another example / 项目 7:更多示例 (uncomment to enable / 取消注释以启用)
# =============================================================================
# [[projects]]