@@ -9,6 +9,12 @@ import type { Logger } from "../logging/logger";
99
1010const execFileAsync = promisify ( execFile ) ;
1111
12+ /**
13+ * Resolves a CLI binary path for a given deployment URL, fetching/downloading
14+ * if needed. Returns the path or throws if unavailable.
15+ */
16+ export type BinaryResolver = ( url : string ) => Promise < string > ;
17+
1218/**
1319 * Returns true on platforms where the OS keyring is supported (macOS, Windows).
1420 */
@@ -18,9 +24,15 @@ export function isKeyringSupported(): boolean {
1824
1925/**
2026 * Delegates credential storage to the Coder CLI to keep the credentials in sync.
27+ *
28+ * For operations that don't have a binary path available (readToken, deleteToken),
29+ * uses the injected BinaryResolver to fetch/locate the CLI binary.
2130 */
2231export class CliCredentialManager {
23- constructor ( private readonly logger : Logger ) { }
32+ constructor (
33+ private readonly logger : Logger ,
34+ private readonly resolveBinary : BinaryResolver ,
35+ ) { }
2436
2537 /**
2638 * Store a token by running:
@@ -29,7 +41,7 @@ export class CliCredentialManager {
2941 * The token is passed via environment variable so it never appears in
3042 * process argument lists.
3143 */
32- async storeToken (
44+ public async storeToken (
3345 binPath : string ,
3446 url : string ,
3547 token : string ,
@@ -56,13 +68,21 @@ export class CliCredentialManager {
5668 * Read a token by running:
5769 * <bin> login token --url <url>
5870 *
59- * Returns trimmed stdout, or undefined on any failure.
71+ * Resolves the CLI binary automatically. Returns trimmed stdout,
72+ * or undefined if the binary can't be resolved or the CLI returns no token.
6073 */
61- async readToken (
62- binPath : string ,
74+ public async readToken (
6375 url : string ,
6476 configs : Pick < WorkspaceConfiguration , "get" > ,
6577 ) : Promise < string | undefined > {
78+ let binPath : string ;
79+ try {
80+ binPath = await this . resolveBinary ( url ) ;
81+ } catch ( error ) {
82+ this . logger . warn ( "Could not resolve CLI binary for token read:" , error ) ;
83+ return undefined ;
84+ }
85+
6686 const args = [ ...getHeaderArgs ( configs ) , "login" , "token" , "--url" , url ] ;
6787 try {
6888 const { stdout } = await execFileAsync ( binPath , args ) ;
@@ -73,4 +93,32 @@ export class CliCredentialManager {
7393 return undefined ;
7494 }
7595 }
96+
97+ /**
98+ * Delete a token by running:
99+ * <bin> logout --url <url>
100+ *
101+ * Resolves the CLI binary automatically. Best-effort: logs warnings
102+ * on failure but never throws.
103+ */
104+ async deleteToken (
105+ url : string ,
106+ configs : Pick < WorkspaceConfiguration , "get" > ,
107+ ) : Promise < void > {
108+ let binPath : string ;
109+ try {
110+ binPath = await this . resolveBinary ( url ) ;
111+ } catch ( error ) {
112+ this . logger . warn ( "Could not resolve CLI binary for token delete:" , error ) ;
113+ return ;
114+ }
115+
116+ const args = [ ...getHeaderArgs ( configs ) , "logout" , "--url" , url , "--yes" ] ;
117+ try {
118+ await execFileAsync ( binPath , args ) ;
119+ this . logger . info ( "Deleted token via CLI for" , url ) ;
120+ } catch ( error ) {
121+ this . logger . warn ( "Failed to delete token via CLI:" , error ) ;
122+ }
123+ }
76124}
0 commit comments