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
5 changes: 3 additions & 2 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 (`.squad/traces/spans.jsonl`); `--export` writes OTLP JSON; optional live push via `OTEL_EXPORTER_OTLP_*` |
| `traces [--last N] [--json] [--export file] [--follow]` | Local spans plus OpenCode SQLite ingest (TUI/Web/serve); `--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 All @@ -111,6 +111,7 @@ internal/watch/ # issue triage (Ralph): health, overnight, backends
internal/githubissues/ # gh issue list
internal/share/ # upstream / pack / link
internal/traces/ # local JSONL spans + OTLP export
internal/opencodestore/ # read-only OpenCode SQLite (TUI ingest)
internal/selfupdate/ # upgrade --self
internal/updatecheck/
internal/version/
Expand All @@ -133,7 +134,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`. 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.
`traces` lists local spans from `run` and `watch --execute`, and ingests completed TUI/Web/serve turns from OpenCode SQLite (default `$XDG_DATA_HOME/opencode/opencode.db` or `~/.local/share/opencode/opencode.db` on every OS, including Windows `%USERPROFILE%\.local\share\opencode\opencode.db`). Override with `OPENCODE_DB` or `opencode_db` in `.squad/config.json`. Dedup is by assistant `messageId` so a later TUI turn on a CLI session still ingests. `--follow` polls every 2s. `--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` and on ingest. 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 groups turns by OpenCode `ses_*` (`session.id`). For in-process streaming, the official Langfuse OpenCode plugin is optional and separate; we do not install it. Aspire Dashboard remains a valid OTLP **consumer**, not a shipped `squad-oc` command.

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

Expand Down
2 changes: 1 addition & 1 deletion docs/get-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ Plain `opencode` opens the interactive UI and does **not** listen on 4096.
- `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 (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 traces` lists local run/watch spans and ingests completed TUI/Web/serve turns from OpenCode SQLite (default `~/.local/share/opencode/opencode.db` / `%USERPROFILE%\.local\share\opencode\opencode.db`; override `OPENCODE_DB` or `opencode_db`). `--follow` polls every 2s. `--export` writes OTLP JSON. Set `OTEL_EXPORTER_OTLP_ENDPOINT` (or traces-specific) to push during `run` / `watch --execute` and ingest. 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`. Same Langfuse session id is the OpenCode `ses_*`. The official Langfuse OpenCode plugin is optional for in-process streaming; we do not install it. 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
2 changes: 1 addition & 1 deletion docs/use-cases.md
Original file line number Diff line number Diff line change
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 / observability | `squad-oc traces` (`.squad/traces/spans.jsonl`) + optional OTLP push via `OTEL_EXPORTER_OTLP_ENDPOINT` |
| Local spans / observability | `squad-oc traces` (JSONL + OpenCode SQLite ingest for TUI/Web/serve) + optional OTLP; Langfuse plugin optional for streaming |
| 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
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,20 @@ require (
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
modernc.org/sqlite v1.57.0
)

require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dustin/go-humanize v1.0.1 // 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/mattn/go-isatty v0.0.24 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // 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
Expand All @@ -33,4 +38,7 @@ require (
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
modernc.org/libc v1.74.4 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
)
46 changes: 46 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1x
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
Expand All @@ -11,10 +13,20 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo=
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 h1:/Tnpcb2E0Pz/tN9s3bfEY2Q8ePCEX9iuS+cneUwncnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0/go.mod h1:zOBXOsUaBSjKgmH4OGzV1esUpR3oUSCPYVd2cUBjKYY=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/sst/opencode-sdk-go v0.19.2 h1:ffgQpE+ms4F0Wop/tT4tqTvFAbocyWYM8iy543b3Ous=
github.com/sst/opencode-sdk-go v0.19.2/go.mod h1:rrpo5n0Be43y6tJ29TeMxH1/zeoDcB0D43nJh6gnL34=
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
Expand Down Expand Up @@ -53,12 +65,18 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI=
Expand All @@ -69,3 +87,31 @@ google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y=
google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI=
modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI=
modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.74.4 h1:fX1Omw4o2/1C2iRkkIsrQTasJQldLhRmuPreXLoWs9k=
modernc.org/libc v1.74.4/go.mod h1:eeQAS9W3sZeKYMFubydxJpII9ybHWshk+7or7bLG9co=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.57.0 h1:qNQP6xnx5M0ISNtlnxoOX0+cD5bJ0/gr9aMmndFczzg=
modernc.org/sqlite v1.57.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
56 changes: 55 additions & 1 deletion internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import (
"errors"
"fmt"
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"

"github.com/xeaser/squad-opencode/internal/brief"
Expand Down Expand Up @@ -132,7 +134,7 @@ Commands:
link --sync
link --off
update-check [--json] [--refresh]
traces [--last N] [--json] [--export file] # local spans.jsonl; OTEL_EXPORTER_OTLP_* optional push
traces [--last N] [--json] [--export file] [--follow] # JSONL + OpenCode SQLite ingest; OTEL optional
mcp apply | list | init
marketplace add <name> <path|git-url> | list | remove <name> | browse [name] | install <plugin> [--from <name>]
plugin install <name>@<marketplace> | list | uninstall <name>
Expand Down Expand Up @@ -1059,11 +1061,14 @@ func cmdTraces(args []string) int {
last := 20
asJSON := false
exportPath := ""
follow := false
for i := 0; i < len(args); i++ {
a := args[i]
switch {
case a == "--json":
asJSON = true
case a == "--follow":
follow = true
case a == "--last" && i+1 < len(args):
i++
n, err := strconv.Atoi(args[i])
Expand Down Expand Up @@ -1093,6 +1098,12 @@ func cmdTraces(args []string) int {
if code != 0 {
return code
}
if follow {
return tracesFollowFn(root)
}
if code := tracesIngestOnce(root); code != 0 {
return code
}
spans, err := traces.List(root, last)
if err != nil {
fmt.Fprintln(os.Stderr, err)
Expand Down Expand Up @@ -1123,6 +1134,49 @@ func cmdTraces(args []string) int {
return 0
}

var tracesFollowFn = tracesFollow

// tracesIngestPush is the OTLP export hook for CLI ingest; tests replace it.
var tracesIngestPush = traces.Push

func tracesIngestOnce(root string) int {
_, err := traces.Ingest(root, squad.Detect(root).Config, os.Getenv, tracesIngestPush)
if err != nil {
if traces.IsOTLPPushError(err) {
fmt.Fprintln(os.Stderr, "traces:", err)
return 0
}
fmt.Fprintln(os.Stderr, err)
return 1
}
return 0
}

func tracesFollow(root string) int {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
for {
n, err := traces.Ingest(root, squad.Detect(root).Config, os.Getenv, tracesIngestPush)
if err != nil {
if traces.IsOTLPPushError(err) {
fmt.Fprintln(os.Stderr, "traces:", err)
// JSONL kept; keep polling
} else {
fmt.Fprintln(os.Stderr, err)
return 1
}
}
if n > 0 {
fmt.Printf("ingested %d\n", n)
}
select {
case <-ctx.Done():
return 0
case <-time.After(2 * time.Second):
}
}
}

func cmdMCP(args []string) int {
if len(args) < 1 {
fmt.Fprintln(os.Stderr, "mcp apply|list|init")
Expand Down
Loading