* feat: add run_as_user for per-project OS-user isolation Implements #496. Adds an optional `run_as_user` field at the project level that, when set, spawns the project's agent command under a different Unix user via `sudo -n -iu <user> --`. This provides OS-level file-system isolation from the supervisor user that runs cc-connect itself: a misbehaving agent can no longer reach files outside the target user's permission scope, even via python -c / shell redirection / other non-hooked code paths. ## What lands in this PR ### Config (config/) - new fields `run_as_user *string` and `run_as_env []string` on each `[[projects]]` entry - validation: rejects root, rejects invalid usernames, rejects use on Windows, enforces POSIX username charset to close a sudo argv injection vector ### Core primitives (core/runas*.go) POSIX-only (Windows stubs provided for API compatibility). - `BuildSpawnCommand(ctx, opts, name, args...)` — wraps exec.Cmd with `sudo -n -iu <user> --preserve-env=<allowlist> -- name args...` when opts.RunAsUser is set; passthrough otherwise - `FilterEnvForSpawn(env, opts)` — strips supervisor env to a minimal allowlist (PATH, LANG, LC_*, TERM) plus any project-level extensions - `VerifyRunAsUserCheap` — the two cheap sudo probes, suitable for per-spawn re-verification (catches sudoers edits between startup and spawn) - `PreflightRunAsUser` — the three go/no-go startup gates from #496: (1) passwordless sudo to target works, (2) target cannot passwordless sudo (if escalation succeeds, captures `sudo -n -l` output in the error message), and (3) target can read AND write work_dir root. Plus a non-fatal descendant scan warning on unreadable/unwritable descendants with prune-paths, 10s timeout, and a 50-entry cap. - `RunIsolationProbe` + embedded runas_probe.sh (//go:embed) — the leak-audit probe codex asked for: spawns a fixed shell script as the target user and parses its output into a structured IsolationReport. Reports effective id/groups/umask/pwd, target-home config presence, cross-user denial attempts for every other configured run_as_user, and supervisor-denial attempts. Never reads file contents, only existence and access. All of these are testable without a real second user via an injectable `SudoRunner` interface; production uses ExecSudoRunner. ### Failure policy Per-spec: all checks are hard go/no-go, not warnings. - Any preflight fatal in any project: cc-connect refuses to start entirely; no half-spawned processes. - Any audit-level `CROSS_LEAKED` (target reads another project user's secrets) or `SUPERVISOR_LEAKED` (target reads the supervisor's secrets) or `WORKDIR_WRITABLE=no`: cc-connect refuses to start. - Descendant-scan findings are warnings (inaccessibility inside a project tree is sometimes intentional). ### Startup wiring (cmd/cc-connect/) - `runRunAsUserStartupChecks` runs preflight + isolation probe for every project with run_as_user set, in parallel, BEFORE any agent or engine is constructed. Logs every outcome via slog. On any fatal, prints the full list and `os.Exit(1)`. - injects project-level run_as_user / run_as_env into the agent's options map before calling `core.CreateAgent`, so agents can opt in without new top-level plumbing. ### Doctor subcommand (cmd/cc-connect/doctor_runas.go) New `cc-connect doctor user-isolation [--project NAME] [--out PATH]` subcommand. Runs preflight + isolation probe for one or all `run_as_user` projects, writes a JSON report per project, and prints a human-readable summary. Default output is `~/.cc-connect/audits/<timestamp>-<project>.json`. Also supports `--print-script` to dump the embedded probe source for inspection (and for running shellcheck against it). ### Claude Code wiring (agent/claudecode/) - `Agent` struct gains `runAsUser` and `runAsEnv` fields - `New()` parses them from the opts map and skips the `exec.LookPath("claude")` check when run_as_user is set (the target user's PATH is what matters, not the supervisor's) - `newClaudeSession` takes `core.SpawnOptions`, runs the per-spawn cheap re-check immediately before building the command, uses `core.BuildSpawnCommand` instead of `exec.CommandContext`, and filters env via `core.FilterEnvForSpawn` ### Docs - new "Running agents as a different Unix user" section in `docs/usage.md` with setup steps, sudoers example, ACL example, environment-propagation migration checklist (PGSSL*, ~/.claude/ settings.json, ~/.pgpass, ~/keys/, ~/.ssh/, MCP binaries, shell profile, language toolchains), opt-out instructions, and failure-mode debug guide - README section with a short configuration example and a pointer to the full docs - `config.example.toml` commented example with setup recipe ### Tests - config: parse run_as_user/run_as_env, reject root, reject invalid chars, Windows rejection, username validator valid/invalid cases - core: BuildSpawnCommand legacy vs. sudo form, FilterEnvForSpawn filtering, VerifyRunAsUserCheap success/no-sudo/escalation-possible/ empty-user, PreflightRunAsUser all-pass/no-sudo/escalation-fatal/ workdir-fatal/descendant-warnings/descendant-cap, probe parser clean/cross-leak-fatal/supervisor-leak-fatal/workdir-not-writable- fatal, shell quoting, other-users filtering, embedded probe script sanity - agent/claudecode: New() parses run_as_user/run_as_env, run_as_user skips LookPath - cmd/cc-connect: default audit dir suffix, printHumanReport doesn't panic on populated report Uses stubbed SudoRunners throughout — no tests require a real second Unix user or real sudo binary. ## Scope Only Claude Code is wired up in this PR. Each non-claudecode agent has its own session/spawn lifecycle (codex re-spawns per turn, cursor has its own state model, etc.) so the sweep needs per-agent reasoning and is deferred to a follow-up. The core primitives land here so each future agent migration is a small, self-contained PR. Follow-up issue tracks: codex, cursor, gemini, iflow, opencode, qoder, acp, pi. Refs: #496 Credit: issue framing via user + codex adversarial review (go/no-go gates, leak-audit probe, env propagation docs, sudo -iu rationale pinning) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(run_as_user): simplify after code review Addresses findings from a parallel code-reuse / code-quality / efficiency review of #498. Behavior unchanged; tests still pass. Highlights: - cache VerifyRunAsUserCheap results for 30s keyed by runAsUser, so rapid-fire messages from the same Slack user don't pay the ~100ms per-spawn sudo tax. Cache evicts on failure so the next spawn re-verifies fresh. Adds ResetVerifyCache for tests + operators. - doctor user-isolation now fans out across projects in parallel (mirroring runas_startup.go) instead of iterating serially. Per-project output is buffered and printed in order so the report stays grouped. - collapse Agent.runAsUser + Agent.runAsEnv into a single core.SpawnOptions field — eliminates the reassemble-at-call-site pattern. - fold core/runas_username.go into core/runas.go (one 17-line file → one helper near its only caller). - IsolationReport.RawOutput is now only populated when the audit had a fatal — clean reports are smaller on disk. - introduce typed string constants for the probe wire format (tagBegin / tagWorkDirReadable / tagCrossLeaked / ...) so the parser and runas_probe.sh share a discoverable contract. - itoa() in tests → strconv.Itoa - contains() helper in tests → slices.Contains - mass comment trim across runas.go / runas_check.go / runas_audit.go: delete WHAT-restates-name comments (~70 lines) while keeping the load-bearing WHY blocks (sudo -n -iu rationale, env propagation reasoning, find expression format note, scan timeout justification). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(runas): drop PATH from default env allowlist Preserving PATH across the sudo boundary defeats the whole point of -i: it overrides the target user's login-profile PATH with the supervisor's, and leaks supervisor work directories (e.g. the cc-connect source tree on the supervisor's PATH) into the isolated session. sudo -i now rebuilds PATH from /etc/profile + the target user's ~/.profile. Binaries the isolated agent needs (like the claude CLI) should be installed system-wide (e.g. /usr/local/bin symlinks) or put on the target user's own shell profile. Caught by Q6/Q8 daemon-spawn verification in data-worklog Phase 0a: partseeker-coder sessions were running with leigh's PATH intact. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(engine): propagate run_as_user to per-workspace agents In multi-workspace mode, getOrCreateWorkspaceAgent constructs a fresh opts map for each per-workspace agent instead of inheriting the project-level opts that main.go decorated at startup. As a result, run_as_user (and run_as_env) silently dropped out of the per-workspace spawn path — the project-level config said partseeker-coder but every spawn actually ran as the supervisor. Fix: expose GetRunAsUser/GetRunAsEnv accessors on *claudecode.Agent and copy them across in getOrCreateWorkspaceAgent via interface assertion, mirroring the existing GetModel/GetMode pattern. Two regression tests cover the propagation and the no-propagation case so multi-workspace deployments that don't use run_as_user are unaffected. Caught by Q6/Q8 daemon-spawn verification in data-worklog Phase 0a. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: add OAuth credential sharing guidance for run_as_user setups OAuth access tokens expire after a few hours and only get refreshed by active Claude CLI sessions. When the target user has no active session between cc-connect spawns, its token expires and auth fails with 401. Document the symlink + ACL pattern: target user's credentials.json symlinks to the supervisor's copy so both share one auto-refreshed token. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: graceful session shutdown — close stdin before signals The old Close() called context.Cancel() which sends SIGKILL immediately via Go's exec.CommandContext, giving Claude Code zero time to run Stop hooks. This broke claude-mem session summaries (120s timeout) and any other cleanup that depends on a clean exit. New three-phase shutdown: 1. Close stdin (EOF) — Claude Code exits cleanly, running Stop hooks 2. SIGTERM after 30s grace period if still alive 3. SIGKILL after 5s more as last resort Also increases engine-level close timeout from 10s to 45s to accommodate the longer graceful window. Tested: all agent/claudecode and core tests pass. Verified Claude Code exits cleanly on stdin EOF (exit code 0). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: 120s graceful close + hold message during session transition Increases the default graceful stop timeout from 30s to 120s to match claude-mem's Stop hook timeout. The wait ends as soon as the process exits — typical shutdowns take seconds, not the full 120s. When a user messages during an idle-reset that triggers a graceful close, they now see "Wrapping up your previous session (usually a few seconds, up to 2 minutes). Your new session will start automatically." instead of a silent delay. The hold message is sent before the blocking close, and the new session starts immediately when the old one finishes. Engine-level close timeout raised to 130s (120s grace + 5s SIGTERM + 5s buffer). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Leigh Stillard <leigh@stillard.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1015 lines
31 KiB
Markdown
1015 lines
31 KiB
Markdown
# Usage Guide
|
|
|
|
Complete guide to using cc-connect features.
|
|
|
|
## Table of Contents
|
|
|
|
- [Session Management](#session-management)
|
|
- [Permission Modes](#permission-modes)
|
|
- [API Provider Management](#api-provider-management)
|
|
- [Model Selection](#model-selection)
|
|
- [Work Directory Switching (`/dir`, `/cd`)](#work-directory-switching-dir-cd)
|
|
- [Feishu Setup CLI](#feishu-setup-cli)
|
|
- [Weixin (personal) Setup CLI](#weixin-personal-setup-cli)
|
|
- [Claude Code Router Integration](#claude-code-router-integration)
|
|
- [Voice Messages (STT)](#voice-messages-speech-to-text)
|
|
- [Voice Reply (TTS)](#voice-reply-text-to-speech)
|
|
- [Image and File Send-Back](#image-and-file-send-back)
|
|
- [Scheduled Tasks (Cron)](#scheduled-tasks-cron)
|
|
- [Multi-Bot Relay](#multi-bot-relay)
|
|
- [Daemon Mode](#daemon-mode)
|
|
- [Multi-Workspace Mode](#multi-workspace-mode)
|
|
- [Web Admin Dashboard (Beta)](#web-admin-dashboard-beta)
|
|
- [Bridge — External Adapter Access (Beta)](#bridge--external-adapter-access-beta)
|
|
- [Configuration Reference](#configuration-reference)
|
|
|
|
---
|
|
|
|
## Session Management
|
|
|
|
Each user gets an independent session with full conversation context. Manage sessions via slash commands:
|
|
|
|
| Command | Description |
|
|
|---------|-------------|
|
|
| `/new [name]` | Start a new session |
|
|
| `/list` | List all agent sessions for this project |
|
|
| `/switch <id>` | Switch to a different session |
|
|
| `/current` | Show current session info |
|
|
| `/history [n]` | Show last n messages (default 10) |
|
|
| `/usage` | Show account/model quota usage (if supported) |
|
|
| `/provider [...]` | Manage API providers |
|
|
| `/model [switch <alias>]` | List available models or switch by alias |
|
|
| `/dir [path]` | Show or switch the agent work directory |
|
|
| `/allow <tool>` | Pre-allow a tool (next session) |
|
|
| `/reasoning [level]` | View or switch reasoning effort (Codex) |
|
|
| `/mode [name]` | View or switch permission mode |
|
|
| `/stop` | Stop current execution |
|
|
| `/help` | Show available commands |
|
|
|
|
During a session, the agent may request tool permissions. Reply **allow** / **deny** / **allow all**.
|
|
|
|
You can also configure automatic session rotation after inactivity:
|
|
|
|
```toml
|
|
[[projects]]
|
|
name = "demo"
|
|
reset_on_idle_mins = 60
|
|
```
|
|
|
|
When enabled, the next normal message after a long idle period starts in a fresh session automatically, without deleting the old session from `/list`.
|
|
|
|
---
|
|
|
|
## Permission Modes
|
|
|
|
All agents support permission modes switchable at runtime via `/mode`.
|
|
|
|
### Claude Code Modes
|
|
|
|
| Mode | Config Value | Behavior |
|
|
|------|-------------|----------|
|
|
| Default | `default` | Every tool call requires approval |
|
|
| Accept Edits | `acceptEdits` / `edit` | File edits auto-approved |
|
|
| Auto | `auto` | Claude decides when to ask for permission |
|
|
| Plan Mode | `plan` | Claude only plans, no execution |
|
|
| YOLO | `bypassPermissions` / `yolo` | All tools auto-approved |
|
|
|
|
### Codex Modes
|
|
|
|
| Mode | Config Value | Behavior |
|
|
|------|-------------|----------|
|
|
| Suggest | `suggest` | Only trusted commands run without approval |
|
|
| Auto Edit | `auto-edit` | Model decides when to ask |
|
|
| Full Auto | `full-auto` | Auto-approve with sandbox |
|
|
| YOLO | `yolo` | Bypass all approvals and sandbox |
|
|
|
|
### Cursor Agent Modes
|
|
|
|
| Mode | Config Value | Behavior |
|
|
|------|-------------|----------|
|
|
| Default | `default` | Trust workspace, ask before tools |
|
|
| Force (YOLO) | `force` / `yolo` | Auto-approve all |
|
|
| Plan | `plan` | Read-only analysis |
|
|
| Ask | `ask` | Q&A style, read-only |
|
|
|
|
### Gemini CLI Modes
|
|
|
|
| Mode | Config Value | Behavior |
|
|
|------|-------------|----------|
|
|
| Default | `default` | Prompt for approval |
|
|
| Auto Edit | `auto_edit` / `edit` | Auto-approve edits |
|
|
| YOLO | `yolo` | Auto-approve all |
|
|
| Plan | `plan` | Read-only plan mode |
|
|
|
|
### Qoder CLI / OpenCode / iFlow CLI
|
|
|
|
| Mode | Config Value | Behavior |
|
|
|------|-------------|----------|
|
|
| Default | `default` | Standard permissions |
|
|
| YOLO | `yolo` | Skip all checks |
|
|
|
|
### Configuration
|
|
|
|
```toml
|
|
[projects.agent.options]
|
|
mode = "default"
|
|
# allowed_tools = ["Read", "Grep", "Glob"]
|
|
```
|
|
|
|
Switch at runtime:
|
|
```
|
|
/mode # show current and available modes
|
|
/mode yolo # switch to YOLO mode
|
|
/mode default # switch back
|
|
```
|
|
|
|
---
|
|
|
|
## API Provider Management
|
|
|
|
Switch between API providers at runtime without restart.
|
|
|
|
### Configure Providers
|
|
|
|
```toml
|
|
[projects.agent.options]
|
|
work_dir = "/path/to/project"
|
|
provider = "anthropic" # active provider
|
|
|
|
[[projects.agent.providers]]
|
|
name = "anthropic"
|
|
api_key = "sk-ant-xxx"
|
|
|
|
[[projects.agent.providers]]
|
|
name = "relay"
|
|
api_key = "sk-xxx"
|
|
base_url = "https://api.relay-service.com"
|
|
model = "claude-sonnet-4-20250514"
|
|
|
|
[[projects.agent.providers.models]]
|
|
model = "claude-sonnet-4-20250514"
|
|
alias = "sonnet"
|
|
|
|
[[projects.agent.providers.models]]
|
|
model = "claude-opus-4-20250514"
|
|
alias = "opus"
|
|
|
|
[[projects.agent.providers.models]]
|
|
model = "claude-haiku-3-5-20241022"
|
|
alias = "haiku"
|
|
|
|
# MiniMax — OpenAI-compatible, 1M context
|
|
[[projects.agent.providers]]
|
|
name = "minimax"
|
|
api_key = "your-minimax-api-key"
|
|
base_url = "https://api.minimax.io/v1"
|
|
model = "MiniMax-M2.7"
|
|
|
|
# For Bedrock, Vertex, etc.
|
|
[[projects.agent.providers]]
|
|
name = "bedrock"
|
|
env = { CLAUDE_CODE_USE_BEDROCK = "1", AWS_PROFILE = "bedrock" }
|
|
```
|
|
|
|
### CLI Commands
|
|
|
|
```bash
|
|
cc-connect provider add --project my-backend --name relay --api-key sk-xxx --base-url https://api.relay.com
|
|
cc-connect provider list --project my-backend
|
|
cc-connect provider remove --project my-backend --name relay
|
|
cc-connect provider import --project my-backend # from cc-switch
|
|
```
|
|
|
|
### Chat Commands
|
|
|
|
```
|
|
/provider Show current provider
|
|
/provider list List all providers
|
|
/provider add <name> <key> [url] [model]
|
|
/provider remove <name>
|
|
/provider switch <name>
|
|
/provider <name> Shortcut for switch
|
|
```
|
|
|
|
### Env Var Mapping
|
|
|
|
| Agent | api_key → | base_url → |
|
|
|-------|-----------|------------|
|
|
| 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 |
|
|
| iFlow CLI | `IFLOW_API_KEY` | `IFLOW_BASE_URL` |
|
|
|
|
---
|
|
|
|
## Model Selection
|
|
|
|
Pre-configure a list of selectable models per provider using `[[providers.models]]`. Each entry has a `model` identifier and an optional `alias` (short name shown in `/model`).
|
|
|
|
### Configure Models
|
|
|
|
```toml
|
|
[[projects.agent.providers]]
|
|
name = "openai"
|
|
api_key = "sk-xxx"
|
|
|
|
[[projects.agent.providers.models]]
|
|
model = "gpt-5.3-codex"
|
|
alias = "codex"
|
|
|
|
[[projects.agent.providers.models]]
|
|
model = "gpt-5.4"
|
|
alias = "gpt"
|
|
|
|
[[projects.agent.providers.models]]
|
|
model = "gpt-5.3-codex-spark"
|
|
alias = "spark"
|
|
```
|
|
|
|
### Chat Commands
|
|
|
|
```
|
|
/model List available models (format: alias - model)
|
|
/model switch <alias> Switch to the model matching the alias
|
|
/model switch <name> Switch to the model by its full name
|
|
/model <alias> Legacy syntax, still supported
|
|
```
|
|
|
|
When `models` is configured, `/model` shows exactly that list without making an API round-trip. When omitted, models are fetched from the provider API or fall back to a built-in list.
|
|
|
|
---
|
|
|
|
## Work Directory Switching (`/dir`, `/cd`)
|
|
|
|
Switch where the next agent session starts, directly from chat.
|
|
|
|
### Chat Commands
|
|
|
|
```
|
|
/dir Show current work directory and recent history
|
|
/dir <path> Switch to a path (relative or absolute)
|
|
/dir <number> Switch to a directory from history
|
|
/dir - Switch back to previous directory
|
|
/dir help Show command usage
|
|
/cd <path> Backward-compatible alias of /dir <path>
|
|
```
|
|
|
|
### Behavior Notes
|
|
|
|
- Directory changes apply to the next session in the current project.
|
|
- Relative paths are resolved from the current agent work directory.
|
|
- Directory history is project-scoped and can be switched by index.
|
|
- `/cd` is kept for compatibility, but `/dir` is the primary command.
|
|
|
|
Examples:
|
|
|
|
```text
|
|
/dir ../another-repo
|
|
/dir 2
|
|
/dir -
|
|
```
|
|
|
|
---
|
|
|
|
## Running agents as a different Unix user (`run_as_user`)
|
|
|
|
> **Platform support**: Linux and macOS. Not supported on Windows.
|
|
> **Agent support**: Claude Code today. Other agents fall back to the
|
|
> supervisor user; see the tracking issue for migration status.
|
|
|
|
### What this is
|
|
|
|
By default, every agent session cc-connect spawns runs as the same Unix
|
|
user that runs `cc-connect` itself. If an agent misbehaves — reads a
|
|
secret, overwrites a sibling repo, trashes `~/.ssh/` — it has the
|
|
supervisor user's full file-system reach.
|
|
|
|
`run_as_user` sets a per-project target Unix user. When it is set,
|
|
cc-connect spawns that project's agent command via
|
|
|
|
```
|
|
sudo -n -iu <target-user> -- claude ...
|
|
```
|
|
|
|
The target user is a real, unprivileged Unix account that you create.
|
|
The agent runs under that account's uid/gid, with **its own** home
|
|
directory, shell profile, PATH, and tool credentials. File-system
|
|
isolation is enforced by the kernel, not by hooks or allowlists.
|
|
|
|
### Security guarantee and non-guarantee
|
|
|
|
**This provides OS-user isolation from any file or process the target
|
|
user cannot reach.** An agent can no longer read or clobber the
|
|
supervisor's `~/.ssh/`, another project user's `~/.pgpass`, or a repo
|
|
whose UNIX permissions don't grant access to the target user.
|
|
|
|
**This does not automatically isolate projects from each other** if they
|
|
share the same `run_as_user`. If you want per-project isolation, create
|
|
a separate Unix user per project.
|
|
|
|
**This is not a sandbox in the sense of Linux namespaces, seccomp, or
|
|
container isolation.** It is strictly file-system scoping by uid.
|
|
|
|
### Setup
|
|
|
|
#### 1. Create the target user and install their tooling
|
|
|
|
The target user needs its own copy of everything the agent touches,
|
|
because `sudo -i` loads the *target* user's login environment — not the
|
|
supervisor's.
|
|
|
|
```bash
|
|
sudo useradd -m -s /bin/bash partseeker-coder
|
|
sudo -iu partseeker-coder
|
|
|
|
# Install the agent CLI under the target user's PATH
|
|
# (for Claude Code, follow the normal install instructions)
|
|
|
|
# Set up the target user's ~/.claude/
|
|
mkdir -p ~/.claude
|
|
# Copy or re-create:
|
|
# ~/.claude/settings.json (MCP servers, hooks, model settings)
|
|
# ~/.claude.json (Claude Code auth)
|
|
# ~/.claude/plugins/ (claude-mem and any other plugin state)
|
|
|
|
exit
|
|
```
|
|
|
|
#### 2. Grant the supervisor passwordless sudo to the target
|
|
|
|
Add a scoped sudoers rule. Do **not** use `NOPASSWD: ALL` for the
|
|
supervisor — that grants the supervisor root, which is irrelevant here
|
|
and dangerous.
|
|
|
|
```
|
|
# /etc/sudoers.d/cc-connect (install with `sudo visudo -f ...`)
|
|
partseeker-orchestrator ALL=(partseeker-coder) NOPASSWD: ALL
|
|
```
|
|
|
|
Adjust the usernames for your setup. The rule says: *"the supervisor
|
|
user may run any command as this specific target user, without a
|
|
password."*
|
|
|
|
#### 3. Verify the target user cannot sudo
|
|
|
|
The whole point of stepping down into a target user is that the target
|
|
cannot immediately escalate back. Verify:
|
|
|
|
```bash
|
|
sudo -n -iu partseeker-coder -- sudo -n true
|
|
# must FAIL with "a password is required" or similar
|
|
```
|
|
|
|
If that command succeeds, cc-connect will refuse to start. Remove any
|
|
`NOPASSWD` sudo grants for the target user first.
|
|
|
|
#### 4. Make the project's `work_dir` accessible to the target user
|
|
|
|
The target user needs read AND write on the project's `work_dir`. If
|
|
the directory is owned by the supervisor, either `chown` it to the
|
|
target, add group ownership the target is in, or apply a POSIX ACL:
|
|
|
|
```bash
|
|
sudo setfacl -R -m u:partseeker-coder:rwX /home/leigh/workspace/sandboxed-repo
|
|
sudo setfacl -R -dm u:partseeker-coder:rwX /home/leigh/workspace/sandboxed-repo
|
|
```
|
|
|
|
cc-connect refuses to start if the target user cannot read+write the
|
|
`work_dir` root, and warns (non-fatal) for descendant paths that look
|
|
inaccessible.
|
|
|
|
#### 5. Audit the setup before starting cc-connect
|
|
|
|
```bash
|
|
cc-connect doctor user-isolation
|
|
```
|
|
|
|
This runs the full preflight (the three go/no-go gates from
|
|
[#496](https://github.com/chenhg5/cc-connect/issues/496)) and an
|
|
**isolation probe**: it spawns a fixed shell script as the target user
|
|
and reports what the target can read, what it's denied, and any
|
|
cross-user leaks. Output goes to stdout plus a JSON report in
|
|
`~/.cc-connect/audits/<timestamp>-<project>.json`.
|
|
|
|
Exit code 0 = clean. Exit code 1 = at least one fatal problem.
|
|
|
|
You can inspect the probe script itself with:
|
|
|
|
```bash
|
|
cc-connect doctor user-isolation --print-script
|
|
```
|
|
|
|
### Configuration
|
|
|
|
```toml
|
|
[[projects]]
|
|
name = "claude-sandboxed"
|
|
run_as_user = "partseeker-coder"
|
|
|
|
# Optional: extend the default env var allowlist that crosses the sudo
|
|
# boundary. The defaults (PATH, LANG, LC_*, TERM) are always included.
|
|
# Only list vars the target user cannot reasonably set in their own
|
|
# shell profile. Secrets belong in the target user's ~/.claude/settings.json
|
|
# env block, NOT here.
|
|
run_as_env = ["PGSSLROOTCERT", "PGSSLMODE"]
|
|
|
|
[projects.agent]
|
|
type = "claudecode"
|
|
|
|
[projects.agent.options]
|
|
mode = "default"
|
|
model = "sonnet"
|
|
work_dir = "/home/leigh/workspace/sandboxed-repo"
|
|
```
|
|
|
|
### Environment propagation: what moves into the target user's home
|
|
|
|
This is the 2am-debugging section. When you switch a project to
|
|
`run_as_user`, the supervisor's environment is **not** forwarded across
|
|
the sudo boundary — that's the whole point. Everything the agent needs
|
|
has to live in the target user's home.
|
|
|
|
Migration checklist:
|
|
|
|
- [ ] **Agent config** — `~/.claude/settings.json` (MCP servers, hooks,
|
|
model settings), `~/.claude.json` (auth). Copy from the supervisor
|
|
or re-create from scratch.
|
|
- [ ] **Plugin state** — `~/.claude/plugins/` — claude-mem, any other
|
|
Claude Code plugins.
|
|
- [ ] **MCP server binaries** — must be on the target user's `PATH`, not
|
|
just the supervisor's. Either install under the target user or
|
|
reference full paths in `settings.json`.
|
|
- [ ] **Postgres TLS** — `PGSSLROOTCERT`, `PGSSLCERT`, `PGSSLKEY` belong
|
|
in the target user's `~/.claude/settings.json` `env` block. Their
|
|
referenced cert files must be readable by the target user.
|
|
- [ ] **Claude OAuth credentials** — if you authenticate via `claude.ai`
|
|
(OAuth), the token lives in `~/.claude/.credentials.json`. OAuth
|
|
access tokens expire after a few hours and are refreshed
|
|
automatically by whichever Claude CLI session is running. The
|
|
target user's token will **not** be refreshed unless the target
|
|
user has an active session — which it often doesn't between
|
|
cc-connect spawns. The recommended fix is to symlink the target
|
|
user's credentials to the supervisor's file so both share one
|
|
token that stays fresh:
|
|
|
|
```bash
|
|
# Grant target user read access via ACL (keeps 600 for everyone else)
|
|
setfacl -m u:<target-user>:rx ~/.claude/
|
|
setfacl -m u:<target-user>:r ~/.claude/.credentials.json
|
|
|
|
# Replace the target user's credentials with a symlink
|
|
sudo -iu <target-user> bash -c \
|
|
'rm -f ~/.claude/.credentials.json && \
|
|
ln -s /home/<supervisor>/.claude/.credentials.json \
|
|
~/.claude/.credentials.json'
|
|
```
|
|
|
|
**If you use an API key** (`ANTHROPIC_API_KEY`) instead of OAuth,
|
|
this is not an issue — set the key in the target user's
|
|
`~/.claude/settings.json` `env` block and it won't expire.
|
|
- [ ] **Credential files** — `~/.pgpass`, `~/.gitconfig`, `~/.netrc`,
|
|
`~/.aws/`, `~/.config/gh/`, `~/.kube/` — whichever the agent
|
|
actually uses. Each needs its own copy or a group-readable shared
|
|
copy.
|
|
- [ ] **SSH keys** — `~/.ssh/id_ed25519` etc., if the agent runs `git
|
|
push` over SSH. Same story: copy or group-share.
|
|
- [ ] **Key material under** `~/keys/` — custom directories the
|
|
supervisor uses need an equivalent under the target user's home
|
|
or a group-readable shared copy.
|
|
- [ ] **Language toolchains** — if the agent uses `asdf`, `mise`, `nvm`,
|
|
`rustup`, etc., those live in `~`. The target user needs either
|
|
its own install or a system-wide install that both users can run.
|
|
- [ ] **Shell profile** — `~/.profile` / `~/.bashrc` on the target user
|
|
needs to set `PATH` and any tool init the agent depends on. Test
|
|
with `sudo -iu partseeker-coder` before wiring cc-connect.
|
|
|
|
After migration, run `cc-connect doctor user-isolation` again. The
|
|
`target home` section reports which expected paths are present and
|
|
which are missing — missing isn't necessarily wrong, but it's your
|
|
checklist.
|
|
|
|
### Opting out
|
|
|
|
Remove `run_as_user` from the project entry, or set it to `""`. Legacy
|
|
behavior (spawn as supervisor) returns on the next restart.
|
|
|
|
### Failure modes and error messages
|
|
|
|
- **"passwordless sudo to user X is not configured"** — step 2 of setup
|
|
is missing or the sudoers rule is scoped to the wrong supervisor. Fix
|
|
the rule, run `visudo -c` to validate syntax, then restart cc-connect.
|
|
- **"target user X can run passwordless sudo"** — step 3 failed. The
|
|
error includes the output of `sudo -l` from the target context; find
|
|
the offending rule and remove it.
|
|
- **"target user X cannot read AND write work_dir Y"** — step 4 failed.
|
|
`chown` the directory or add an ACL as shown above.
|
|
- **"CROSS_LEAKED"** or **"SUPERVISOR_LEAKED"** in the audit — the
|
|
target user can read another user's secrets. Tighten the offending
|
|
file's permissions (usually `chmod 600 file; chown user:user file`)
|
|
and re-audit.
|
|
- **"descendant scan timed out"** — non-fatal. The `work_dir` is large
|
|
enough that the permission walk exceeded its timeout. Run
|
|
`cc-connect doctor user-isolation` manually if you want the full
|
|
walk, or narrow the project's `work_dir`.
|
|
|
|
---
|
|
|
|
## Feishu Setup CLI
|
|
|
|
Use CLI to create or bind Feishu/Lark bot credentials and write them back to `config.toml`.
|
|
|
|
```bash
|
|
# Recommended: unified entry
|
|
cc-connect feishu setup --project my-project
|
|
cc-connect feishu setup --project my-project --app cli_xxx:sec_xxx
|
|
|
|
# Force modes (usually unnecessary)
|
|
cc-connect feishu new --project my-project
|
|
cc-connect feishu bind --project my-project --app cli_xxx:sec_xxx
|
|
```
|
|
|
|
Differences:
|
|
- `setup`: unified entry. No credentials => behaves like `new`; with `--app` => behaves like `bind`.
|
|
- `new`: force QR onboarding flow; rejects `--app`.
|
|
- `bind`: force credential binding flow; requires credentials.
|
|
|
|
Behavior:
|
|
- `setup` uses QR onboarding by default, or bind mode when `--app` is provided.
|
|
- If `--project` does not exist, it is created automatically.
|
|
- If project exists but has no `feishu/lark` platform, one is added automatically.
|
|
- The command writes credentials (`app_id`, `app_secret`); in QR onboarding flow, Feishu usually pre-configures permissions and event subscriptions.
|
|
- Still verify app publish status and availability scope in Feishu Open Platform.
|
|
- Runtime platform config also supports an optional `domain` override for Feishu/Lark API endpoints; this does not change setup/onboarding URLs.
|
|
|
|
---
|
|
|
|
## Weixin (personal) Setup CLI
|
|
|
|
Weixin personal chat uses the **ilink bot HTTP API** (long polling + `sendMessage`, same family as OpenClaw `openclaw-weixin`). Use the CLI to scan a QR code or bind an existing Bearer token and write `config.toml`.
|
|
|
|
**Full walkthrough (Chinese): [docs/weixin.md](./weixin.md).**
|
|
|
|
```bash
|
|
cc-connect weixin setup --project my-project
|
|
cc-connect weixin bind --project my-project --token '<token>'
|
|
cc-connect weixin new --project my-project
|
|
```
|
|
|
|
Notes:
|
|
- `setup` without `--token` runs QR login; with `--token` behaves like bind.
|
|
- Auto-creates the project and/or a `weixin` platform block when missing.
|
|
- After login, send a message from WeChat once so `context_token` is cached.
|
|
- See `cc-connect weixin help` for flags (`--api-url`, `--cdn-url`, `--route-tag`, etc.).
|
|
|
|
---
|
|
|
|
## Claude Code Router Integration
|
|
|
|
[Claude Code Router](https://github.com/musistudio/claude-code-router) routes requests to different model providers.
|
|
|
|
### Setup
|
|
|
|
1. Install: `npm install -g @musistudio/claude-code-router`
|
|
|
|
2. Configure `~/.claude-code-router/config.json`:
|
|
```json
|
|
{
|
|
"APIKEY": "your-secret-key",
|
|
"Providers": [
|
|
{
|
|
"name": "deepseek",
|
|
"api_base_url": "https://api.deepseek.com/chat/completions",
|
|
"api_key": "sk-xxx",
|
|
"models": ["deepseek-chat", "deepseek-reasoner"],
|
|
"transformer": { "use": ["deepseek"] }
|
|
}
|
|
],
|
|
"Router": {
|
|
"default": "deepseek,deepseek-chat",
|
|
"think": "deepseek,deepseek-reasoner"
|
|
}
|
|
}
|
|
```
|
|
|
|
3. Start: `ccr start`
|
|
|
|
4. Configure cc-connect:
|
|
```toml
|
|
[projects.agent.options]
|
|
router_url = "http://127.0.0.1:3456"
|
|
router_api_key = "your-secret-key" # optional
|
|
```
|
|
|
|
---
|
|
|
|
## Voice Messages (Speech-to-Text)
|
|
|
|
Send voice messages — cc-connect transcribes them automatically.
|
|
|
|
**Supported:** Feishu, WeChat Work, Telegram, LINE, Discord, Slack
|
|
|
|
**Requirements:** OpenAI/Groq API key, `ffmpeg`
|
|
|
|
### Configure
|
|
|
|
```toml
|
|
[speech]
|
|
enabled = true
|
|
provider = "openai" # or "groq"
|
|
language = "" # "zh", "en", or auto-detect
|
|
|
|
[speech.openai]
|
|
api_key = "sk-xxx"
|
|
# base_url = ""
|
|
# model = "whisper-1"
|
|
|
|
# [speech.groq]
|
|
# api_key = "gsk_xxx"
|
|
# model = "whisper-large-v3-turbo"
|
|
```
|
|
|
|
### Install ffmpeg
|
|
|
|
```bash
|
|
# Ubuntu/Debian
|
|
sudo apt install ffmpeg
|
|
|
|
# macOS
|
|
brew install ffmpeg
|
|
```
|
|
|
|
---
|
|
|
|
## Voice Reply (Text-to-Speech)
|
|
|
|
Synthesize AI replies into voice messages.
|
|
|
|
**Supported:** Feishu (Lark)
|
|
|
|
### Configure
|
|
|
|
```toml
|
|
[tts]
|
|
enabled = true
|
|
provider = "qwen" # or "openai"
|
|
voice = "Cherry"
|
|
tts_mode = "voice_only" # "voice_only" | "always"
|
|
max_text_len = 0 # 0 = no limit
|
|
|
|
[tts.qwen]
|
|
api_key = "sk-xxx"
|
|
# model = "qwen3-tts-flash"
|
|
```
|
|
|
|
### TTS Modes
|
|
|
|
| Mode | Behavior |
|
|
|------|----------|
|
|
| `voice_only` | Reply with voice only when user sends voice |
|
|
| `always` | Always send voice reply |
|
|
|
|
Switch: `/tts always` or `/tts voice_only`
|
|
|
|
---
|
|
|
|
## Image and File Send-Back
|
|
|
|
When an agent generates a local image, PDF, report, bundle, or other file and needs to deliver it directly to the current chat, use attachment mode in `cc-connect send`.
|
|
|
|
**Currently supported platforms:**
|
|
- Feishu
|
|
- Telegram
|
|
|
|
### When to run setup first
|
|
|
|
If the current agent does not natively inject the system prompt, run this once in chat after upgrading:
|
|
|
|
```text
|
|
/bind setup
|
|
```
|
|
|
|
or:
|
|
|
|
```text
|
|
/cron setup
|
|
```
|
|
|
|
These two commands write the same cc-connect instructions. Either one is enough. After that, the agent knows:
|
|
- normal text replies should be returned normally
|
|
- generated attachments should be sent back with `cc-connect send --image/--file`
|
|
|
|
If you have run setup before, run it again after upgrading so the instructions are refreshed to the latest version.
|
|
|
|
### Config switch
|
|
|
|
Add this to `config.toml` if you want to disable agent-driven attachment send-back:
|
|
|
|
```toml
|
|
attachment_send = "off"
|
|
```
|
|
|
|
The default is `on`. This switch is independent from the agent's `/mode` and only affects `cc-connect send --image/--file`.
|
|
|
|
### CLI examples
|
|
|
|
```bash
|
|
cc-connect send --image /absolute/path/to/chart.png
|
|
cc-connect send --file /absolute/path/to/report.pdf
|
|
cc-connect send --file /absolute/path/to/report.pdf --image /absolute/path/to/chart.png
|
|
```
|
|
|
|
Notes:
|
|
- `--image` is for image attachments.
|
|
- `--file` is for any file attachment.
|
|
- `--message` is optional and sends a text note before the attachments.
|
|
- `--image` and `--file` can both be repeated.
|
|
- Absolute paths are recommended so the command does not depend on the agent's current working directory.
|
|
- With `attachment_send = "off"`, image/file send-back is blocked but ordinary text replies still work.
|
|
|
|
### Typical use cases
|
|
|
|
1. The agent generates a screenshot or chart and should send it directly to the user.
|
|
2. The agent generates a PDF, Markdown export, log bundle, or patch file that should be delivered as an attachment.
|
|
3. The agent wants to send a short status message together with one or more generated files.
|
|
|
|
### Important notes
|
|
|
|
- This command is for attachment delivery, not ordinary text replies.
|
|
- The files must exist on the local machine where the agent runs.
|
|
- There must be an active session; otherwise the command fails because cc-connect has no chat context to deliver to.
|
|
- Platform-specific file size and file type limits still apply.
|
|
|
|
---
|
|
|
|
## Scheduled Tasks (Cron)
|
|
|
|
Create scheduled tasks that run automatically.
|
|
|
|
### Chat Commands
|
|
|
|
```
|
|
/cron List all jobs
|
|
/cron add <min> <hour> <day> <mon> <wk> <prompt> Create job
|
|
/cron del <id> Delete job
|
|
/cron enable <id> Enable job
|
|
/cron disable <id> Disable job
|
|
```
|
|
|
|
Example:
|
|
```
|
|
/cron add 0 6 * * * Summarize GitHub trending repos
|
|
```
|
|
|
|
### CLI Commands
|
|
|
|
```bash
|
|
cc-connect cron add --cron "0 6 * * *" --prompt "Summarize GitHub trending" --desc "Daily Trending"
|
|
cc-connect cron list
|
|
cc-connect cron del <job-id>
|
|
```
|
|
|
|
Optional: `--session-mode new-per-run` starts a fresh agent session on each run (default is `reuse`, same as before). `--timeout-mins N` sets how long the scheduler waits per run (`0` = no limit; omit = 30 minutes).
|
|
|
|
### Natural Language (Claude Code)
|
|
|
|
> "Every day at 6am, summarize GitHub trending"
|
|
|
|
Claude Code auto-creates the cron job. For other agents that rely on memory files, run `/cron setup` or `/bind setup` once first; both write the same instructions.
|
|
|
|
---
|
|
|
|
## Multi-Bot Relay
|
|
|
|
Cross-platform bot communication in group chats.
|
|
|
|
### Group Chat Binding
|
|
|
|
```
|
|
/bind Show bindings
|
|
/bind claudecode Add claudecode project
|
|
/bind gemini Add gemini project
|
|
/bind -claudecode Remove claudecode
|
|
```
|
|
|
|
### Bot-to-Bot Communication
|
|
|
|
```bash
|
|
cc-connect relay send --to gemini "What do you think about this architecture?"
|
|
```
|
|
|
|
---
|
|
|
|
## Daemon Mode
|
|
|
|
Run as background service.
|
|
|
|
```bash
|
|
cc-connect daemon install --config ~/.cc-connect/config.toml
|
|
cc-connect daemon start
|
|
cc-connect daemon stop
|
|
cc-connect daemon restart
|
|
cc-connect daemon status
|
|
cc-connect daemon logs [-f]
|
|
cc-connect daemon uninstall
|
|
```
|
|
|
|
---
|
|
|
|
## Multi-Workspace Mode
|
|
|
|
One bot serving multiple workspaces per channel.
|
|
|
|
### Configure
|
|
|
|
```toml
|
|
[[projects]]
|
|
name = "my-project"
|
|
mode = "multi-workspace"
|
|
base_dir = "~/workspaces"
|
|
|
|
[projects.agent]
|
|
type = "claudecode"
|
|
```
|
|
|
|
### Commands
|
|
|
|
```
|
|
/workspace Show current binding
|
|
/workspace bind <name> Bind local folder
|
|
/workspace init <git-url> Clone and bind repo
|
|
/workspace unbind Remove binding
|
|
/workspace list List all bindings
|
|
```
|
|
|
|
### How It Works
|
|
|
|
- Channel name `#project-a` → auto-binds to `base_dir/project-a/`
|
|
- Each channel has isolated sessions and agent state
|
|
|
|
---
|
|
|
|
## Web Admin Dashboard (Beta)
|
|
|
|
> **Status: Beta.** This feature is available since v1.2.2-beta.5. The UI and API may change in future releases.
|
|
|
|
A full-featured management UI embedded in the binary — project CRUD, session management, cron job editor, global settings, chat interface, and i18n support.
|
|
|
|
### Quick Setup (Chat Command)
|
|
|
|
The easiest way to enable web admin:
|
|
|
|
```
|
|
/web setup
|
|
```
|
|
|
|
This automatically enables both the **Management API** and the **Bridge** in `config.toml`, generates tokens, and prints the access URL. You may need to run `/restart` for changes to take effect.
|
|
|
|
After setup, open the URL shown (default `http://localhost:9820`) and log in with the token.
|
|
|
|
### Check Status
|
|
|
|
```
|
|
/web # or /web status — show current web admin URL and status
|
|
```
|
|
|
|
### Manual Configuration
|
|
|
|
Add the following to `config.toml`:
|
|
|
|
```toml
|
|
[management]
|
|
enabled = true
|
|
port = 9820 # Management UI & API listen port
|
|
token = "your-secret-token" # Login token; /web setup generates one automatically
|
|
cors_origins = ["*"] # Allowed CORS origins; empty = no CORS headers
|
|
```
|
|
|
|
Then restart cc-connect.
|
|
|
|
### Build Options
|
|
|
|
Web assets are compiled into the binary by default. To exclude them (saves ~1MB):
|
|
|
|
```bash
|
|
make build-noweb
|
|
# or
|
|
go build -tags 'no_web' ./cmd/cc-connect
|
|
```
|
|
|
|
When built with `no_web`, the `/web` command will report that web admin is not available.
|
|
|
|
### Management API
|
|
|
|
The Management API is served on the same port as the UI. Base URL: `http://<host>:<port>/api/v1`
|
|
|
|
All API requests require the `Authorization: Bearer <token>` header.
|
|
|
|
Key endpoints:
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `GET` | `/api/v1/status` | System status (version, uptime, platforms) |
|
|
| `POST` | `/api/v1/restart` | Restart cc-connect |
|
|
| `POST` | `/api/v1/reload` | Reload configuration |
|
|
| `GET` | `/api/v1/projects` | List projects |
|
|
| `GET` | `/api/v1/sessions?project=<name>` | List sessions for a project |
|
|
| `GET` | `/api/v1/cron` | List cron jobs |
|
|
| `GET` | `/api/v1/settings` | Get global settings |
|
|
| `PATCH` | `/api/v1/settings` | Update global settings |
|
|
|
|
Full API reference: [management-api.md](./management-api.md)
|
|
|
|
---
|
|
|
|
## Bridge — External Adapter Access (Beta)
|
|
|
|
> **Status: Beta.** This feature is available since v1.2.2-beta.5. The protocol may change in future releases.
|
|
|
|
The Bridge exposes a WebSocket + REST server so external adapters (custom UIs, bots, scripts) can interact with cc-connect sessions — send messages, receive events, manage sessions.
|
|
|
|
### Enable via Chat
|
|
|
|
The `/web setup` command enables Bridge automatically alongside the Management API.
|
|
|
|
### Manual Configuration
|
|
|
|
Add the following to `config.toml`:
|
|
|
|
```toml
|
|
[bridge]
|
|
enabled = true
|
|
port = 9810 # Bridge listen port (separate from management)
|
|
token = "your-bridge-secret" # Auth token for WebSocket and REST
|
|
path = "/bridge/ws" # WebSocket endpoint path
|
|
cors_origins = ["*"] # Allowed CORS origins; empty = no CORS
|
|
```
|
|
|
|
Then restart cc-connect.
|
|
|
|
### Authentication
|
|
|
|
All Bridge connections require a token. Supported methods:
|
|
|
|
- Query parameter: `?token=<bridge-token>`
|
|
- Header: `Authorization: Bearer <bridge-token>`
|
|
- Header: `X-Bridge-Token: <bridge-token>`
|
|
|
|
### WebSocket
|
|
|
|
Connect to:
|
|
|
|
```
|
|
ws://<host>:<bridge-port>/bridge/ws?token=<bridge-token>
|
|
```
|
|
|
|
The WebSocket supports bidirectional messaging — send user messages to the agent and receive agent events (text, tool calls, permission requests, etc.) in real time.
|
|
|
|
### REST API
|
|
|
|
Served on the same port as the WebSocket.
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `GET` | `/bridge/sessions?session_key=...&project=...` | List sessions |
|
|
| `POST` | `/bridge/sessions` | Create a new session |
|
|
| `GET` | `/bridge/sessions/{id}?session_key=...&project=...` | Get session detail + history |
|
|
| `DELETE` | `/bridge/sessions/{id}?session_key=...&project=...` | Delete a session |
|
|
| `POST` | `/bridge/sessions/switch` | Switch active session |
|
|
|
|
Full protocol reference: [bridge-protocol.md](./bridge-protocol.md)
|
|
|
|
### Port Summary
|
|
|
|
| Service | Default Port | Config Block |
|
|
|---------|-------------|--------------|
|
|
| Management (Web UI + API) | 9820 | `[management]` |
|
|
| Bridge (WebSocket + REST) | 9810 | `[bridge]` |
|
|
|
|
---
|
|
|
|
## Configuration Reference
|
|
|
|
See [config.example.toml](../config.example.toml) for full examples.
|
|
|
|
### Project Structure
|
|
|
|
```toml
|
|
[[projects]]
|
|
name = "my-project"
|
|
|
|
[projects.agent]
|
|
type = "claudecode" # or codex, cursor, gemini, qoder, opencode, iflow
|
|
|
|
[projects.agent.options]
|
|
work_dir = "/path/to/project"
|
|
mode = "default"
|
|
provider = "anthropic"
|
|
|
|
[[projects.platforms]]
|
|
type = "feishu" # or dingtalk, telegram, slack, discord, wecom, weixin, line, qq, qqbot
|
|
|
|
[projects.platforms.options]
|
|
# platform-specific options
|
|
```
|