Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ squad-export.json
.env
.env.*
!.env.example
# Local-only Langfuse init keys (not production); needed for live-traces.ps1
!scripts/langfuse/.env
*.pem
*.key

Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ go test ./...
go build -o squad-oc.exe ./cmd/squad-oc
```

Prefer Task when it is on PATH (`task --list`): `task test`, `task build`, `task ci`, `task release` (snapshot only), `task bump TAG=vX.Y.Z`, `task langfuse:up` / `task langfuse:down`, `task live:e2e` / `task live:traces`. Real publish is `task release:tag TAG=vX.Y.Z` on main then `task release:push`. Do not edit `internal/version/version.go`. Do not use this git clone as the live serve cwd.

Live OpenCode HTTP checks need a running `opencode serve` and use the `live` build tag.

## Architecture
Expand Down
27 changes: 25 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,36 @@ go test ./...
go build -o squad-oc ./cmd/squad-oc
```

Requires **Go 1.26.6+**. Optional live check (dummy project only, never this clone as the serve cwd unless you mean to dogfood):
Or, with [Task](https://taskfile.dev) (`go install github.com/go-task/task/v3/cmd/task@latest`):

```bash
task # list
task test # go test ./...
task build # go build -o squad-oc[.exe]
task ci # local CI gate (fmt/vet/lint/race/build/vuln/actionlint)
task tools # install lint/vuln/actionlint/goreleaser
task release:check # validate .goreleaser.yaml
task release # GoReleaser snapshot into dist/ (no tag, no publish)
task bump TAG=vX.Y.Z # pin Homebrew/Scoop/winget from dist/ (no PR)
task release:tag TAG=vX.Y.Z # annotated tag on main only (no push)
task release:push TAG=vX.Y.Z # push tag; GitHub release workflow publishes
task langfuse:up
task langfuse:down
task live:e2e
task live:traces
```

Do not edit `internal/version/version.go` to bump. Do not tag a feature branch.

Requires **Go 1.26.6+**. Optional live checks (dummy project only, never this clone as the serve cwd unless you mean to dogfood):

```powershell
./scripts/live-e2e.ps1
./scripts/live-traces.ps1
# same as: task live:e2e / task live:traces
```

That talks to `opencode serve` on `127.0.0.1:4096`. The TUI (`opencode`) is not the API.
`live-e2e.ps1` talks to `opencode serve` on `127.0.0.1:4096` (doctor + PONG). `live-traces.ps1` exercises JSONL spans and optional local Langfuse OTLP ingest. The TUI (`opencode`) is not the API. CI stays `go test ./...` without live env. `task ci` is the local mirror of `.github/workflows/ci.yml`.

## Branch and PR

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ OpenCode creates `.opencode/package.json` (`@opencode-ai/plugin`) and runs an in
| `pack <path\|git-url>` | One-shot pull of extra agents/skills |
| `link <team-dir|git-url>` / `link --sync` / `link --off` | Share one team directory across several repos (git URL clones into `~/.squad-oc/links/`) |
| `update-check [--json] [--refresh]` | Prints `up to date` or `update available` vs GitHub latest tag |
| `traces [--last N] [--json] [--export file]` | Local `run` / `watch` spans; `--export` writes OTLP JSON |
| `traces [--last N] [--json] [--export file]` | Local `run` / `watch` spans (`.squad/traces/spans.jsonl`); `--export` writes OTLP JSON; optional live push via `OTEL_EXPORTER_OTLP_*` |
| `mcp apply` / `list` / `init` | Merge org `.squad/mcp-config.json` into `opencode.json` |
| `marketplace add` / `list` / `remove` / `browse` / `install` | Register a skills pack and copy a plugin into `.opencode/skills/` |
| `plugin install <name>@<marketplace>` / `list` / `uninstall <name>` | Named skill install; uninstall removes only `.opencode/skills/<name>/` |
Expand Down Expand Up @@ -133,7 +133,7 @@ squad-oc (Go)

`upgrade --self` downloads the latest GitHub Release for this OS/arch and replaces the running binary. On Windows, if the exe is locked, it writes `squad-oc.exe.new` beside it (`replaced on next start`).

`traces` lists local spans from `run` and `watch --execute`. `--export file` writes OTLP JSON any collector can ingest.
`traces` lists local spans from `run` and `watch --execute`. Default storage is `.squad/traces/spans.jsonl`. `--export file` writes OTLP JSON any collector can ingest. Set `OTEL_EXPORTER_OTLP_ENDPOINT` (or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`) to push live during `run` / `watch --execute`. Protocols: `http/protobuf` (default) and `grpc` via `OTEL_EXPORTER_OTLP_PROTOCOL`. Prompt/completion bodies always land in local JSONL; OTel message attributes only when `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` is on (default off). Langfuse local example: endpoint `http://127.0.0.1:3000/api/public/otel`, Basic Auth in `OTEL_EXPORTER_OTLP_HEADERS`, plus header `x-langfuse-ingestion-version=4`. Aspire Dashboard remains a valid OTLP **consumer**, not a shipped `squad-oc` command.

### Share extra agents (`upstream` / `pack`)

Expand Down Expand Up @@ -218,7 +218,7 @@ Original-Squad / Copilot-host pieces we are not building:

- GitHub Copilot CLI / Copilot SDK
- Interactive Ink/`squad` shell (use the OpenCode TUI)
- Aspire / .NET dashboard (traces are local OTLP JSON, not Aspire)
- Aspire / .NET dashboard (won’t ship; point any OTLP consumer at optional push or `traces --export`)

## Develop

Expand Down
191 changes: 191 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# https://taskfile.dev — local CI + Langfuse/live helpers.
# Install: go install github.com/go-task/task/v3/cmd/task@latest
version: "3"

vars:
BINARY: '{{if eq OS "windows"}}squad-oc.exe{{else}}squad-oc{{end}}'
COMPOSE: scripts/langfuse/docker-compose.yml
PWSH: '{{if eq OS "windows"}}powershell{{else}}pwsh{{end}}'

tasks:
default:
desc: List tasks
silent: true
cmds:
- task --list

tools:
desc: Install golangci-lint, govulncheck, actionlint, goreleaser (versions match CI)
cmds:
- go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2
- go install golang.org/x/vuln/cmd/govulncheck@v1.7.0
- go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.12
- go install github.com/goreleaser/goreleaser/v2@v2.17.1

tidy:
desc: go mod tidy
cmds:
- go mod tidy

cover:
desc: Unit tests with coverage profile
cmds:
- go test -coverprofile=coverage.out ./...
- go tool cover -func=coverage.out

test:
desc: Unit tests (no race, no live OpenCode)
cmds:
- go test ./...

test:race:
desc: Unit tests with -race (needs a C toolchain on Windows)
cmds:
- go test -race -count=1 ./...

vet:
desc: go vet
cmds:
- go vet ./...

lint:
desc: golangci-lint (same timeout as CI)
cmds:
- golangci-lint run --timeout=4m

fmt:
desc: Apply gofmt to all .go files
cmds:
- gofmt -w .

fmt:check:
desc: Fail if gofmt would change files (Linux/macOS; CI). Skipped on Windows (CRLF false positive).
cmds:
- task: fmt:check:windows
- task: fmt:check:unix

fmt:check:windows:
internal: true
platforms: [windows]
cmds:
- cmd /c echo skip gofmt -l on Windows CI still checks

fmt:check:unix:
internal: true
platforms: [linux, darwin]
cmds:
- |
files=$(gofmt -l .)
if [ -n "$files" ]; then
echo unformatted:
echo "$files"
exit 1
fi

build:
desc: Build squad-oc
cmds:
- go build -o {{.BINARY}} ./cmd/squad-oc

install:
desc: go install ./cmd/squad-oc onto GOPATH/bin
cmds:
- go install ./cmd/squad-oc

clean:
desc: Remove local binary, dist/, and coverage.out
cmds:
- '{{if eq OS "windows"}}cmd /c if exist {{.BINARY}} del /q {{.BINARY}} & if exist dist rmdir /s /q dist & if exist coverage.out del /q coverage.out{{else}}rm -rf {{.BINARY}} dist coverage.out{{end}}'

vuln:
desc: govulncheck
cmds:
- govulncheck ./...

actionlint:
desc: Lint GitHub workflow YAML
cmds:
- actionlint

ci:
desc: Local CI gate matching .github/workflows/ci.yml
deps: [fmt:check, vet, lint, test:race, build, vuln, actionlint]

langfuse:up:
desc: Start local Langfuse v4 on 127.0.0.1:3000
cmds:
- docker compose -f {{.COMPOSE}} up -d

langfuse:down:
desc: Stop local Langfuse (keeps volumes)
cmds:
- docker compose -f {{.COMPOSE}} down

langfuse:down:volumes:
desc: Stop local Langfuse and remove squad-oc-langfuse-* volumes
cmds:
- docker compose -f {{.COMPOSE}} down -v

live:e2e:
desc: Dummy PONG check (D:\xAI\squad-oc-dummy only)
cmds:
- "{{.PWSH}} -File scripts/live-e2e.ps1"

live:traces:
desc: Dummy traces + optional local Langfuse OTLP
cmds:
- "{{.PWSH}} -File scripts/live-traces.ps1"

live:traces:skip-langfuse:
desc: Dummy traces only (no Langfuse)
cmds:
- "{{.PWSH}} -File scripts/live-traces.ps1 -SkipLangfuse"

live:models:
desc: Dummy per-agent model pin check
cmds:
- "{{.PWSH}} -File scripts/live-models.ps1"

release:check:
desc: Validate .goreleaser.yaml
cmds:
- goreleaser check

release:
desc: Local GoReleaser snapshot into dist/ (no tag, no GitHub publish)
cmds:
- goreleaser release --snapshot --clean

release:tag:
desc: Annotated tag TAG=vX.Y.Z on main HEAD only (does not push)
requires:
vars: [TAG]
cmds:
- task: release:tag:windows
- task: release:tag:unix

release:tag:windows:
internal: true
platforms: [windows]
cmds:
- powershell -NoProfile -File scripts/release-tag.ps1 -Tag {{.TAG}}

release:tag:unix:
internal: true
platforms: [linux, darwin]
cmds:
- bash scripts/release-tag.sh {{.TAG}}

release:push:
desc: Push existing tag TAG=vX.Y.Z to origin (starts GitHub release workflow)
requires:
vars: [TAG]
cmds:
- git push origin {{.TAG}}

bump:
desc: Pin Homebrew/Scoop/winget from dist/ (TAG=vX.Y.Z). Does not open a PR.
requires:
vars: [TAG]
cmds:
- bash -c "TAG='{{.TAG}}' REPO='{{.REPO | default "xeaser/squad-opencode"}}' DIST='{{.DIST | default "dist"}}' scripts/copy-packaging-from-dist.sh"
4 changes: 2 additions & 2 deletions docs/get-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Inside OpenCode:
3. Paste your API key
4. Quit when done

Optional: pin a seat to an OpenCode id (`provider/model-id`) with `squad-oc cast --model squad xai/grok-3` or `cast --add NAME --model anthropic/claude-sonnet-4-5`. Empty cells inherit the Squad model, then the session default. This does not write `opencode.json`.
Optional: pin a seat to an OpenCode id (`provider/model-id`) with `squad-oc cast --model squad opencode/big-pickle` or `cast --add NAME --model opencode/hy3-free`. Use an id from `opencode models` on this machine — `xai/…` and `anthropic/…` only work after `/connect` for that provider. Empty cells inherit the Squad model, then the session default. This does not write `opencode.json`.

---

Expand Down Expand Up @@ -186,7 +186,7 @@ The script builds `squad-oc.exe`, inits `D:\xAI\squad-oc-dummy` if needed, start
- `squad-oc watch --health` prints the last Ralph snapshot
- `squad-oc brief` prints the morning listing (PRs, tickets, last done, next). Soft if `gh` is missing.
- `squad-oc watch --execute --overnight-start 18:00 --overnight-end 08:00`
- `squad-oc traces` lists local run/watch spans (`--export` writes OTLP JSON)
- `squad-oc traces` lists local run/watch spans (default: `.squad/traces/spans.jsonl`; `--export` writes OTLP JSON). File is the default; set `OTEL_EXPORTER_OTLP_ENDPOINT` (or traces-specific) to push during `run` / `watch --execute`. Protocols: `http/protobuf` (default) and `grpc` via `OTEL_EXPORTER_OTLP_PROTOCOL`. Bodies always in local JSONL; OTel message content only if `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` is on (default off). Langfuse local: endpoint `http://127.0.0.1:3000/api/public/otel`, Basic Auth in `OTEL_EXPORTER_OTLP_HEADERS`, header `x-langfuse-ingestion-version=4`. Aspire standalone can consume OTLP; it is not a shipped command.
- `squad-oc run -p "…"` starts `opencode serve` on `127.0.0.1:4096` if nothing is there; a custom `--url` never auto-starts
- `squad-oc pack <path|git-url>` or `squad-oc upstream add <name> <path|git-url>` to pull extra agents/skills (see README)
- `squad-oc mcp init` / `apply` / `list` — org `.squad/mcp-config.json` into `opencode.json` (workshop §8)
Expand Down
4 changes: 2 additions & 2 deletions docs/use-cases.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Matches README **Non-goals** (plus the memory MCP the roadmap refuses):
|------------------------------|---------|
| GitHub Copilot CLI / Copilot SDK | Different host. OpenCode TUI + `opencode serve`. |
| Interactive Ink / `squad` shell | Use the OpenCode TUI. |
| Aspire / .NET dashboard | `squad-oc traces` is local OTLP JSON, not Aspire. |
| Aspire / .NET dashboard | Won’t ship a dashboard. Local JSONL + optional OTLP push; Aspire standalone can still consume OTLP. |
| `squad_state` memory MCP | OpenCode + `.squad/` files remain memory. |

Also not product features (workshop may mention them as **example** org MCP servers after P1): WorkIQ, Outlook COM, Teams Adaptive Cards.
Expand Down Expand Up @@ -101,7 +101,7 @@ Each original-Squad ease row is a **squad-oc command** you already ran, a **late
| Snapshot team files | `export` / `import` |
| Move team out of the worktree | `externalize` / `internalize` |
| Context / PII hygiene | `nap` / `scrub-emails` |
| Local spans | `squad-oc traces` |
| Local spans / observability | `squad-oc traces` (`.squad/traces/spans.jsonl`) + optional OTLP push via `OTEL_EXPORTER_OTLP_ENDPOINT` |
| Drop-in `mcp-config.json` | `squad-oc mcp init` / `apply` / `list` |
| Marketplace browse + install | `squad-oc marketplace browse` / `install` |
| Office / themed names | `squad-oc init --theme office` (native `@michael`) or later `cast --theme office` (mention map; `@lead` gone) |
Expand Down
26 changes: 25 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,35 @@ module github.com/xeaser/squad-opencode

go 1.26.6

require github.com/sst/opencode-sdk-go v0.19.2
require (
github.com/sst/opencode-sdk-go v0.19.2
go.opentelemetry.io/otel v1.46.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.46.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0
go.opentelemetry.io/otel/sdk v1.46.0
go.opentelemetry.io/otel/trace v1.46.0
)

require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/go-logr/logr v1.4.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect
github.com/tidwall/gjson v1.14.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 // indirect
go.opentelemetry.io/otel/metric v1.46.0 // indirect
go.opentelemetry.io/proto/otlp v1.11.0 // indirect
golang.org/x/net v0.58.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/text v0.41.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect
google.golang.org/grpc v1.83.1 // indirect
google.golang.org/protobuf v1.36.12 // indirect
)
Loading