@@ -392,7 +392,7 @@ const TOP_LEVEL_HELP = `${ADE_BANNER}
392392 $ ade chat list | create | send | interrupt Work with ADE agent chats
393393 $ ade agent spawn --lane <id> --prompt <text> Launch an agent session in ADE
394394 $ ade cto state | chats Operate CTO state and Work chats
395- $ ade linear workflows | run | sync Operate Linear routing and sync workflows
395+ $ ade linear graphql | workflows | run | sync Operate Linear GraphQL, routing, and sync workflows
396396 $ ade automations list | create | run | runs Manage automation rules
397397 $ ade coordinator <tool> Call coordinator runtime tools
398398 $ ade tests list | run | stop | runs | logs Run configured test suites
@@ -1415,6 +1415,10 @@ const HELP_BY_COMMAND: Record<string, string> = {
14151415 $ ade linear set-state ENG-431 <state-id> Move an issue to a workflow state
14161416 $ ade linear assign ENG-431 <user-id|none> Assign or clear an issue assignee
14171417 $ ade linear label ENG-431 "needs-review" Add a label to an issue
1418+ $ ade linear graphql --query 'query { viewer { id name } }'
1419+ Run Linear GraphQL through the project connection
1420+ $ ade linear graphql --query-file query.graphql --variables-file vars.json
1421+ Use files for larger GraphQL operations
14181422 $ ade linear detach --this-session [--issue-id ENG-431]
14191423 Detach one issue (or all) from this session
14201424
@@ -1910,6 +1914,18 @@ function readJsonFileOption(
19101914 return parseJson ( text , label ) ;
19111915}
19121916
1917+ function readTextFileOption ( args : string [ ] , names : string [ ] , label : string ) : string | null {
1918+ const filePath = readValue ( args , names ) ;
1919+ if ( filePath == null ) return null ;
1920+ const resolvedPath = path . resolve ( filePath ) ;
1921+ try {
1922+ return fs . readFileSync ( resolvedPath , "utf8" ) ;
1923+ } catch ( error ) {
1924+ const message = error instanceof Error ? error . message : String ( error ) ;
1925+ throw new CliUsageError ( `Could not read ${ label } file '${ filePath } ': ${ message } ` ) ;
1926+ }
1927+ }
1928+
19131929function readJsonPayloadOption (
19141930 args : string [ ] ,
19151931 jsonNames : string [ ] ,
@@ -2056,6 +2072,60 @@ function readIssueIdFlag(args: string[]): string | null {
20562072 return readValue ( args , [ "--issue-id" , "--linear-issue-id" , "--issue" ] ) ;
20572073}
20582074
2075+ function normalizeLinearGraphQLInput ( input : JsonObject ) : JsonObject {
2076+ const query = asString ( input . query ) ;
2077+ if ( ! query ) {
2078+ throw new CliUsageError ( "GraphQL query is required." ) ;
2079+ }
2080+
2081+ const variables = input . variables ;
2082+ if ( variables != null && ! isRecord ( variables ) ) {
2083+ throw new CliUsageError ( "--variables-json must be a JSON object." ) ;
2084+ }
2085+
2086+ const maxRetries = input . maxRetries ;
2087+ if ( maxRetries != null && ( typeof maxRetries !== "number" || ! Number . isFinite ( maxRetries ) ) ) {
2088+ throw new CliUsageError ( "--max-retries must be a number." ) ;
2089+ }
2090+
2091+ const normalized : JsonObject = { ...input , query } ;
2092+ if ( variables == null ) {
2093+ delete normalized . variables ;
2094+ }
2095+ const operationName = asString ( input . operationName ) ;
2096+ if ( operationName ) {
2097+ normalized . operationName = operationName ;
2098+ } else {
2099+ delete normalized . operationName ;
2100+ }
2101+ return normalized ;
2102+ }
2103+
2104+ function readLinearGraphQLArgs ( args : string [ ] ) : JsonObject {
2105+ const inlineQuery = readValue ( args , [ "--query" , "--graphql" , "--gql" ] ) ;
2106+ const fileQuery = readTextFileOption ( args , [ "--query-file" , "--graphql-file" , "--gql-file" ] , "--query-file" ) ;
2107+ if ( inlineQuery != null && fileQuery != null ) {
2108+ throw new CliUsageError ( "Use either --query or --query-file, not both." ) ;
2109+ }
2110+ const positionalQuery = inlineQuery == null && fileQuery == null ? firstPositional ( args ) : null ;
2111+ const query = requireValue ( inlineQuery ?? fileQuery ?? positionalQuery , "GraphQL query" ) ;
2112+ const variables = readJsonPayloadOption (
2113+ args ,
2114+ [ "--variables-json" , "--vars-json" ] ,
2115+ [ "--variables-file" , "--vars-file" ] ,
2116+ "--variables-json" ,
2117+ ) ;
2118+ if ( variables !== undefined && ! isRecord ( variables ) ) {
2119+ throw new CliUsageError ( "--variables-json must be a JSON object." ) ;
2120+ }
2121+ const input : JsonObject = { query } ;
2122+ if ( variables !== undefined ) input . variables = variables ;
2123+ maybePut ( input , "operationName" , readValue ( args , [ "--operation-name" , "--operation" ] ) ) ;
2124+ const maxRetries = readNumberOption ( args , [ "--max-retries" ] ) ;
2125+ if ( maxRetries !== undefined ) input . maxRetries = maxRetries ;
2126+ return normalizeLinearGraphQLInput ( collectGenericObjectArgs ( args , input ) ) ;
2127+ }
2128+
20592129/**
20602130 * Resolve a Linear write-bridge command's issue id when the command takes no
20612131 * positional value (e.g. `ade linear issue [<id>]`). Precedence: --issue-id flag
@@ -3078,6 +3148,25 @@ function buildCreateLaneFromLinearPlan(args: string[], issue: JsonObject): CliPl
30783148 } ,
30793149 unwrapToolResult : true ,
30803150 } ) ;
3151+ steps . push ( {
3152+ key : "attach" ,
3153+ method : "ade/actions/call" ,
3154+ params : ( values ) => {
3155+ const sessionId = sessionIdFromCreateChatValue ( values . chat ) ;
3156+ if ( ! sessionId ) {
3157+ throw new CliUsageError ( "create-from-linear launched a chat but could not resolve its session id to attach the issue." ) ;
3158+ }
3159+ return {
3160+ name : "run_ade_action" ,
3161+ arguments : {
3162+ domain : LINEAR_ATTACH_ACTIONS . domain ,
3163+ action : LINEAR_ATTACH_ACTIONS . attachSession ,
3164+ args : { chatSessionId : sessionId , issues : [ issue ] , role : "worked" , source : "chat_attach" } ,
3165+ } ,
3166+ } ;
3167+ } ,
3168+ unwrapToolResult : true ,
3169+ } ) ;
30813170 steps . push ( {
30823171 key : "result" ,
30833172 method : "ade/actions/call" ,
@@ -8776,6 +8865,13 @@ function buildLinearPlan(args: string[]): CliPlan {
87768865 steps : [ actionArgsListStep ( "result" , "linear_issue_tracker" , "fetchIssueById" , [ issueId ] ) ] ,
87778866 } ;
87788867 }
8868+ if ( sub === "graphql" || sub === "gql" ) {
8869+ return {
8870+ kind : "execute" ,
8871+ label : "Linear GraphQL" ,
8872+ steps : [ actionStep ( "result" , "linear_issue_tracker" , "graphql" , readLinearGraphQLArgs ( args ) ) ] ,
8873+ } ;
8874+ }
87798875 if ( sub === "quick-view" || sub === "quick" || sub === "overview" ) {
87808876 return {
87818877 kind : "execute" ,
0 commit comments