mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-22 04:40:44 +02:00
feat(browse): tunnel revoke/agents CLI with post-revoke verification
`$B tunnel revoke <name>` was documented in the instruction block, pair-agent/SKILL.md, and REMOTE_BROWSER_ACCESS.md but implemented nowhere: the CLI forwarded it to the daemon as Unknown command 'tunnel', and nothing in the repo called DELETE /token/:clientId or GET /agents. New pre-server short-circuit (#2254 pattern: tokens are memory-only, never boot a daemon to revoke against it). `tunnel revoke <name>` DELETEs the token, prints the deleted count ("(count unknown)" for old daemons that answer {revoked} without tokens_deleted), then RE-READS GET /agents to prove the agent is gone. The still-listed branch is the version-skew net: a new CLI against a still-running old daemon with the first-match revoke bug exits 1 and says to re-run (each old-daemon call deletes the next match) or stop. An alive pid with an unreachable port reports "Could not reach daemon" (exit 1), never a false "no daemon". `tunnel agents` lists sessions plus pending (unexchanged) setup keys, which GET /agents now exposes via listTokens({includeSetup}) — without them the revocation view was blind to a paired-but-never-connected agent. Setup-key tokens never leave the server. DELETE /token/ now decodeURIComponents the clientId (400 on malformed encoding) so CLI-encoded names round-trip. Tests: subprocess CLI coverage (usage paths, no-daemon exit 0 without spawning, live pair/connect/revoke loop, pending-key listing), stub-daemon pins for the skew and unreachable branches, and e2e pins for revoke-all semantics, percent-encoded ids, and the second-DELETE-is-404 regression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b9eb108f16
commit
f28bfd0158
@@ -366,6 +366,18 @@ describe('token-registry', () => {
|
||||
createSetupKey({}); // setup keys not listed
|
||||
expect(listTokens()).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('includeSetup lists pending setup keys but hides spent ones', () => {
|
||||
createToken({ clientId: 'sess' });
|
||||
createSetupKey({ clientId: 'pending' });
|
||||
const spent = createSetupKey({ clientId: 'spent' });
|
||||
exchangeSetupKey(spent.token);
|
||||
expect(listTokens().map(t => t.clientId).sort()).toEqual(['sess', 'spent']);
|
||||
const withSetup = listTokens({ includeSetup: true });
|
||||
// Pending key = a live grant the operator must see; the SPENT key is
|
||||
// re-exchange bookkeeping for the already-listed session and stays hidden.
|
||||
expect(withSetup.filter(t => t.type === 'setup').map(t => t.clientId)).toEqual(['pending']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('serialization', () => {
|
||||
|
||||
Reference in New Issue
Block a user