9.7 KiB
CC-Connect Development Guide
Project Overview
CC-Connect is a bridge that connects AI coding agents (Claude Code, Codex, Gemini CLI, Cursor, etc.) with messaging platforms (Feishu/Lark, Telegram, Discord, Slack, DingTalk, WeChat Work, QQ, LINE). Users interact with their coding agent through their preferred messaging app.
Architecture
┌─────────────────────────────────────────────────┐
│ cmd/cc-connect │ ← entry point, CLI, daemon
├─────────────────────────────────────────────────┤
│ config/ │ ← TOML config parsing
├─────────────────────────────────────────────────┤
│ core/ │ ← engine, interfaces, i18n,
│ │ cards, sessions, registry
├──────────────────────┬──────────────────────────┤
│ agent/ │ platform/ │
│ ├── claudecode/ │ ├── feishu/ │
│ ├── codex/ │ ├── telegram/ │
│ ├── cursor/ │ ├── discord/ │
│ ├── gemini/ │ ├── slack/ │
│ ├── iflow/ │ ├── dingtalk/ │
│ ├── opencode/ │ ├── wecom/ │
│ ├── acp/ │ ├── qq/ │
│ └── qoder/ │ ├── qqbot/ │
│ │ ├── line/ │
│ │ └── weibo/ │
├──────────────────────┴──────────────────────────┤
│ daemon/ │ ← systemd/launchd service
└─────────────────────────────────────────────────┘
Key Design Principles
core/ is the nucleus. It defines all interfaces (Platform, Agent, AgentSession, etc.) and contains the Engine that orchestrates message flow. The core package must never import from agent/ or platform/.
Plugin architecture via registries. Agents and platforms register themselves through core.RegisterAgent() and core.RegisterPlatform() in their init() functions. The engine creates instances via core.CreateAgent() / core.CreatePlatform() using string names from config.
Dependency direction:
cmd/ → config/, core/, agent/*, platform/*
agent/* → core/ (never other agents or platforms)
platform/* → core/ (never other platforms or agents)
core/ → stdlib only (never agent/ or platform/)
Core Interfaces
Platform— messaging platform adapter (Start, Reply, Send, Stop)Agent— AI coding agent adapter (StartSession, ListSessions, Stop)AgentSession— a running bidirectional session (Send, RespondPermission, Events)Engine— the central orchestrator that routes messages between platforms and agents
Optional capability interfaces (implement only when needed):
CardSender— rich card messagesInlineButtonSender— inline keyboard buttonsProviderSwitcher— multi-model switchingDoctorChecker— agent-specific health checksAgentDoctorInfo— CLI binary metadata for diagnostics
Development Rules
1. No Hardcoding Platform or Agent Names in Core
The core/ package must remain agnostic. Never write if p.Name() == "feishu" or CreateAgent("claudecode", ...) in core. Use interfaces and capability checks instead:
// BAD — hardcodes platform knowledge in core
if p.Name() == "feishu" && supportsCards(p) {
// GOOD — capability-based check
if supportsCards(p) {
// BAD — hardcodes agent type
agent, _ := CreateAgent("claudecode", opts)
// GOOD — derives from current agent
agent, _ := CreateAgent(e.agent.Name(), opts)
2. Prefer Interfaces Over Type Switches
When behavior differs across platforms/agents, define an optional interface in core and let implementations opt in:
// In core/
type AgentDoctorInfo interface {
CLIBinaryName() string
CLIDisplayName() string
}
// In agent/claudecode/
func (a *Agent) CLIBinaryName() string { return "claude" }
func (a *Agent) CLIDisplayName() string { return "Claude" }
// In core/ — query via interface, fallback gracefully
if info, ok := agent.(AgentDoctorInfo); ok {
bin = info.CLIBinaryName()
}
3. Configuration Over Code
- Features that may vary per deployment should be configurable in
config.toml - Use
map[string]anyoptions for agent/platform factories to stay flexible - Add new config fields with sensible defaults so existing configs don't break
4. High Cohesion, Low Coupling
- Each
agent/X/package is self-contained: it handles process lifecycle, output parsing, and session management for agent X - Each
platform/X/package is self-contained: it handles API connection, message receiving/sending, and card rendering for platform X - Cross-cutting concerns (i18n, cards, streaming, rate limiting) live in
core/
5. Error Handling
- Always wrap errors with context:
fmt.Errorf("feishu: reply card: %w", err) - Never silently swallow errors; at minimum log them with
slog.Error/slog.Warn - Use
slog(structured logging) consistently; neverlog.Printforfmt.Printffor runtime logs - Redact tokens/secrets in error messages using
core.RedactToken()
6. Concurrency Safety
- Agent sessions are accessed from multiple goroutines; protect shared state with
sync.Mutexoratomictypes - Use
context.Contextfor cancellation propagation - Channels should have clear ownership; document who closes them
- Prefer
sync.Oncefor one-time teardown (pendingPermission.resolve())
7. i18n
All user-facing strings must go through core/i18n.go:
- Define a
MsgKeyconstant - Add translations for all supported languages (EN, ZH, ZH-TW, JA, ES)
- Use
e.i18n.T(MsgKey)ore.i18n.Tf(MsgKey, args...)
Code Style
- Follow standard Go conventions (
gofmt,go vet) - Use
strings.EqualFoldfor case-insensitive comparisons - Avoid
init()for anything other than platform/agent registration - Keep functions focused; extract helpers when a function exceeds ~80 lines
- Naming:
New()for constructors,Get/Setfor accessors, avoid stuttering (feishu.FeishuPlatform→feishu.Platform)
Testing
Requirements
- All new features must include unit tests
- All bug fixes should include a regression test
- Tests must pass before committing:
go test ./...
Running Tests
# Full test suite
go test ./...
# Specific package
go test ./core/ -v
# Run specific test
go test ./core/ -run TestHandlePendingPermission -v
# With race detector (CI)
go test -race ./...
Test Patterns
- Use stub types for
PlatformandAgentin core tests (seecore/engine_test.go) - Test card rendering by inspecting the returned
*Cardstruct, not JSON - For agent session tests, simulate event streams via channels
Selective Compilation
Each agent and platform is imported via a separate plugin_*.go file with a
build tag (e.g. //go:build !no_feishu). By default all agents and
platforms are compiled in.
Include only specific agents/platforms
# Only Claude Code agent + Feishu and Telegram platforms
make build AGENTS=claudecode PLATFORMS_INCLUDE=feishu,telegram
# Multiple agents
make build AGENTS=claudecode,codex PLATFORMS_INCLUDE=feishu,telegram,discord
Exclude specific agents/platforms
# Exclude some platforms you don't need
make build EXCLUDE=discord,dingtalk,qq,qqbot,line
Direct build tag usage (without Make)
go build -tags 'no_discord no_dingtalk no_qq no_qqbot no_line' ./cmd/cc-connect
Available tags: no_acp, no_claudecode, no_codex, no_copilot, no_cursor, no_gemini,
no_iflow, no_opencode, no_qoder, no_feishu, no_telegram,
no_discord, no_slack, no_dingtalk, no_wecom, no_weixin, no_qq, no_qqbot,
no_line, no_weibo.
Pre-Commit Checklist
- Build passes:
go build ./... - Tests pass:
go test ./... - No new hardcoded platform/agent names in core: grep for platform names in
core/*.go - i18n complete: all new user-facing strings have translations for all languages
- No secrets in code: no API keys, tokens, or credentials in source files
Adding a New Platform
- Create
platform/newplatform/newplatform.go - Implement
core.Platforminterface (and optional interfaces as needed) - Register in
init():core.RegisterPlatform("newplatform", factory) - Create
cmd/cc-connect/plugin_platform_newplatform.gowith//go:build !no_newplatformtag - Add
newplatformtoALL_PLATFORMSinMakefile - Add config example in
config.example.toml - Add unit tests
Adding a New Agent
- Create
agent/newagent/newagent.go - Implement
core.Agentandcore.AgentSessioninterfaces - Register in
init():core.RegisterAgent("newagent", factory) - Create
cmd/cc-connect/plugin_agent_newagent.gowith//go:build !no_newagenttag - Add
newagenttoALL_AGENTSinMakefile - Optionally implement
AgentDoctorInfoforcc-connect doctorsupport - Add config example in
config.example.toml - Add unit tests