From 3007132d67cd4a52e8e192ad0c7b04dbc0157d97 Mon Sep 17 00:00:00 2001 From: Ricky Schema Cascade Date: Wed, 13 May 2026 15:33:25 +0200 Subject: [PATCH] preserve relay cloud login on workforce logout --- packages/cli/src/deploy-command.test.ts | 46 ++++++++++++++++++++++++- packages/cli/src/deploy-command.ts | 24 +++++++++---- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/deploy-command.test.ts b/packages/cli/src/deploy-command.test.ts index b21f1426..2596a8ce 100644 --- a/packages/cli/src/deploy-command.test.ts +++ b/packages/cli/src/deploy-command.test.ts @@ -110,7 +110,7 @@ test('runLogin uses cloud SDK auth, mints a workspace token, and stores it', asy } }); -test('runLogout clears cloud auth and workspace token even when a workspace is passed', async () => { +test('runLogout preserves shared cloud auth and clears only the workspace token by default', async () => { const calls: string[] = []; const restoreDeps = configureDeployCommandForTest({ clearStoredAuth: async () => { @@ -124,6 +124,28 @@ test('runLogout clears cloud auth and workspace token even when a workspace is p try { await runLogout(['--workspace', 'acme']); assert.deepEqual(trap.exits, [0]); + assert.deepEqual(calls, ['clear-workspace:acme']); + assert.match(trap.stdout, /workspace login cleared/); + } finally { + trap.restore(); + restoreDeps(); + } +}); + +test('runLogout clears shared cloud auth when explicitly requested', async () => { + const calls: string[] = []; + const restoreDeps = configureDeployCommandForTest({ + clearStoredAuth: async () => { + calls.push('clear-auth'); + }, + clearStoredWorkspaceToken: async (workspace?: string) => { + calls.push(`clear-workspace:${workspace ?? ''}`); + } + }); + const trap = trapExit(false); + try { + await runLogout(['--workspace', 'acme', '--cloud-auth']); + assert.deepEqual(trap.exits, [0]); assert.deepEqual(calls, ['clear-auth', 'clear-workspace:acme']); assert.match(trap.stdout, /logged out/); } finally { @@ -132,6 +154,28 @@ test('runLogout clears cloud auth and workspace token even when a workspace is p } }); +test('runLogout treats --all as an alias for clearing shared cloud auth', async () => { + const calls: string[] = []; + const restoreDeps = configureDeployCommandForTest({ + clearStoredAuth: async () => { + calls.push('clear-auth'); + }, + clearStoredWorkspaceToken: async (workspace?: string) => { + calls.push(`clear-workspace:${workspace ?? ''}`); + } + }); + const trap = trapExit(false); + try { + await runLogout(['--all']); + assert.deepEqual(trap.exits, [0]); + assert.deepEqual(calls, ['clear-auth', 'clear-workspace:']); + assert.match(trap.stdout, /logged out/); + } finally { + trap.restore(); + restoreDeps(); + } +}); + test('parseDeployArgs: single --input parses and forwards', () => { const parsed = parseDeployArgs(['./persona.json', '--input', 'TOPIC=Deploy v1']); diff --git a/packages/cli/src/deploy-command.ts b/packages/cli/src/deploy-command.ts index b9960e85..bb390c27 100644 --- a/packages/cli/src/deploy-command.ts +++ b/packages/cli/src/deploy-command.ts @@ -167,9 +167,11 @@ export async function runLogout(args: readonly string[]): Promise { } const opts = parseLogoutArgs(args); try { - await deployCommandDeps.clearStoredAuth(); + if (opts.cloudAuth) { + await deployCommandDeps.clearStoredAuth(); + } await deployCommandDeps.clearStoredWorkspaceToken(opts.workspace); - process.stdout.write('logged out\n'); + process.stdout.write(opts.cloudAuth ? 'logged out\n' : 'workspace login cleared\n'); process.exit(0); } catch (err) { process.stderr.write( @@ -201,8 +203,9 @@ Flags: const LOGIN_USAGE = `usage: agentworkforce login [flags] Connect this machine to a workforce workspace using the browser OAuth flow. -The resulting workspace token is stored in the OS keychain when available, -falling back to ~/.agentworkforce/login.json. +If an Agent Relay Cloud login already exists, it is reused and the workforce +workspace token is stored beside it. Set WORKFORCE_LOGIN_FILE to force the +legacy ~/.agentworkforce/login.json-style fallback instead. Flags: --workspace Workforce workspace; defaults to WORKFORCE_WORKSPACE_ID or prompt @@ -212,10 +215,13 @@ Flags: const LOGOUT_USAGE = `usage: agentworkforce logout [flags] -Clear the browser OAuth login and the stored workspace token. +Clear the stored workforce workspace token. Agent Relay Cloud browser auth is +shared with agent-relay and is preserved unless --cloud-auth is passed. Flags: --workspace Optional workspace token entry to clear + --cloud-auth Also clear the shared Agent Relay Cloud login + --all Alias for --cloud-auth -h, --help Print this message `; @@ -386,8 +392,9 @@ function parseLoginArgs(args: readonly string[]): { workspace?: string; cloudUrl }; } -function parseLogoutArgs(args: readonly string[]): { workspace?: string } { +function parseLogoutArgs(args: readonly string[]): { workspace?: string; cloudAuth?: boolean } { let workspace: string | undefined; + let cloudAuth = false; for (let i = 0; i < args.length; i += 1) { const a = args[i]; if (a === '-h' || a === '--help') { @@ -397,12 +404,15 @@ function parseLogoutArgs(args: readonly string[]): { workspace?: string } { workspace = expectValue('--workspace', args[++i]); } else if (a.startsWith('--workspace=')) { workspace = expectInlineValue('--workspace', a.slice('--workspace='.length)); + } else if (a === '--cloud-auth' || a === '--all') { + cloudAuth = true; } else { die(`logout: unknown argument "${a}"`); } } return { - ...(workspace ? { workspace } : {}) + ...(workspace ? { workspace } : {}), + ...(cloudAuth ? { cloudAuth } : {}) }; }