Development Guide
This guide is for contributors working on innate-aiswitcher.
Project Structure
├── cmd/aisw/ # CLI main entrypoint
├── cmd/mock-provider/ # Local mock LLM server for smoke tests
├── internal/
│ ├── adapter/ # Agent launch plan builders (claude, codex, gemini, openai_env)
│ ├── app/ # PocketBase app bootstrap and custom routes
│ ├── configfile/ # TOML config export/import
│ ├── httpcheck/ # Provider connectivity and model listing
│ ├── safefile/ # Atomic file writes
│ ├── store/ # Typed PocketBase collection wrappers
│ ├── templates/ # Embedded config/preset templates
│ ├── tui/ # Interactive terminal UI
│ └── webui/ # Embedded Web UI (static HTML/CSS/JS via go:embed)
├── docs/ # Markdown 文档源(docmd 发布到 GitHub Pages)
├── docmd.config.json # docmd 站点配置(summer 模板)
├── package.json # docmd 依赖与 npm scripts
├── migrations/ # PocketBase collection migrations
└── Taskfile.yml # Build tasks
Prerequisites
- Go 1.26+
- Task (optional but recommended)
- Node.js 18+ (only for building/previewing the documentation site)
Build
# Build the CLI binary
task build
# Cross-compile for a single target
task build:windows
task build:linux
# Build then install to ~/.local/bin
task install
# Format code
task fmt
# Run go vet
task vet
# Compile all packages
task compile
# Full verification pipeline
task verify
Note: Do not use
go test ./...orgo build ./...from the repository root. The repo contains reference projects with unrelated Go modules. Use the scoped tasks inTaskfile.yml.
Testing
Unit Tests
task test
This runs tests for the scoped packages:
go test . ./cmd/aisw ./cmd/mock-provider ./internal/... ./migrations
Coverage Areas
| Package | Test Focus |
|---|---|
internal/adapter |
Launch plan projection for each agent adapter, settings file generation, cleanup |
internal/httpcheck |
Request construction per protocol, endpoint overrides, model extraction |
internal/templates |
Embedded template loading, preset resolution, provider generation |
internal/safefile |
Atomic writes, permissions, parent directory creation |
internal/configfile |
TOML roundtrip, default path formatting |
Smoke Tests
task smoke
This runs a full integration test:
- Creates a temporary PocketBase data directory.
- Starts the mock provider server.
- Imports a config template.
- Adds a provider and tests connectivity.
- Lists models.
- Creates a profile.
- Runs a dry-run launch.
- Exports config.
Adding a New Adapter
- Implement a
Builderfunction ininternal/adapter/adapter.go:
func buildMyAgentPlan(ctx BuildContext) (LaunchPlan, func(), error) {
// ... projection logic
}
- Register it in the
buildersmap (or calladapter.Register("my_agent", buildMyAgentPlan)):
var builders = map[string]Builder{
// ... existing adapters
"my_agent": buildMyAgentPlan,
}
Add a unit test in
internal/adapter/adapter_test.go(uset.TempDir()for any filesystem side effects; the existing tests use--dry-runplans).Add the agent seed in a migration. For new collections follow
migrations/1780565700_init_aisw.go; for upserting into an existing collection use theFindFirstRecordByFilterpattern inmigrations/1780992004_add_skip_permissions_to_agents.goormigrations/1780567400_seed_hermes_openclaw_agents.go.
Adding a New Provider Preset
- Edit
internal/templates/files/provider-presets.toml. - Add a
[[presets]]entry withslug,name, and one or more[[presets.url_options]]blocks. Each option needsslug,label,base_url,api_protocol(openai_chat|anthropic|openai_responses),default_model, and protocol-specificendpoints. - If the option targets Claude Code and needs extra session env (long timeouts, traffic flags), set
capabilities = { claude_extra_env = { ... } }— see thevolcengineandxiaomiclaude options for the pattern. For Codex bearer-token auth, setcapabilities = { codex_auth_mode = "experimental_bearer_token" }. - Slug projection: when an option’s
slugdiffers from the presetslug(or a preset has multiple options),ProviderFromPresetprojects the provider aspreset-slug-option-slug(e.g.volcengine-claude,minimax-codex). Single-option presets where they match keep the original slug (e.g.deepseek,openai). - Run
task testto verify the preset loads correctly, and add a preset-shape test ininternal/templates/templates_test.go(compareTestProviderPresetsVolcengineURLOptions).
Code Style
- Follow standard Go formatting (
gofmt). - Use
t.TempDir()in tests for filesystem operations. - Prefer table-driven tests for multiple cases.
- Keep adapter logic free of protocol-specific
switchstatements in the core path.
Commit Messages
Use conventional commit prefixes:
feat:— new featurefix:— bug fixtest:— test addition or improvementdocs:— documentation onlychore:— maintenance, build, tooling
REST & Web UI Development
Start the server for local API and Web UI work:
task serve
Or directly:
go run ./cmd/aisw serve --http 127.0.0.1:8090
Open http://127.0.0.1:8090/ in a browser. The UI calls /api/aisw/* endpoints registered in internal/app/app.go (registerProviderRoutes, registerProfileRoutes, registerAgentRoutes, registerPresetRoutes). Static assets live in internal/webui/static/ and are served via internal/webui/webui.go.
Enable the PocketBase admin UI (separate from the AISwitcher Web UI):
go run ./cmd/aisw --admin-ui serve --http 127.0.0.1:8090
Use --quiet on serve to suppress HTTP access logs. API reference: REST API.
Documentation Site (docmd)
User-facing docs are built with docmd from the docs/ folder and published to GitHub Pages on every push to main.
| URL | Purpose |
|---|---|
| https://variableway.github.io/innate-aiswitcher/ | Production docs site |
http://localhost:3000 |
Local preview (npm run dev) |
npm install # first time only
task docs:dev # hot-reload dev server
task docs:build # output static site to site/
Configuration lives in docmd.config.json (summer template, navigation, base: /innate-aiswitcher/). Deployment workflow: .github/workflows/docs.yml.
One-time GitHub setup: Settings → Pages → Source → GitHub Actions.
For usage examples, see Usage Guide.
For architecture details, see Architecture.