Skip to content

Commit a9ee5db

Browse files
khaliqgantRicky Schema Cascade
andauthored
preserve relay cloud login on workforce logout (#111)
Co-authored-by: Ricky Schema Cascade <ricky@agent-relay.com>
1 parent 5303028 commit a9ee5db

2 files changed

Lines changed: 62 additions & 8 deletions

File tree

packages/cli/src/deploy-command.test.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ test('runLogin uses cloud SDK auth, mints a workspace token, and stores it', asy
110110
}
111111
});
112112

113-
test('runLogout clears cloud auth and workspace token even when a workspace is passed', async () => {
113+
test('runLogout preserves shared cloud auth and clears only the workspace token by default', async () => {
114114
const calls: string[] = [];
115115
const restoreDeps = configureDeployCommandForTest({
116116
clearStoredAuth: async () => {
@@ -124,6 +124,28 @@ test('runLogout clears cloud auth and workspace token even when a workspace is p
124124
try {
125125
await runLogout(['--workspace', 'acme']);
126126
assert.deepEqual(trap.exits, [0]);
127+
assert.deepEqual(calls, ['clear-workspace:acme']);
128+
assert.match(trap.stdout, /workspace login cleared/);
129+
} finally {
130+
trap.restore();
131+
restoreDeps();
132+
}
133+
});
134+
135+
test('runLogout clears shared cloud auth when explicitly requested', async () => {
136+
const calls: string[] = [];
137+
const restoreDeps = configureDeployCommandForTest({
138+
clearStoredAuth: async () => {
139+
calls.push('clear-auth');
140+
},
141+
clearStoredWorkspaceToken: async (workspace?: string) => {
142+
calls.push(`clear-workspace:${workspace ?? ''}`);
143+
}
144+
});
145+
const trap = trapExit(false);
146+
try {
147+
await runLogout(['--workspace', 'acme', '--cloud-auth']);
148+
assert.deepEqual(trap.exits, [0]);
127149
assert.deepEqual(calls, ['clear-auth', 'clear-workspace:acme']);
128150
assert.match(trap.stdout, /logged out/);
129151
} finally {
@@ -132,6 +154,28 @@ test('runLogout clears cloud auth and workspace token even when a workspace is p
132154
}
133155
});
134156

157+
test('runLogout treats --all as an alias for clearing shared cloud auth', async () => {
158+
const calls: string[] = [];
159+
const restoreDeps = configureDeployCommandForTest({
160+
clearStoredAuth: async () => {
161+
calls.push('clear-auth');
162+
},
163+
clearStoredWorkspaceToken: async (workspace?: string) => {
164+
calls.push(`clear-workspace:${workspace ?? ''}`);
165+
}
166+
});
167+
const trap = trapExit(false);
168+
try {
169+
await runLogout(['--all']);
170+
assert.deepEqual(trap.exits, [0]);
171+
assert.deepEqual(calls, ['clear-auth', 'clear-workspace:']);
172+
assert.match(trap.stdout, /logged out/);
173+
} finally {
174+
trap.restore();
175+
restoreDeps();
176+
}
177+
});
178+
135179
test('parseDeployArgs: single --input parses and forwards', () => {
136180
const parsed = parseDeployArgs(['./persona.json', '--input', 'TOPIC=Deploy v1']);
137181

packages/cli/src/deploy-command.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,11 @@ export async function runLogout(args: readonly string[]): Promise<void> {
167167
}
168168
const opts = parseLogoutArgs(args);
169169
try {
170-
await deployCommandDeps.clearStoredAuth();
170+
if (opts.cloudAuth) {
171+
await deployCommandDeps.clearStoredAuth();
172+
}
171173
await deployCommandDeps.clearStoredWorkspaceToken(opts.workspace);
172-
process.stdout.write('logged out\n');
174+
process.stdout.write(opts.cloudAuth ? 'logged out\n' : 'workspace login cleared\n');
173175
process.exit(0);
174176
} catch (err) {
175177
process.stderr.write(
@@ -201,8 +203,9 @@ Flags:
201203
const LOGIN_USAGE = `usage: agentworkforce login [flags]
202204
203205
Connect this machine to a workforce workspace using the browser OAuth flow.
204-
The resulting workspace token is stored in the OS keychain when available,
205-
falling back to ~/.agentworkforce/login.json.
206+
If an Agent Relay Cloud login already exists, it is reused and the workforce
207+
workspace token is stored beside it. Set WORKFORCE_LOGIN_FILE to force the
208+
legacy ~/.agentworkforce/login.json-style fallback instead.
206209
207210
Flags:
208211
--workspace <name> Workforce workspace; defaults to WORKFORCE_WORKSPACE_ID or prompt
@@ -212,10 +215,13 @@ Flags:
212215

213216
const LOGOUT_USAGE = `usage: agentworkforce logout [flags]
214217
215-
Clear the browser OAuth login and the stored workspace token.
218+
Clear the stored workforce workspace token. Agent Relay Cloud browser auth is
219+
shared with agent-relay and is preserved unless --cloud-auth is passed.
216220
217221
Flags:
218222
--workspace <name> Optional workspace token entry to clear
223+
--cloud-auth Also clear the shared Agent Relay Cloud login
224+
--all Alias for --cloud-auth
219225
-h, --help Print this message
220226
`;
221227

@@ -386,8 +392,9 @@ function parseLoginArgs(args: readonly string[]): { workspace?: string; cloudUrl
386392
};
387393
}
388394

389-
function parseLogoutArgs(args: readonly string[]): { workspace?: string } {
395+
function parseLogoutArgs(args: readonly string[]): { workspace?: string; cloudAuth?: boolean } {
390396
let workspace: string | undefined;
397+
let cloudAuth = false;
391398
for (let i = 0; i < args.length; i += 1) {
392399
const a = args[i];
393400
if (a === '-h' || a === '--help') {
@@ -397,12 +404,15 @@ function parseLogoutArgs(args: readonly string[]): { workspace?: string } {
397404
workspace = expectValue('--workspace', args[++i]);
398405
} else if (a.startsWith('--workspace=')) {
399406
workspace = expectInlineValue('--workspace', a.slice('--workspace='.length));
407+
} else if (a === '--cloud-auth' || a === '--all') {
408+
cloudAuth = true;
400409
} else {
401410
die(`logout: unknown argument "${a}"`);
402411
}
403412
}
404413
return {
405-
...(workspace ? { workspace } : {})
414+
...(workspace ? { workspace } : {}),
415+
...(cloudAuth ? { cloudAuth } : {})
406416
};
407417
}
408418

0 commit comments

Comments
 (0)