@@ -24,6 +24,7 @@ import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
2424import {
2525 CreateSecretCommand ,
2626 GetSecretValueCommand ,
27+ ListSecretsCommand ,
2728 PutSecretValueCommand ,
2829 ResourceExistsException ,
2930 SecretsManagerClient ,
@@ -39,6 +40,7 @@ import {
3940 computeExpiresAt ,
4041 exchangeAuthorizationCode ,
4142 generatePkce ,
43+ LINEAR_OAUTH_SECRET_PREFIX ,
4244 linearOauthSecretName ,
4345 StoredLinearOauthToken ,
4446} from '../linear-oauth' ;
@@ -617,67 +619,108 @@ export function makeLinearCommand(): Command {
617619
618620 linear . addCommand (
619621 new Command ( 'list-projects' )
620- . description ( 'List Linear projects visible to the stored API token (with full UUIDs)' )
622+ . description ( 'List Linear projects visible to the OAuth-installed workspace (with full UUIDs)' )
621623 . option ( '--region <region>' , 'AWS region (defaults to configured region)' )
622- . option ( '--stack-name <name >' , 'CloudFormation stack name' , 'backgroundagent-dev ')
624+ . option ( '--slug <slug >' , 'Linear workspace slug (urlKey). If omitted, queries every active workspace in the registry. ' )
623625 . option ( '--output <format>' , 'Output format (text or json)' , 'text' )
624626 . action ( async ( opts ) => {
625627 const config = loadConfig ( ) ;
626628 const region = opts . region || config . region ;
629+ const sm = new SecretsManagerClient ( { region } ) ;
627630
628- const apiTokenSecretArn = await getStackOutput ( region , opts . stackName , 'LinearApiTokenSecretArn' ) ;
629- if ( ! apiTokenSecretArn ) {
630- console . error ( 'Could not find LinearApiTokenSecretArn in stack outputs. Deploy the stack first.' ) ;
631- process . exit ( 1 ) ;
631+ // Resolve the set of workspace slugs to query. Either an
632+ // explicit `--slug` (one workspace) or every Linear workspace
633+ // we have an OAuth secret for (list every `bgagent-linear-oauth-*`).
634+ let slugs : string [ ] ;
635+ if ( opts . slug ) {
636+ slugs = [ opts . slug ] ;
637+ } else {
638+ const listed = await sm . send ( new ListSecretsCommand ( {
639+ Filters : [ { Key : 'name' , Values : [ LINEAR_OAUTH_SECRET_PREFIX ] } ] ,
640+ } ) ) ;
641+ slugs = ( listed . SecretList ?? [ ] )
642+ . map ( ( s ) => s . Name ?? '' )
643+ . filter ( ( n ) => n . startsWith ( LINEAR_OAUTH_SECRET_PREFIX ) )
644+ . map ( ( n ) => n . slice ( LINEAR_OAUTH_SECRET_PREFIX . length ) ) ;
645+ if ( slugs . length === 0 ) {
646+ console . error ( 'No Linear OAuth installs found. Run `bgagent linear setup <slug>` first.' ) ;
647+ process . exit ( 1 ) ;
648+ }
632649 }
633650
634- const sm = new SecretsManagerClient ( { region } ) ;
635- const secret = await sm . send ( new GetSecretValueCommand ( { SecretId : apiTokenSecretArn } ) ) ;
636- const apiToken = secret . SecretString ;
637- if ( ! apiToken || apiToken === ' ' ) {
638- console . error ( 'Linear API token is not populated. Run `bgagent linear setup` first.' ) ;
639- process . exit ( 1 ) ;
640- }
651+ type ProjectRow = {
652+ slug : string ;
653+ id : string ;
654+ name : string ;
655+ team ?: string ;
656+ } ;
657+ const rows : ProjectRow [ ] = [ ] ;
658+
659+ for ( const slug of slugs ) {
660+ const secretName = linearOauthSecretName ( slug ) ;
661+ let accessToken : string ;
662+ try {
663+ const resp = await sm . send ( new GetSecretValueCommand ( { SecretId : secretName } ) ) ;
664+ const stored = JSON . parse ( resp . SecretString ?? '{}' ) as { access_token ?: string } ;
665+ if ( ! stored . access_token ) {
666+ console . error ( `Secret ${ secretName } is missing access_token; skipping.` ) ;
667+ continue ;
668+ }
669+ accessToken = stored . access_token ;
670+ } catch ( err ) {
671+ console . error ( `Failed to read ${ secretName } : ${ err instanceof Error ? err . message : String ( err ) } ` ) ;
672+ continue ;
673+ }
641674
642- let projects : Array < { id : string ; name : string ; teams ?: { nodes ?: Array < { id : string ; name : string } > } } > ;
643- try {
644- const res = await fetch ( 'https://api.linear.app/graphql' , {
645- method : 'POST' ,
646- headers : {
647- 'Content-Type' : 'application/json' ,
648- 'Authorization' : apiToken ,
649- } ,
650- body : JSON . stringify ( {
651- query : '{ projects { nodes { id name teams { nodes { id name } } } } }' ,
652- } ) ,
653- } ) ;
654- if ( ! res . ok ) {
655- throw new Error ( `Linear API returned ${ res . status } ` ) ;
675+ try {
676+ const res = await fetch ( 'https://api.linear.app/graphql' , {
677+ method : 'POST' ,
678+ headers : {
679+ 'Content-Type' : 'application/json' ,
680+ 'Authorization' : `Bearer ${ accessToken } ` ,
681+ } ,
682+ body : JSON . stringify ( {
683+ query : '{ projects(first: 100) { nodes { id name teams(first: 1) { nodes { name } } } } }' ,
684+ } ) ,
685+ } ) ;
686+ if ( ! res . ok ) {
687+ console . error ( `Linear API returned ${ res . status } for workspace '${ slug } '` ) ;
688+ continue ;
689+ }
690+ const body = await res . json ( ) as {
691+ data ?: { projects ?: { nodes ?: Array < { id : string ; name : string ; teams ?: { nodes ?: Array < { name : string } > } } > } } ;
692+ } ;
693+ for ( const p of body . data ?. projects ?. nodes ?? [ ] ) {
694+ rows . push ( {
695+ slug,
696+ id : p . id ,
697+ name : p . name ,
698+ team : p . teams ?. nodes ?. [ 0 ] ?. name ,
699+ } ) ;
700+ }
701+ } catch ( err ) {
702+ console . error ( `Failed to fetch projects for '${ slug } ': ${ err instanceof Error ? err . message : String ( err ) } ` ) ;
703+ continue ;
656704 }
657- const body = await res . json ( ) as { data ?: { projects ?: { nodes ?: typeof projects } } } ;
658- projects = body . data ?. projects ?. nodes ?? [ ] ;
659- } catch ( err ) {
660- console . error ( `Failed to fetch Linear projects: ${ err instanceof Error ? err . message : String ( err ) } ` ) ;
661- process . exit ( 1 ) ;
662705 }
663706
664707 if ( opts . output === 'json' ) {
665- console . log ( formatJson ( projects ) ) ;
708+ console . log ( formatJson ( rows ) ) ;
666709 return ;
667710 }
668711
669- if ( projects . length === 0 ) {
670- console . log ( 'No Linear projects visible to the stored API token .' ) ;
712+ if ( rows . length === 0 ) {
713+ console . log ( 'No Linear projects visible to any installed workspace .' ) ;
671714 return ;
672715 }
673716
674- console . log ( `Found ${ projects . length } Linear project(s):\n` ) ;
675- for ( const p of projects ) {
676- const team = p . teams ?. nodes ?. [ 0 ] ;
677- console . log ( ` ${ p . name } ` ) ;
678- console . log ( ` id: ${ p . id } ` ) ;
679- if ( team ) {
680- console . log ( ` team: ${ team . name } ( ${ team . id } ) ` ) ;
717+ console . log ( `Found ${ rows . length } Linear project(s):\n` ) ;
718+ for ( const r of rows ) {
719+ console . log ( ` ${ r . name } ` ) ;
720+ console . log ( ` id: ${ r . id } ` ) ;
721+ console . log ( ` workspace: ${ r . slug } ` ) ;
722+ if ( r . team ) {
723+ console . log ( ` team: ${ r . team } ` ) ;
681724 }
682725 console . log ( '' ) ;
683726 }
0 commit comments