Skip to content

feat(cli): share surface — mir share ls/revoke, guest state, overview (G1d) - #103

Merged
frahlg merged 1 commit into
mainfrom
103-g1d-cli
Aug 30, 2026
Merged

feat(cli): share surface — mir share ls/revoke, guest state, overview (G1d)#103
frahlg merged 1 commit into
mainfrom
103-g1d-cli

Conversation

@frahlg

@frahlg frahlg commented Aug 30, 2026

Copy link
Copy Markdown
Member

The CLI surface of guest sharing (G1 spec §6). Part of #55.

Owner side

  • Every mint is recorded under <dir>/shares/<gid>.json (the agent's store stays the authority). mir share ls:
3f2a1b9c  sharebox         read-only  main     expires in 42 min
77ac01de  officebox        read-write main     revoked
  • mir share revoke <id-prefix> — unambiguous-prefix resolution, tombstone delivered over an authenticated attach (G1c's RevokeGrantOverSession), local record flipped, idempotent re-run says "already revoked".
  • Offline-revoke behavior chosen: revocation is agent-local (§4), so an unreachable machine means the revoke has NOT happened. The refusal says exactly that — plus the honest counterpart from the spec's own reasoning: while the machine is offline the guest cannot reach it either, and the TTL backstop time. No pending-revoke queue in v1 (no daemon to drain it); re-run when the machine is back.

Guest side

  • mir ls: a share renders as sharebox <id> shared with you · read-only · expires in 42 min — never as an owned machine.
  • mir attach checks the grant's clock before dialing: an expired share gets "your share of X has ended — ask the owner for a new invite" instead of the agent's silent refusal reading as "offline".
  • Shares whose window has fully closed (na + skew) age out of the local store on mir ls (machine entry + grant file).

Overview (O1)

 ▸ ⇢ teambox
     shared with you · read-only · expires in 42 min
  • s on an owned machine leaves the alt screen + raw mode, runs the mint ceremony (defaults: ro, 1 h, main — flags need the command form), and returns to the overview; hint bar now reads enter attach · s share · r rename · x retire · q quit · ? help.
  • s/r/x on a share answer with a quiet status hint (only the owner shares onward / renames; it expires on its own).

Tests — the full live loop on the hermetic harness: mint → join → guest ls flagged → share ls shows the id → share revoke <prefix> lands on the live agent → local flag → idempotent re-revoke. Plus: prefix resolution (unknown/ambiguous), sweep table (live kept, closed removed, owned untouched), expired-share attach refusal (no dial), overview shared-row render, expiry phrasing table. G1b/G1c E2E suites stay green as the gate. go test ./... green, gofmt -l empty, go vet clean, web 157/157; no vector changes; mir doctor untouched.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KeiotDVE94wEzvc7wcvm1y


Note

Medium Risk
Touches guest access paths (attach pre-checks, revoke over live sessions, local grant bookkeeping); agent remains authoritative but mis-revoke or stale local state could confuse users until re-sync.

Overview
This PR completes the G1d CLI surface for time-boxed guest sharing: owners can see and end invites; guests see shares distinctly and get clear errors when access has ended.

Owners get mir share ls (mints recorded under local shares/) and mir share revoke <id-prefix> with prefix resolution, agent tombstone via RevokeGrantOverSession, and idempotent “already revoked” handling. Offline revoke is explicit: the agent must ack; unreachable machines get honest copy about TTL and guest reachability. Successful mints are persisted locally; help text documents the new subcommands.

Guests see shared machines in mir ls and the overview as “shared with you” with mode and expiry (not relay URLs). mir attach validates the grant clock before dialing so expired shares fail locally. SweepGuestState on list drops closed grants and guest machine pins.

The machine overview adds s share (mint ceremony with defaults, exiting alt-screen/raw mode), rows for shares, and blocks share/rename/retire on guest entries. Share mint logic is factored into shareResolved for reuse from CLI and overview.

New client/shares.go plus tests cover owner records, guest grant lookup, sweep, and live ls/revoke/attach/overview coverage.

Reviewed by Cursor Bugbot for commit 7b4fdd2. Bugbot is set up for automated code reviews on this repo. Configure here.

… (G1d)

Per G1 spec §6. Owner side: every mint is recorded locally so `mir share
ls` lists it (short id, machine, mode, scope, expiry countdown, revoked)
and `mir share revoke <id-prefix>` delivers the tombstone over an
authenticated attach and flips the record. Revocation stays agent-local
(§4): an unreachable machine means the revoke has NOT happened — the
copy says so plainly, and says the honest counterpart too: while the
machine is offline the guest cannot reach it either.

Guest side: `mir ls` renders a share as what it is ("shared with you ·
read-only · expires in 42 min"), attach checks the grant's clock before
dialing (an expired share earns a clear line, not a fake "offline"),
and shares whose window has fully closed age out of the local store.

Overview: a share renders as ⇢ with the grant summary; `s` on an owned
machine drops to the mint ceremony and returns; rename/retire/share on
a share answer with a quiet hint instead of acting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeiotDVE94wEzvc7wcvm1y
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-30T11:25:18.522863Z 7b4fdd2 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7b4fdd2899

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread go/internal/cli/share.go
if err := a.requireRootedIdentity(idn); err != nil {
return err
}
machines, err := a.resolveMachines(context.Background(), *dir, []string{share.MachineName}, idn)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Resolve revocations by stable machine ID

If the machine is renamed after a share is minted, MachineName retains the old display name while resolveMachines only resolves current names, so mir share revoke <id> reports an unknown machine and the guest keeps access until expiry. Resolve the target using the stable share.Grant.Machine ID (and use the current name only for display) so supported machine renames cannot disable revocation.

Useful? React with 👍 / 👎.

Comment on lines +218 to +219
if g := client.GuestGrantFor(ov.dir, m.MachineID); g != nil {
row.WindowsLine = fmt.Sprintf("shared with you · %s · %s", modeWord(g.Mode), expiryPhrase(g.NA, false, time.Now()))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check shared grants before overview attach

When a guest opens the overview after a grant expires and presses Enter on this shared row, the overview's attach path bypasses the expiry check added to cmdAttach. It therefore enters the reconnect loop and retries the agent's refusal for roughly the full failure budget instead of immediately explaining that the share ended; validate GuestGrantFor(...).ValidAt(...) before making the row attachable.

Useful? React with 👍 / 👎.

func SweepGuestState(dir string, now time.Time) {
cutoff := now.Add(-identity.GrantSkew).Unix()
live := map[string]bool{}
for _, g := range ListGuestGrants(dir) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Abort guest sweeping when grants cannot be read

If the grants directory or an active grant file has a transient read error, ListGuestGrants silently returns or omits that grant, and this loop consequently leaves its machine out of live; the following loop then permanently removes the still-active guest machine from machines.json. Make grant loading return errors and skip the destructive sweep unless the grant store was read successfully, removing entries only when their expiry was positively established.

Useful? React with 👍 / 👎.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 4 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7b4fdd2. Configure here.

if row.Shared {
ov.model.Status = "that's a share — it expires on its own; nothing to retire"
break
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename prompt drops the letter s

Medium Severity

handlePromptKey never treats ovShare as typed input, so the new s binding is swallowed inside the rename prompt. Any name that contains s is stored without those letters.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7b4fdd2. Configure here.

ov.model.Status = err.Error()
default:
ov.model.Status = "shared — `" + a.binary + " share ls` lists it"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview share reports success on decline

Medium Severity

Overview share() treats a nil return from shareResolved as a completed mint. Canceling at the safety-number prompt also returns nil, so the status bar claims the machine was shared when nothing was granted.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7b4fdd2. Configure here.

if row.Shared {
ov.model.Status = "that's a share — it expires on its own; nothing to retire"
break
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview attach skips expired-share check

Medium Severity

cmdAttach now refuses an expired guest grant before dialing so the agent’s silent drop is not mistaken for “offline”. Overview Enter still calls client.Attach with no ValidAt check, so the default mir picker keeps showing that misleading failure.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7b4fdd2. Configure here.

Comment thread go/internal/cli/share.go
if err != nil {
return err
}
m := machines[0]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revoke breaks after a machine rename

Medium Severity

cmdShareRevoke resolves the target with share.MachineName, a display name frozen at mint. After machine rename, that name is gone, so the tombstone never reaches the agent and the guest keeps access until TTL.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7b4fdd2. Configure here.

@cursor
cursor Bot requested a review from wachtelhund August 30, 2026 11:27

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not approved: Cursor Bugbot finished as skipped and reported 4 unresolved issues that need human review. Assigned wachtelhund.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@frahlg
frahlg merged commit 02b21cc into main Aug 30, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant