Welcome to the YAOCC development guide! This document covers how to build, test, and contribute to the project.
- Go: Version 1.23 or higher.
- Git: For version control.
cmd/: Application entry points.yaocc/: The CLI tool.yaocc-server/: The backend server (if applicable).
pkg/: Library code.agent/: Core agent logic.config/: Configuration handling.llm/: LLM client implementation.skills/: Skill loading and management.version/: Build-time version info (injected via ldflags).messaging/: Messaging provider implementations.telegram/: Telegram bot client.
skills/: Default skill definitions and implementations.
To build the project, run:
# Build CLI
go build -o build/yaocc.exe ./cmd/yaocc
# Build Server
go build -o build/yaocc-server.exe ./cmd/yaocc-serverYAOCC uses Go ldflags to inject version information at build time. The pkg/version package exposes three variables:
| Variable | Description | Default |
|---|---|---|
Version |
Semantic version tag (e.g. v1.2.3) |
dev |
Commit |
Short Git commit SHA | unknown |
BuildDate |
ISO 8601 build timestamp | unknown |
# CLI
yaocc version
# or
yaocc --version
# Server
yaocc-server --version$VERSION = "v1.0.0"
$COMMIT = (git rev-parse --short HEAD)
$DATE = (Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ")
$LDFLAGS = "-X github.com/dev-dhg/yaocc/pkg/version.Version=$VERSION -X github.com/dev-dhg/yaocc/pkg/version.Commit=$COMMIT -X github.com/dev-dhg/yaocc/pkg/version.BuildDate=$DATE"
go build -ldflags $LDFLAGS -o build/yaocc.exe ./cmd/yaocc
go build -ldflags $LDFLAGS -o build/yaocc-server.exe ./cmd/yaocc-serverVERSION="v1.0.0"
COMMIT=$(git rev-parse --short HEAD)
DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS="-X github.com/dev-dhg/yaocc/pkg/version.Version=${VERSION} -X github.com/dev-dhg/yaocc/pkg/version.Commit=${COMMIT} -X github.com/dev-dhg/yaocc/pkg/version.BuildDate=${DATE}"
go build -ldflags "$LDFLAGS" -o build/yaocc ./cmd/yaocc
go build -ldflags "$LDFLAGS" -o build/yaocc-server ./cmd/yaocc-serverThe Dockerfile accepts YAOCC_VERSION, YAOCC_COMMIT, and YAOCC_BUILD_DATE as build args:
docker build \
--build-arg YAOCC_VERSION="v1.0.0" \
--build-arg YAOCC_COMMIT="$(git rev-parse --short HEAD)" \
--build-arg YAOCC_BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--build-arg YAOCC_BASE_IMAGE="node:24-alpine" \
--build-arg YAOCC_DOCKER_APK_PACKAGES="git nano curl unzip python3" \
--build-arg YAOCC_USER="node" \
-t yaocc:v1.0.0 .Or with Docker Compose (PowerShell):
$env:YAOCC_VERSION="v1.0.0"
$env:YAOCC_COMMIT=(git rev-parse --short HEAD)
$env:YAOCC_BUILD_DATE=(Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ")
docker compose build --build-arg YAOCC_VERSION=$env:YAOCC_VERSION --build-arg YAOCC_COMMIT=$env:YAOCC_COMMIT --build-arg YAOCC_BUILD_DATE=$env:YAOCC_BUILD_DATENote: If you don't pass version args, the binaries default to
dev/unknown. This is fine for local development.
Both GitLab CI and GitHub Actions automatically inject version info:
- Tagged builds (
v*): Version is set to the tag name (e.g.v1.2.3) - Branch builds: Version is set to
dev-<short-sha>
YAOCC uses a split configuration architecture:
config.json: General settings (models, messaging, server, storage, websearch, session, etc.).cron.json: Cron job definitions (independently watched, changes only reload the scheduler).skills_register.json: Registered custom skills (independently watched, no agent restart needed).
Copy the example files to get started:
config.json.example→config.jsoncron.json.example→cron.jsonskills_register.json.example→skills_register.json
Environment variables can be used in config files using ${VAR_NAME} syntax.
If you have an existing config.json with cron or skills.registered fields, YAOCC will automatically migrate them to cron.json and skills_register.json on first startup and clean up the old fields from config.json.
Run unit tests with:
go test ./...To run the manual LLM integration test:
go run test/manual_llm.goYAOCC supports multiple messaging providers via a unified interface.
-
Create a new package/directory: Under
pkg/messaging/<provider_name>. -
Implement the Provider Interface: Your struct must implement the
Providerinterface defined inpkg/messaging/provider.go.type Provider interface { Name() string Start() SendMessage(targetID string, message string) error SendImage(targetID string, url string, caption string) error SendAudio(targetID string, url string, caption string) error SendVideo(targetID string, url string, caption string) error SendDocument(targetID string, url string, caption string) error }
-
Register the Provider: In
cmd/yaocc-server/main.go, initialize your provider based on configuration and add it to theprovidersmap before creating theScheduler.// Example in main.go if msgCfg.Provider == "discord" { discordClient := discord.NewClient(...) go discordClient.Start() providers["discord"] = discordClient }
-
Configuration: Update
pkg/config/config.goto include any specific configuration fields your provider needs inMessagingProviderConfigor a sub-struct.
The Agent can be extended to support rich media interactions. Currently, the Agent supports sending Images, Audio, Video, and Documents through specific text protocols that the messaging clients intercept.
To send media files, the Agent (or any Skill) should output a message starting with one of the following prefixes:
| Type | Prefix | Example |
|---|---|---|
| Image | #IMAGE#: |
#IMAGE#:https://example.com/image.png or #IMAGE#:./local_file.png |
| Audio | #AUDIO#: |
#AUDIO#:https://example.com/audio.mp3 |
| Video | #VIDEO#: |
#VIDEO#:https://example.com/video.mp4 |
| Document | #DOC#: |
#DOC#:./report.pdf |
Base64 Support:
If a tool outputs raw base64 data for an image, use #BASE64_IMAGE#:<data>. The Message Client will automatically convert this to a temporary file and send it as an image.
Each messaging provider can inject specific formatting instructions into the Agent's system prompt via SystemPromptInstruction().
Telegram Formatting (HTML):
Current Telegram implementation uses parse_mode: HTML. To ensure rich formatting works correctly, the Agent is instructed to:
- Use standard tags like
<b>,<i>,<u>,<s>,<a>. - Use specialized tags like
<tg-spoiler>,<tg-emoji>, and<tg-time>. - Use
<b>for headlines (Markdown#headers are ignored). - Use
<,>, and&for escaping literal characters. - Represent tables using ASCII formatting inside
<pre>or<blockquote>blocks. - Use horizontal separators (e.g.,
———) for visual spacing.
To add new capabilities:
- Define a Protocol: Choose a unique prefix (e.g.,
#LOCATION#:). - Update Messaging Interface: Add a method to
pkg/messaging/provider.go(e.g.,SendLocation). - Update Clients: Implement the method in all providers (e.g.,
pkg/messaging/telegram/client.go). - Update System Prompt: Add instructions to
pkg/agent/agent.goso the LLM knows about the new capability.
YAOCC supports multiple web search providers.
-
Create a new file: Under
pkg/websearch/<provider>.go. -
Implement the Provider Interface: Your struct must implement the
Providerinterface defined inpkg/websearch/provider.go.type Provider interface { Search(query string) ([]SearchResult, error) }
-
Register the Provider: In
pkg/websearch/provider.go, add your provider to theNewProviderfactory function.func NewProvider(...) (Provider, error) { switch cfg.Type { case "myprovider": return NewMyProvider(cfg), nil // ... } }
-
Configuration: Update
pkg/config/config.goif your provider needs specific configuration fields. Standard fields likemaxResultsandfallback(generic provider fallback) are available inSearchProvider.
you are using windows 11 and windows powershell as terminal, any command you run you be compatible with windows powershell
YAOCC uses modernc.org/sqlite instead of github.com/mattn/go-sqlite3. This means the chat database runs on pure Go without requiring CGO or a C compiler, making it easily cross-compilable to ARM/Windows/Mac from a single environment. gated by the pkg/exec package.
- Default Blacklist:
pkg/exec/exec.gocontains a hardcoded list of dangerous patterns (rm -rf,sudo, etc.). - Configuration:
pkg/config/config.godefinesCmdsstructure.whitelist: If present, ONLY matching commands are allowed.blacklist: If regex matches, command is blocked.
- Timeout: All commands have a 30-second timeout.
- Context: Commands run in the
YAOCC_CONFIG_DIR.