Commit 2c06a2c
Add session name & tags to browsers update (kernel-go-sdk v0.66.0) (#184)
## Summary
Bumps `kernel-go-sdk` v0.65.0 → v0.66.0, which adds `Name`/`Tags` to
`BrowserUpdateParams` (PATCH /browser), and uses it to give `browsers
update <id-or-name>` the `--name` / `--clear-name` / `--tag KEY=VALUE` /
`--clear-tags` flags that #177 had left creation-only. The flags mirror
the existing `--clear-proxy` convention and honor the SDK semantics
exactly (omit = unchanged, empty = clear, tags = full replace), with
validation that rejects `--name`/`--clear-name` and
`--tag`/`--clear-tags` conflicts, guards against an accidental `--name
""`, and stays correct via a `SetName`/`SetTags` flag signal even when a
malformed `--tag` is dropped to an empty map. Also refreshes the
now-stale `create --name` help and the `update --tag` cap in
help/README, and adds unit tests across the new flags, clear/omit
marshaling, and the malformed-tag edge cases.
## Test Plan
- [x] `go build ./...`, `go vet ./cmd/`, `gofmt` — clean
- [x] `go test ./...` — passing (16 new `TestBrowsersUpdate_*` cases)
- [x] Binary exercised end-to-end: `--help` shows flags; every
validation path errors before any API call
- [x] **Live round-trip verified against a local Kernel**
(`localhost:3001`): 18/19 🌐 rows pass, **0 product defects** —
full-replace tags, `--name`-only does not clobber tags (omit =
unchanged), clear name/tags, by-id/by-name resolution, not-found, JSON
echo, name+telemetry, and the 50-tag cap (`Invalid_tags: too many tags:
51 (maximum 50)`). Sessions were cleaned up. The one unrun row, B3
(rename-collision between two **concurrent** active sessions), isn't
exercisable on the local stack (it returns `Capacity_exhausted` for a
second simultaneous browser); name uniqueness is server-enforced and the
CLI only relays the error.
## Manual test matrix
**Status:** ✅ = verified locally (path returns before any API call, or
pinned by a named unit test asserting the exact forwarded params / JSON)
· 🌐 = needs a live Kernel API round-trip (not yet run here).
**Notes**
- *Error display:* `pterm` capitalizes the first letter (`Cannot
specify…`, `--Name requires…`); the raw `fmt.Errorf` strings are
lowercase — same error.
- *Success output:* the `Name:` / `Tags:` lines echo the **server's
returned** values, not the request. Unit tests pin the **request body**
(✅); the displayed value is a 🌐 observation — except after a *clear*,
where `Name: -` / `Tags: -` is correct regardless.
- *Scope:* `disable_default_proxy` (also new in v0.66) is intentionally
**not** exposed by this PR.
**Preconditions for 🌐 rows:** one session with a known starting name +
tags (`browsers create --name mtx-base --tag a=1 --tag team=qa`); D2
needs the session to start with `{a=1}` then run `--tag b=2`; B2/B3 need
a second session (`--name mtx-taken`) so a colliding active name exists.
### A. Build / help
| # | Scenario | Command | Expected | Status |
|---|----------|---------|----------|--------|
| A1 | Builds & flags wired | `go build ./cmd/kernel` | Builds clean | ✅
|
| A2 | Help lists new flags | `browsers update --help` | Shows `--name`,
`--clear-name`, `--tag`, `--clear-tags`; the `--tag` line carries the
full-replace + "up to 50 pairs" notes | ✅ |
| A3 | `Long` help documents ops | `browsers update --help` | Long text
lists rename/clear-name and replace/clear-tags, and the "--tag replaces
the entire tag set" note | ✅ |
### B. Set name
| # | Scenario | Command | Expected | Status |
|---|----------|---------|----------|--------|
| B1 | Set/rename | `browsers update <id> --name new-name` | Request
body `{"name":"new-name"}` ✅; output `Updated browser <id>` then `Name:
new-name` (value echoed from server response) 🌐 | ✅ (request body
unit-tested); 🌐 (displayed value + persistence) |
| B2 | Rename resolves by current name | `browsers update old-name
--name newer` | Resolves session by name, applies rename | 🌐 |
| B3 | Rename to a name used by another active session | `browsers
update <id> --name taken` | API rejects: `Conflict: browser session name
already exists` | 🌐 |
### C. Clear name
| # | Scenario | Command | Expected | Status |
|---|----------|---------|----------|--------|
| C1 | Clear name | `browsers update <id> --clear-name` | Request body
`{"name":""}`; output `Name: -` | ✅ (request body unit-tested); 🌐
(persisted: get shows no name) |
| C2 | `--name ""` rejected (use --clear-name) | `browsers update <id>
--name ""` | Error: `--name requires a non-empty value; use --clear-name
to clear the name` | ✅ |
| C3 | `--name=` (explicit empty) rejected | `browsers update <id>
--name=` | Same error as C2 | ✅ |
### D. Set tags (full replace)
| # | Scenario | Command | Expected | Status |
|---|----------|---------|----------|--------|
| D1 | Replace tag set | `browsers update <id> --tag team=backend --tag
env=prod` | Request body `{"tags":{"team":"backend","env":"prod"}}` ✅;
output `Tags: env=prod, team=backend` (sorted; echoed from server
response) 🌐 | ✅ (request body unit-tested); 🌐 (displayed value +
persistence) |
| D2 | Full-replace semantics (not merge) | Session has `{a=1}`, run
`--tag b=2` | Resulting tags are `{b=2}` only — `a` is gone | 🌐 |
| D3 | Special-char tag key round-trips | `browsers update <id> --tag
region.us=1` | Body contains `"region.us":"1"` | ✅ (parser unit-tested);
🌐 (persisted) |
| D4 | 50-tag cap | `--tag` ×51 | API rejects over-limit
(server-enforced) | 🌐 |
| D5 | Tag value containing `=` | `browsers update <id> --tag url=a=b` |
Splits on first `=` only → body `{"tags":{"url":"a=b"}}` (not dropped as
malformed) | ✅ (parser unit-tested: `k=v=w`→`k:"v=w"`); 🌐 (persisted) |
### E. Clear tags
| # | Scenario | Command | Expected | Status |
|---|----------|---------|----------|--------|
| E1 | Clear all tags | `browsers update <id> --clear-tags` | Body
`{"tags":{}}`; output `Tags: -` | ✅ (request body unit-tested); 🌐
(persisted: get shows no tags) |
### F. Combined name + tags
| # | Scenario | Command | Expected | Status |
|---|----------|---------|----------|--------|
| F1 | Set name + set tags together | `browsers update <id> --name combo
--tag k=v` | Body has both `"name":"combo"` and `"tags":{"k":"v"}` | ✅
(unit-tested); 🌐 (persisted) |
| F2 | Clear name + set tags | `browsers update <id> --clear-name --tag
env=prod` | Body `{"name":"","tags":{"env":"prod"}}` | ✅ (unit-tested) |
| F3 | Set name + clear tags | `browsers update <id> --name renamed
--clear-tags` | Body `{"name":"renamed","tags":{}}` | ✅ (unit-tested) |
### G. Combine with existing update flags
| # | Scenario | Command | Expected | Status |
|---|----------|---------|----------|--------|
| G1 | Name/tags + proxy in one call | `browsers update <id> --proxy-id
<pid> --name combo --tag k=v` | All three forwarded: `proxy_id`, `name`,
`tags` | ✅ (unit-tested); 🌐 (persisted) |
| G2 | Name + telemetry | `browsers update <id> --name n
--telemetry=all` | Name applied and telemetry summary printed | 🌐 |
| G3 | Partial update does NOT clobber unrelated fields | Session has
tags + proxy; run `--name` only | Only name changes; tags & proxy
untouched (omit = unchanged) | ✅ (omit-not-sent unit-tested); 🌐
(persisted) |
### H. Conflict & "at least one" validation
| # | Scenario | Command | Expected | Status |
|---|----------|---------|----------|--------|
| H1 | `--name` + `--clear-name` | `browsers update <id> --name x
--clear-name` | Error: `cannot specify both --name and --clear-name` | ✅
|
| H2 | `--tag` + `--clear-tags` | `browsers update <id> --tag a=1
--clear-tags` | Error: `cannot specify both --tag and --clear-tags` | ✅
|
| H3 | No options at all | `browsers update <id>` | Error: `must specify
at least one of: …` (full list of 10 flags, ending `--name,
--clear-name, --tag, or --clear-tags`) — substring check | ✅ |
| H4 | Name-only satisfies "at least one" | `browsers update <id> --name
x` | Proceeds (no "at least one" error) | ✅ |
| H5 | Tags-only satisfies "at least one" | `browsers update <id> --tag
k=v` | Proceeds | ✅ (param path); 🌐 (persisted) |
### I. Malformed-tag edge cases
| # | Scenario | Command | Expected | Status |
|---|----------|---------|----------|--------|
| I1 | All `--tag` values malformed, alone | `browsers update <id> --tag
foo` | Warns `Ignoring malformed tag: foo`, then error `no valid --tag
KEY=VALUE pairs provided` (not the generic "at least one") | ✅ |
| I2 | Malformed `--tag` + `--clear-tags` still conflicts | `browsers
update <id> --tag foo --clear-tags` | Warns, then error `cannot specify
both --tag and --clear-tags` (does NOT silently clear) | ✅ |
| I3 | Partially malformed list warns & continues (documented sharp
edge) | `browsers update <id> --tag a=1 --tag bad` | Warns on `bad`,
replaces tag set with `{a=1}` only | ✅ (parser unit-tested); 🌐
(persisted) |
### J. ID-vs-name resolution & errors
| # | Scenario | Command | Expected | Status |
|---|----------|---------|----------|--------|
| J1 | Update by ID | `browsers update <cuid> --name n` | Resolves by ID
| 🌐 |
| J2 | Update by name | `browsers update <name> --name n2` | Resolves by
name | 🌐 |
| J3 | Not found | `browsers update nonexistent --name n` | Clean
not-found error | 🌐 |
### K. JSON output
| # | Scenario | Command | Expected | Status |
|---|----------|---------|----------|--------|
| K1 | JSON echoes persisted name/tags | `browsers update <id> --name n
--tag k=v -o json` | Prints full `BrowserUpdateResponse` JSON including
`name` and `tags` | 🌐 |
| K2 | `-o yaml` rejected | `browsers update <id> --name n -o yaml` |
Error: `unsupported --output value "yaml"; use "json"…` | ✅ |
### L. Round-trip / persistence
| # | Scenario | Command | Expected | Status |
|---|----------|---------|----------|--------|
| L1 | `get` reflects rename | update `--name n` → `browsers get <id>` |
Detail table + JSON show new name | 🌐 |
| L2 | `get` reflects tag replace | update `--tag k=v` → `browsers get
<id>` | Shows exactly the new tag set | 🌐 |
| L3 | `get`/`list` reflect clears | `--clear-name`/`--clear-tags` →
`get` & `list --query` | No name / no tags after clear; `list --tag` no
longer matches | 🌐 |
| L4 | `list --tag` filter after replace | replace tags → `browsers list
--tag k=v` | Session appears under the new tag, not the old | 🌐 |
| L5 | `list` Name column after rename | `--name newname` → `browsers
list` | Name column shows `newname` for that session | 🌐 |
> Display asymmetry (L1–L3): `update`'s success output prints `Name: -`
/ `Tags: -` after a clear, but `get`'s detail table **omits** the
Name/Tags rows entirely when empty. Both mean "no name / no tags" — a
missing `get` row is not a failure.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> CLI-only changes to browser session metadata with local validation and
tests; API behavior is delegated to the bumped SDK.
>
> **Overview**
> Bumps **kernel-go-sdk** to **v0.66.0** and wires `browsers update` to
the new PATCH fields for session **name** and **tags**, so renaming and
retagging no longer require recreating a session.
>
> `kernel browsers update` gains **`--name` / `--clear-name`** and
**`--tag` / `--clear-tags`**, following the same omit-vs-clear pattern
as **`--clear-proxy`**: fields are omitted when unchanged, empty values
clear, and **`--tag` fully replaces** the tag set (not a merge).
Client-side validation rejects conflicting flag pairs, empty
**`--name`**, and malformed-only **`--tag`** input (using **`SetName` /
`SetTags`** so edge cases still fail before the API). Success output
echoes **Name** / **Tags** when those were updated; README and **`create
--name`** help note that names can be changed via update.
>
> Adds broad **`TestBrowsersUpdate_*`** coverage for param forwarding,
JSON marshaling, and validation edge cases.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
feb115c. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>1 parent 9fdb40f commit 2c06a2c
5 files changed
Lines changed: 345 additions & 8 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
213 | 213 | | |
214 | 214 | | |
215 | 215 | | |
216 | | - | |
| 216 | + | |
217 | 217 | | |
218 | 218 | | |
219 | 219 | | |
| |||
230 | 230 | | |
231 | 231 | | |
232 | 232 | | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
233 | 237 | | |
234 | 238 | | |
235 | 239 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
298 | 298 | | |
299 | 299 | | |
300 | 300 | | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
301 | 307 | | |
302 | 308 | | |
303 | 309 | | |
| |||
697 | 703 | | |
698 | 704 | | |
699 | 705 | | |
| 706 | + | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
| 713 | + | |
| 714 | + | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
| 718 | + | |
| 719 | + | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
| 728 | + | |
| 729 | + | |
| 730 | + | |
700 | 731 | | |
701 | 732 | | |
702 | 733 | | |
| 734 | + | |
| 735 | + | |
| 736 | + | |
703 | 737 | | |
704 | 738 | | |
705 | 739 | | |
| |||
712 | 746 | | |
713 | 747 | | |
714 | 748 | | |
715 | | - | |
716 | | - | |
| 749 | + | |
| 750 | + | |
717 | 751 | | |
718 | 752 | | |
719 | 753 | | |
720 | 754 | | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
| 760 | + | |
| 761 | + | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
721 | 770 | | |
722 | 771 | | |
723 | 772 | | |
| |||
781 | 830 | | |
782 | 831 | | |
783 | 832 | | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
784 | 839 | | |
785 | 840 | | |
786 | 841 | | |
| |||
2338 | 2393 | | |
2339 | 2394 | | |
2340 | 2395 | | |
| 2396 | + | |
| 2397 | + | |
2341 | 2398 | | |
2342 | | - | |
| 2399 | + | |
| 2400 | + | |
| 2401 | + | |
2343 | 2402 | | |
2344 | 2403 | | |
2345 | 2404 | | |
| |||
2379 | 2438 | | |
2380 | 2439 | | |
2381 | 2440 | | |
| 2441 | + | |
| 2442 | + | |
| 2443 | + | |
| 2444 | + | |
2382 | 2445 | | |
2383 | 2446 | | |
2384 | 2447 | | |
| |||
2645 | 2708 | | |
2646 | 2709 | | |
2647 | 2710 | | |
2648 | | - | |
| 2711 | + | |
2649 | 2712 | | |
2650 | 2713 | | |
2651 | 2714 | | |
| |||
2921 | 2984 | | |
2922 | 2985 | | |
2923 | 2986 | | |
| 2987 | + | |
| 2988 | + | |
| 2989 | + | |
| 2990 | + | |
2924 | 2991 | | |
2925 | 2992 | | |
2926 | 2993 | | |
| |||
2934 | 3001 | | |
2935 | 3002 | | |
2936 | 3003 | | |
| 3004 | + | |
| 3005 | + | |
| 3006 | + | |
| 3007 | + | |
| 3008 | + | |
| 3009 | + | |
2937 | 3010 | | |
2938 | 3011 | | |
2939 | 3012 | | |
| |||
0 commit comments