feat: add coder exp sync list command (#26443)

Add a new subcommand to list all registered sync units and their current
statuses. This provides a quick overview of the dependency coordination
state in a workspace without needing to query each unit individually.

The command supports both table (default) and JSON output formats.

```
$ coder exp sync list
UNIT    STATUS     READY
unit-a  started    true
unit-b  completed  true
unit-c  pending    false

$ coder exp sync list --output json
[
  {
    "unit_name": "my-unit",
    "status": "started",
    "is_ready": true
  }
]
```

When no units are registered, the command prints `No units registered`.

<details><summary>Changes across layers</summary>

- `agent/unit`: add `Manager.ListUnits()` method
- `agent/agentsocket/proto`: add `SyncList` RPC, bump API to v1.2
- `agent/agentsocket`: add service and client implementations
- `cli`: add `sync_list.go` command, register in `sync.go`
- Tests: three golden-file test cases (empty list, multiple units, JSON)

</details>

> Generated by Coder Agents on behalf of @SasSwart

---------

Co-authored-by: Cian Johnston <cian@coder.com>
This commit is contained in:
Sas Swart
2026-06-17 13:01:57 +02:00
committed by GitHub
co-authored by Cian Johnston
parent 74a7ad0929
commit 7d95153bf4
18 changed files with 553 additions and 39 deletions
+77
View File
@@ -409,4 +409,81 @@ func TestSyncCommands_Golden(t *testing.T) {
clitest.TestGoldenFile(t, "TestSyncCommands_Golden/status_json_format", outBuf.Bytes(), nil)
})
t.Run("list_no_units", func(t *testing.T) {
t.Parallel()
path, cleanup := setupSocketServer(t)
defer cleanup()
ctx := testutil.Context(t, testutil.WaitShort)
var outBuf bytes.Buffer
inv, _ := clitest.New(t, "exp", "sync", "list", "--socket-path", path)
inv.Stdout = &outBuf
inv.Stderr = &outBuf
err := inv.WithContext(ctx).Run()
require.NoError(t, err)
clitest.TestGoldenFile(t, "TestSyncCommands_Golden/list_no_units", outBuf.Bytes(), nil)
})
t.Run("list_with_units", func(t *testing.T) {
t.Parallel()
path, cleanup := setupSocketServer(t)
defer cleanup()
ctx := testutil.Context(t, testutil.WaitShort)
// Register some units in various states.
client, err := agentsocket.NewClient(ctx, agentsocket.WithPath(path))
require.NoError(t, err)
// unit-a: started
err = client.SyncStart(ctx, "unit-a")
require.NoError(t, err)
// unit-b: completed
err = client.SyncStart(ctx, "unit-b")
require.NoError(t, err)
err = client.SyncComplete(ctx, "unit-b")
require.NoError(t, err)
// unit-c: pending (has unsatisfied dependency on unit-a completing)
err = client.SyncWant(ctx, "unit-c", "unit-a")
require.NoError(t, err)
client.Close()
var outBuf bytes.Buffer
inv, _ := clitest.New(t, "exp", "sync", "list", "--socket-path", path)
inv.Stdout = &outBuf
inv.Stderr = &outBuf
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
clitest.TestGoldenFile(t, "TestSyncCommands_Golden/list_with_units", outBuf.Bytes(), nil)
})
t.Run("list_json_format", func(t *testing.T) {
t.Parallel()
path, cleanup := setupSocketServer(t)
defer cleanup()
ctx := testutil.Context(t, testutil.WaitShort)
// Register a unit.
client, err := agentsocket.NewClient(ctx, agentsocket.WithPath(path))
require.NoError(t, err)
err = client.SyncStart(ctx, "my-unit")
require.NoError(t, err)
client.Close()
var outBuf bytes.Buffer
inv, _ := clitest.New(t, "exp", "sync", "list", "--output", "json", "--socket-path", path)
inv.Stdout = &outBuf
inv.Stderr = &outBuf
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
clitest.TestGoldenFile(t, "TestSyncCommands_Golden/list_json_format", outBuf.Bytes(), nil)
})
}