@jive-ai/cli 0.0.30 → 0.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # Jive CLI
2
+
3
+ The official CLI for Jive AI - autonomous development workflows powered by Claude.
4
+
5
+ ## Installation
6
+
7
+ ### npm
8
+
9
+ ```bash
10
+ npm install -g @jive-ai/cli
11
+ ```
12
+
13
+ ### Docker
14
+
15
+ ```bash
16
+ docker pull jive-ai/task:latest
17
+ ```
18
+
19
+ ## Configuration
20
+
21
+ ### Environment Variables
22
+
23
+ The CLI can be configured using environment variables:
24
+
25
+ - `JIVE_TASK_IDLE_TIMEOUT` - Minutes of idle time before a task automatically terminates (default: 5). Tasks are ephemeral and will gracefully shutdown after this period of inactivity, freeing up runner capacity. Session data is preserved for resume.
26
+ - Development: `JIVE_TASK_IDLE_TIMEOUT=10` (10 minutes - more forgiving)
27
+ - Production: `JIVE_TASK_IDLE_TIMEOUT=5` (5 minutes - default)
28
+ - CI/CD: `JIVE_TASK_IDLE_TIMEOUT=30` (30 minutes - long builds)
29
+
30
+ - `JIVE_API_KEY` - Your Jive API authentication key (required for task runners)
31
+ - `JIVE_TEAM_ID` - The team ID to run tasks for (required for task runners)
32
+ - `JIVE_API_URL` - API endpoint (defaults to production)
33
+ - `JIVE_WS_URL` - WebSocket endpoint (defaults to production)
34
+ - `JIVE_LOG_FILE` - MCP server log path (defaults to `/tmp/jive-mcp.log`)
35
+
36
+ ## Docker Image
37
+
38
+ The docker image is used for each task process container. Whenever you spin up a new task in Jive,
39
+ it will start a new container with this image. You can specify your own custom image in the project settings
40
+ as long as the same entrypoint script is provided. The task runner relies on it.
41
+
42
+ ### Building Your Own Images
43
+
44
+ You can extend the Jive task runner image to add custom tools or dependencies:
45
+
46
+ ```dockerfile
47
+ FROM jive-ai/task:latest
48
+
49
+ # Add your custom dependencies
50
+ RUN apk add --no-cache python3 py3-pip
51
+
52
+ # Install additional tools
53
+ RUN pip install --break-system-packages black ruff
54
+ ```
55
+
56
+ ## Publishing the Docker Image (Maintainers)
57
+
58
+ ### One-Time Setup
59
+
60
+ 1. Login to Docker Hub:
61
+ ```bash
62
+ docker login
63
+ ```
64
+
65
+ 2. Create a buildx builder for multi-platform builds:
66
+ ```bash
67
+ docker buildx create --name mybuilder --use
68
+ docker buildx inspect --bootstrap
69
+ ```
70
+
71
+ ### Publishing a New Version
72
+
73
+ After publishing a new version to npm, publish the corresponding Docker image:
74
+
75
+ ```bash
76
+ cd cli
77
+ npm run docker:publish
78
+ ```
79
+
80
+ This will:
81
+ - Build for both `linux/amd64` and `linux/arm64` platforms
82
+ - Tag with both `latest` and the current package version (e.g., `0.0.31`)
83
+ - Push both tags to Docker Hub
84
+
85
+ The image pulls the CLI from npm (using the `@jive-ai/cli` package), so make sure you've published to npm first.
86
+
87
+ ### Manual Steps
88
+
89
+ If you need more control:
90
+
91
+ ```bash
92
+ # Build only (no push)
93
+ npm run docker:build:public
94
+
95
+ # Push to Docker Hub
96
+ npm run docker:push
97
+ ```
98
+
99
+ ## Development
100
+
101
+ ### Local Development with Docker
102
+
103
+ To test the Docker image with your local code changes:
104
+
105
+ ```bash
106
+ npm run docker:build:local
107
+ ```
108
+
109
+ This builds an image using the local CLI tarball instead of pulling from npm.
@@ -0,0 +1,3 @@
1
+ import { a as queries, i as mutations, n as cliMutations, r as cliQueries, t as getGraphQLClient } from "./index.mjs";
2
+
3
+ export { getGraphQLClient };
package/dist/index.mjs CHANGED
@@ -14,6 +14,7 @@ import crypto from "crypto";
14
14
  import "gray-matter";
15
15
  import WebSocket from "ws";
16
16
  import { decode, encode } from "js-base64";
17
+ import { GraphQLClient } from "graphql-request";
17
18
  import { createSdkMcpServer, query, tool } from "@anthropic-ai/claude-agent-sdk";
18
19
  import { z } from "zod";
19
20
  import dedent from "dedent";
@@ -154,9 +155,9 @@ async function isProjectInitialized() {
154
155
 
155
156
  //#endregion
156
157
  //#region src/constants.ts
157
- const API_URL = process.env.JIVE_API_URL || "http://localhost:5173";
158
- const GRAPHQL_API_URL = process.env.JIVE_GRAPHQL_API_URL || "http://localhost:4000/graphql";
159
- const WS_URL = process.env.JIVE_WS_URL || "ws://localhost:4000";
158
+ const API_URL = process.env.JIVE_API_URL || "https://getjive.app";
159
+ const GRAPHQL_API_URL = process.env.JIVE_GRAPHQL_API_URL || "https://api.getjive.app/graphql";
160
+ const WS_URL = process.env.JIVE_WS_URL || "wss://api.getjive.app";
160
161
 
161
162
  //#endregion
162
163
  //#region src/commands/auth.ts
@@ -2337,12 +2338,13 @@ const queries = {
2337
2338
  }
2338
2339
  `),
2339
2340
  GetSessionLines: graphql(`
2340
- query GetSessionLines($taskId: ID!, $sessionId: ID!, $first: Int, $after: String) {
2341
- sessionLines(taskId: $taskId, sessionId: $sessionId, first: $first, after: $after) {
2341
+ query GetSessionLines($taskId: ID!, $sessionId: ID!, $first: Int, $after: String, $excludePending: Boolean) {
2342
+ sessionLines(taskId: $taskId, sessionId: $sessionId, first: $first, after: $after, excludePending: $excludePending) {
2342
2343
  edges {
2343
2344
  node {
2344
2345
  uuid
2345
2346
  schema
2347
+ status
2346
2348
  payload
2347
2349
  createdAt
2348
2350
  }
@@ -2404,9 +2406,9 @@ const mutations = {
2404
2406
  }
2405
2407
  }
2406
2408
  `),
2407
- CreateSessionLine: graphql(`
2408
- mutation CreateSessionLine($input: CreateSessionLineInput!) {
2409
- createSessionLine(input: $input) {
2409
+ PromptSendToAgent: graphql(`
2410
+ mutation PromptSendToAgent($input: PromptSendToAgentInput!) {
2411
+ promptSendToAgent(input: $input) {
2410
2412
  line {
2411
2413
  uuid
2412
2414
  schema
@@ -2433,34 +2435,6 @@ const mutations = {
2433
2435
  }
2434
2436
  }
2435
2437
  }
2436
- `),
2437
- RefreshPreview: graphql(`
2438
- mutation RefreshPreview($input: RefreshPreviewInput!) {
2439
- refreshPreview(input: $input) {
2440
- task {
2441
- id
2442
- previewUrl
2443
- }
2444
- errors {
2445
- message
2446
- code
2447
- }
2448
- }
2449
- }
2450
- `),
2451
- AddressReview: graphql(`
2452
- mutation AddressReview($input: AddressReviewInput!) {
2453
- addressReview(input: $input) {
2454
- task {
2455
- id
2456
- status
2457
- }
2458
- errors {
2459
- message
2460
- code
2461
- }
2462
- }
2463
- }
2464
2438
  `),
2465
2439
  CreatePlan: graphql(`
2466
2440
  mutation CreatePlan($input: CreatePlanInput!) {
@@ -2565,6 +2539,21 @@ const cliQueries = {
2565
2539
  createdAt
2566
2540
  }
2567
2541
  }
2542
+ `),
2543
+ Team: graphql(`
2544
+ query Team($id: ID!) {
2545
+ team(id: $id) {
2546
+ id
2547
+ name
2548
+ settings {
2549
+ claude {
2550
+ defaultModel
2551
+ environmentVariables
2552
+ betaFlags
2553
+ }
2554
+ }
2555
+ }
2556
+ }
2568
2557
  `),
2569
2558
  InterruptedTasks: graphql(`
2570
2559
  query InterruptedTasks($runnerId: ID!, $limit: Int) {
@@ -2577,7 +2566,13 @@ const cliQueries = {
2577
2566
  directory
2578
2567
  repository
2579
2568
  branch
2580
- gitAuth
2569
+ gitAuth {
2570
+ privateKey
2571
+ repositoryUrl
2572
+ provider
2573
+ method
2574
+ }
2575
+ permissionMode
2581
2576
  }
2582
2577
  sessionId
2583
2578
  }
@@ -2616,6 +2611,9 @@ const cliQueries = {
2616
2611
  description
2617
2612
  gitRepoUrl
2618
2613
  gitDefaultBranch
2614
+ team {
2615
+ id
2616
+ }
2619
2617
  createdAt
2620
2618
  updatedAt
2621
2619
  }
@@ -2763,6 +2761,7 @@ async function getGraphQLClient() {
2763
2761
  function mapStatusToGraphQL(dbStatus) {
2764
2762
  return {
2765
2763
  "draft": "DRAFT",
2764
+ "pending": "PENDING",
2766
2765
  "planning": "PLANNING",
2767
2766
  "planned": "PLANNED",
2768
2767
  "in-progress": "IN_PROGRESS",
@@ -2951,10 +2950,11 @@ var ApiClient = class {
2951
2950
  } : null
2952
2951
  };
2953
2952
  }
2954
- async getSessionLines(taskId, sessionId) {
2953
+ async getSessionLines(taskId, sessionId, excludePending = false) {
2955
2954
  return (await (await getGraphQLClient()).request(queries.GetSessionLines, {
2956
2955
  taskId: String(taskId),
2957
- sessionId: String(sessionId)
2956
+ sessionId: String(sessionId),
2957
+ excludePending
2958
2958
  })).sessionLines.edges.map((edge) => ({
2959
2959
  uuid: edge.node.uuid,
2960
2960
  schema: edge.node.schema,
@@ -3269,15 +3269,42 @@ async function fetchProject(teamId) {
3269
3269
 
3270
3270
  //#endregion
3271
3271
  //#region src/runner/taskUtils.ts
3272
+ const ProjectResourceLimitsQuery = graphql(`
3273
+ query ProjectResourceLimits($projectId: ID!) {
3274
+ project(id: $projectId) {
3275
+ id
3276
+ dockerCpus
3277
+ dockerMemory
3278
+ dockerImage
3279
+ }
3280
+ }
3281
+ `);
3282
+ async function fetchProjectResourceLimits(projectId) {
3283
+ try {
3284
+ const result = await (await getGraphQLClient()).request(ProjectResourceLimitsQuery, { projectId: projectId.toString() });
3285
+ if (!result.project) {
3286
+ console.log(chalk.yellow(`Warning: Could not fetch project ${projectId} resource limits`));
3287
+ return;
3288
+ }
3289
+ return {
3290
+ dockerCpus: result.project.dockerCpus,
3291
+ dockerMemory: result.project.dockerMemory,
3292
+ dockerImage: result.project.dockerImage
3293
+ };
3294
+ } catch (error$1) {
3295
+ console.log(chalk.yellow(`Warning: Failed to fetch project resource limits: ${error$1.message}`));
3296
+ return;
3297
+ }
3298
+ }
3272
3299
  function spawnTask(ctx, opts) {
3273
3300
  throw new Error("Shell executor not supported anymore.");
3274
3301
  }
3275
- async function spawnTaskDocker(ctx, opts) {
3302
+ async function spawnTaskDocker(ctx, config$2, opts) {
3276
3303
  mkdirSync(ctx.directory, { recursive: true });
3304
+ const limits = getEffectiveResourceLimits(config$2, await fetchProjectResourceLimits(ctx.projectId));
3277
3305
  const envVars = [];
3278
3306
  const credentials = await getCredentials();
3279
3307
  if (!credentials) throw new Error("No credentials found");
3280
- const config$2 = await getRunnerConfig();
3281
3308
  envVars.push("-e", `JIVE_API_KEY=${credentials.token}`);
3282
3309
  envVars.push("-e", `ANTHROPIC_API_KEY=${credentials.anthropicApiKey}`);
3283
3310
  envVars.push("-e", `JIVE_TEAM_ID=${config$2.teamId}`);
@@ -3311,7 +3338,8 @@ async function spawnTaskDocker(ctx, opts) {
3311
3338
  }
3312
3339
  dockerArgs.push("--network", "bridge");
3313
3340
  if (isDevMode) dockerArgs.push("--add-host", "host.docker.internal:host-gateway");
3314
- dockerArgs.push("--cpus", "2", "--memory", "2g", "-v", `${ctx.directory}:/workspace`, "-w", "/workspace", ...envVars, "jive-task-runner:latest", encode(JSON.stringify(ctx)));
3341
+ dockerArgs.push("--cpus", limits.docker.cpus, "--memory", limits.docker.memory, "-v", `${ctx.directory}:/workspace`, "-w", "/workspace", ...envVars, limits.docker.image, encode(JSON.stringify(ctx)));
3342
+ console.log(chalk.dim(`[Task ${ctx.taskId}] Spawning Docker container with args:`, JSON.stringify(dockerArgs, null, 2)));
3315
3343
  const taskProcess = spawn("docker", dockerArgs, { cwd: ctx.directory });
3316
3344
  let buffer = "";
3317
3345
  taskProcess.stdout.on("data", (data) => {
@@ -3352,21 +3380,49 @@ async function spawnTaskDocker(ctx, opts) {
3352
3380
  return taskProcess;
3353
3381
  }
3354
3382
 
3383
+ //#endregion
3384
+ //#region src/runner/graphql-client.ts
3385
+ async function createGraphQLClient() {
3386
+ const credentials = await getCredentials();
3387
+ if (!credentials?.token) throw new Error("No credentials found, run `jive login` first");
3388
+ const client = new GraphQLClient(GRAPHQL_API_URL, { headers: { "X-API-Key": credentials.token } });
3389
+ return {
3390
+ async request(document, variables) {
3391
+ return client.request(document, variables);
3392
+ },
3393
+ async rawRequest(document, variables) {
3394
+ return client.request(document, variables);
3395
+ }
3396
+ };
3397
+ }
3398
+
3355
3399
  //#endregion
3356
3400
  //#region src/runner/index.ts
3357
3401
  const execAsync$3 = promisify(exec);
3358
- const MAX_CONCURRENT_TASKS = 5;
3359
- const INTERRUPTED_TASK_POLL_INTERVAL = 3e4;
3402
+ const PENDING_TASK_POLL_INTERVAL = 3e4;
3403
+ function getEffectiveResourceLimits(config$2, projectLimits) {
3404
+ const maxConcurrentTasksEnv = process.env.JIVE_MAX_CONCURRENT_TASKS;
3405
+ return {
3406
+ maxConcurrentTasks: maxConcurrentTasksEnv && !isNaN(parseInt(maxConcurrentTasksEnv)) ? parseInt(maxConcurrentTasksEnv) : config$2.resourceLimits?.maxConcurrentTasks ?? 5,
3407
+ docker: {
3408
+ cpus: projectLimits?.dockerCpus?.toString() || process.env.JIVE_DOCKER_CPUS || config$2.resourceLimits?.docker?.cpus || "2",
3409
+ memory: projectLimits?.dockerMemory || process.env.JIVE_DOCKER_MEMORY || config$2.resourceLimits?.docker?.memory || "2g",
3410
+ image: projectLimits?.dockerImage || process.env.JIVE_DOCKER_IMAGE || config$2.resourceLimits?.docker?.image || "jive-ai/task:latest"
3411
+ }
3412
+ };
3413
+ }
3360
3414
  var TaskRunner = class {
3361
3415
  config;
3362
3416
  ws = null;
3363
3417
  heartbeatInterval = null;
3364
- interruptedTaskPollInterval = null;
3418
+ pendingTaskPollInterval = null;
3365
3419
  isRunning = false;
3366
3420
  reconnectTimeout = null;
3421
+ maxConcurrentTasks;
3367
3422
  taskProcesses = {};
3368
3423
  constructor(config$2) {
3369
3424
  this.config = config$2;
3425
+ this.maxConcurrentTasks = getEffectiveResourceLimits(config$2).maxConcurrentTasks;
3370
3426
  }
3371
3427
  async start() {
3372
3428
  this.isRunning = true;
@@ -3384,7 +3440,7 @@ var TaskRunner = class {
3384
3440
  clearInterval(this.heartbeatInterval);
3385
3441
  this.heartbeatInterval = null;
3386
3442
  }
3387
- this.stopInterruptedTaskPolling();
3443
+ this.stopPendingTaskPolling();
3388
3444
  if (this.reconnectTimeout) {
3389
3445
  clearTimeout(this.reconnectTimeout);
3390
3446
  this.reconnectTimeout = null;
@@ -3426,12 +3482,75 @@ var TaskRunner = class {
3426
3482
  }
3427
3483
  });
3428
3484
  }
3429
- fetchTaskProcess(ctx) {
3430
- const existingProcess = this.taskProcesses[ctx.taskId.toString()];
3485
+ async fetchTaskContext(taskId) {
3486
+ try {
3487
+ const { getGraphQLClient: getGraphQLClient$1 } = await import("./graphql-client-DtLFKd4m.mjs");
3488
+ const client = await getGraphQLClient$1();
3489
+ const query$1 = graphql(`
3490
+ query TaskContext($taskId: ID!) {
3491
+ taskContext(taskId: $taskId) {
3492
+ taskId
3493
+ sessionId
3494
+ claudeSessionId
3495
+ projectId
3496
+ directory
3497
+ repository
3498
+ branch
3499
+ gitAuth {
3500
+ privateKey
3501
+ repositoryUrl
3502
+ provider
3503
+ method
3504
+ }
3505
+ permissionMode
3506
+ }
3507
+ }
3508
+ `);
3509
+ const data = await client.request(query$1, { taskId: taskId.toString() });
3510
+ if (!data.taskContext) {
3511
+ console.error(chalk.red(`No context found for task ${taskId}`));
3512
+ return null;
3513
+ }
3514
+ const auth = (() => {
3515
+ const gitAuth = data.taskContext.gitAuth;
3516
+ if (!gitAuth) return null;
3517
+ return {
3518
+ ...gitAuth,
3519
+ provider: gitAuth.provider,
3520
+ method: gitAuth.method
3521
+ };
3522
+ })();
3523
+ return {
3524
+ taskId: data.taskContext.taskId,
3525
+ sessionId: data.taskContext.sessionId,
3526
+ claudeSessionId: data.taskContext.claudeSessionId,
3527
+ projectId: data.taskContext.projectId,
3528
+ directory: data.taskContext.directory,
3529
+ repository: data.taskContext.repository,
3530
+ branch: data.taskContext.branch,
3531
+ gitAuth: auth || void 0,
3532
+ permissionMode: data.taskContext.permissionMode
3533
+ };
3534
+ } catch (error$1) {
3535
+ console.error(error$1);
3536
+ console.error(chalk.red(`Failed to fetch task context: ${error$1.message}`));
3537
+ return null;
3538
+ }
3539
+ }
3540
+ async fetchTaskProcess(taskId) {
3541
+ const existingProcess = this.taskProcesses[taskId.toString()];
3431
3542
  if (existingProcess) return existingProcess;
3432
- if (Object.values(this.taskProcesses).filter((p$1) => p$1 !== null).length >= MAX_CONCURRENT_TASKS) throw new Error(`Maximum concurrent tasks reached (${MAX_CONCURRENT_TASKS})`);
3433
- return this.taskProcesses[ctx.taskId.toString()] = new Promise((resolve) => {
3434
- resolve((this.config.type === "docker" ? spawnTaskDocker : spawnTask)(ctx, {
3543
+ if (Object.values(this.taskProcesses).filter((p$1) => p$1 !== null).length >= this.maxConcurrentTasks) {
3544
+ console.warn(chalk.yellow(`Maximum concurrent tasks reached (${this.maxConcurrentTasks})`));
3545
+ return null;
3546
+ }
3547
+ const ctx = await this.fetchTaskContext(taskId);
3548
+ if (!ctx) {
3549
+ console.error(chalk.red(`Failed to fetch context for task ${taskId}`));
3550
+ return null;
3551
+ }
3552
+ return this.taskProcesses[taskId.toString()] = new Promise((resolve) => {
3553
+ (this.config.type === "docker" ? spawnTaskDocker(ctx, this.config, {
3435
3554
  onMessage: (message) => this.onTaskMessage(ctx.taskId, message),
3436
3555
  onError: () => {
3437
3556
  this.taskProcesses[ctx.taskId.toString()] = null;
@@ -3439,11 +3558,29 @@ var TaskRunner = class {
3439
3558
  onClose: () => {
3440
3559
  this.taskProcesses[ctx.taskId.toString()] = null;
3441
3560
  }
3442
- }));
3561
+ }) : Promise.resolve(spawnTask(ctx, {
3562
+ onMessage: (message) => this.onTaskMessage(ctx.taskId, message),
3563
+ onError: () => {
3564
+ this.taskProcesses[ctx.taskId.toString()] = null;
3565
+ },
3566
+ onClose: () => {
3567
+ this.taskProcesses[ctx.taskId.toString()] = null;
3568
+ }
3569
+ }))).then((process$1) => {
3570
+ resolve({
3571
+ process: process$1,
3572
+ ctx
3573
+ });
3574
+ });
3443
3575
  });
3444
3576
  }
3445
- async sendToTask(ctx, message) {
3446
- (await this.fetchTaskProcess(ctx)).stdin?.write(JSON.stringify(message) + "\n");
3577
+ async sendToTask(taskId, message) {
3578
+ const result = await this.fetchTaskProcess(taskId);
3579
+ if (!result) {
3580
+ console.warn(chalk.yellow(`Cannot write message for task ${taskId}, process not found or unobtainable`));
3581
+ return;
3582
+ }
3583
+ result.process.stdin?.write(JSON.stringify(message) + "\n");
3447
3584
  }
3448
3585
  async connect() {
3449
3586
  console.log(chalk.dim(`Connecting to ${WS_URL}...`));
@@ -3475,7 +3612,7 @@ var TaskRunner = class {
3475
3612
  process.exit(1);
3476
3613
  }
3477
3614
  this.startHeartbeat();
3478
- this.startInterruptedTaskPolling();
3615
+ this.startPendingTaskPolling();
3479
3616
  this.send({
3480
3617
  type: "runner_connected",
3481
3618
  payload: {
@@ -3518,7 +3655,20 @@ var TaskRunner = class {
3518
3655
  });
3519
3656
  break;
3520
3657
  case "task_message":
3521
- this.sendToTask(message.payload.ctx, message.payload.message);
3658
+ const { taskId, message: taskMsg } = message.payload;
3659
+ const messageId = message.payload.messageId;
3660
+ const activeCount = Object.values(this.taskProcesses).filter((p$1) => p$1 !== null).length;
3661
+ const accepted = activeCount < this.maxConcurrentTasks;
3662
+ if (messageId) this.send({
3663
+ type: "task_ack",
3664
+ payload: {
3665
+ messageId,
3666
+ accepted,
3667
+ reason: accepted ? void 0 : "capacity_full"
3668
+ }
3669
+ });
3670
+ if (accepted) this.sendToTask(taskId, taskMsg);
3671
+ else console.warn(chalk.yellow(`Rejected task ${taskId} - at capacity (${activeCount}/${this.maxConcurrentTasks})`));
3522
3672
  break;
3523
3673
  default: console.warn(chalk.yellow(`Unable to handle message:\n\n${JSON.stringify(message, null, 2)}`));
3524
3674
  }
@@ -3532,7 +3682,7 @@ var TaskRunner = class {
3532
3682
  clearInterval(this.heartbeatInterval);
3533
3683
  this.heartbeatInterval = null;
3534
3684
  }
3535
- this.stopInterruptedTaskPolling();
3685
+ this.stopPendingTaskPolling();
3536
3686
  if (this.isRunning) {
3537
3687
  console.log(chalk.dim("Attempting to reconnect in 5 seconds..."));
3538
3688
  this.reconnectTimeout = setTimeout(() => {
@@ -3549,59 +3699,131 @@ var TaskRunner = class {
3549
3699
  type: "heartbeat",
3550
3700
  payload: { timestamp: (/* @__PURE__ */ new Date()).toISOString() }
3551
3701
  });
3552
- }, 3e4);
3702
+ }, 5e3);
3553
3703
  }
3554
- startInterruptedTaskPolling() {
3555
- this.pollForInterruptedTasks();
3556
- this.interruptedTaskPollInterval = setInterval(() => {
3557
- this.pollForInterruptedTasks();
3558
- }, INTERRUPTED_TASK_POLL_INTERVAL);
3704
+ startPendingTaskPolling() {
3705
+ this.pollForPendingTasks();
3706
+ this.pendingTaskPollInterval = setInterval(() => {
3707
+ this.pollForPendingTasks();
3708
+ }, PENDING_TASK_POLL_INTERVAL);
3559
3709
  }
3560
- stopInterruptedTaskPolling() {
3561
- if (this.interruptedTaskPollInterval) {
3562
- clearInterval(this.interruptedTaskPollInterval);
3563
- this.interruptedTaskPollInterval = null;
3710
+ stopPendingTaskPolling() {
3711
+ if (this.pendingTaskPollInterval) {
3712
+ clearInterval(this.pendingTaskPollInterval);
3713
+ this.pendingTaskPollInterval = null;
3564
3714
  }
3565
3715
  }
3566
- async pollForInterruptedTasks() {
3567
- console.log("TODO: pollForInterruptedTasks graphql migration");
3716
+ async pollForPendingTasks() {
3717
+ const activeCount = Object.values(this.taskProcesses).filter((p$1) => p$1 !== null).length;
3718
+ if (activeCount >= this.maxConcurrentTasks) {
3719
+ console.log(chalk.dim(`At capacity (${activeCount}/${this.maxConcurrentTasks}), skipping poll`));
3720
+ return;
3721
+ }
3722
+ const availableSlots = this.maxConcurrentTasks - activeCount;
3723
+ console.log(`Finding pending tasks, ${availableSlots} / ${this.maxConcurrentTasks} task slots available`);
3724
+ try {
3725
+ const client = await createGraphQLClient();
3726
+ const query$1 = graphql(`
3727
+ query PendingTasks($limit: Int!) {
3728
+ tasks(
3729
+ status: [PENDING]
3730
+ first: $limit
3731
+ ) {
3732
+ edges {
3733
+ node {
3734
+ id
3735
+ title
3736
+ project { id team { id } }
3737
+ }
3738
+ }
3739
+ }
3740
+ }
3741
+ `);
3742
+ const pendingTasks = (await client.request(query$1, { limit: availableSlots })).tasks.edges.map((e$3) => e$3.node);
3743
+ if (pendingTasks.length > 0) console.log(chalk.yellow(`Found ${pendingTasks.length} pending task(s) to pick up`));
3744
+ for (const task of pendingTasks) await this.pickupPendingTask(parseInt(task.id));
3745
+ } catch (error$1) {
3746
+ if (!error$1.message?.includes("Unable to connect")) console.error(chalk.red(`Failed to poll for pending tasks: ${error$1.message}`));
3747
+ }
3568
3748
  }
3569
- async resumeTask(ctx, sessionId) {
3570
- console.log(chalk.yellow(`Resuming interrupted task ${ctx.taskId}...`));
3749
+ async pickupPendingTask(taskId) {
3750
+ console.log(chalk.yellow(`Picking up pending task ${taskId}...`));
3571
3751
  try {
3572
- const taskProcess = await this.fetchTaskProcess(ctx);
3573
- const resumeMessage = {
3574
- type: "user_message",
3575
- payload: {
3576
- content: "Your task was interrupted and some of your progress may have been lost. Assess what has been lost, attempt to restore, then resume your original task.",
3577
- sessionId: sessionId.toString()
3578
- }
3579
- };
3580
- taskProcess.stdin?.write(JSON.stringify(resumeMessage) + "\n");
3581
- console.log(chalk.green(`Task ${ctx.taskId} resume message sent`));
3752
+ const client = await createGraphQLClient();
3753
+ const mutation = graphql(`
3754
+ mutation PickupPendingTask($taskId: ID!, $runnerId: ID!) {
3755
+ pickupPendingTask(taskId: $taskId, runnerId: $runnerId) {
3756
+ success
3757
+ task { id status }
3758
+ errors { message code }
3759
+ }
3760
+ }
3761
+ `);
3762
+ const result = await client.request(mutation, {
3763
+ taskId: taskId.toString(),
3764
+ runnerId: this.config.id.toString()
3765
+ });
3766
+ if (result.pickupPendingTask.success) {
3767
+ console.log(chalk.green(`Successfully picked up task ${taskId}`));
3768
+ await this.fetchTaskProcess(taskId);
3769
+ } else {
3770
+ const error$1 = result.pickupPendingTask.errors?.[0];
3771
+ console.warn(chalk.yellow(`Failed to pickup task ${taskId}: ${error$1?.message || "unknown error"}`));
3772
+ }
3582
3773
  } catch (error$1) {
3583
- console.error(chalk.red(`Failed to resume task ${ctx.taskId}: ${error$1.message}`));
3774
+ console.error(chalk.red(`Failed to pickup task ${taskId}: ${error$1.message}`));
3584
3775
  }
3585
3776
  }
3586
3777
  };
3587
3778
 
3779
+ //#endregion
3780
+ //#region src/runner/task/debugLog.ts
3781
+ function debugLog(message) {
3782
+ console.log(JSON.stringify({
3783
+ type: "debug_log",
3784
+ message
3785
+ }).replace(/\n/g, "\\n"));
3786
+ }
3787
+
3588
3788
  //#endregion
3589
3789
  //#region src/runner/query.ts
3590
3790
  async function queryClaude(prompt, mcpServer, opts) {
3591
- const { sessionId, abortController, permissionMode = "bypassPermissions", task } = opts;
3791
+ const { sessionId, abortController, permissionMode = "BYPASS_PERMISSIONS", task } = opts;
3592
3792
  const credentials = await getCredentials();
3593
3793
  if (!credentials?.anthropicApiKey) throw new Error("Anthropic API key not found in credentials JSON");
3594
3794
  process.env.ANTHROPIC_API_KEY = credentials.anthropicApiKey;
3595
- console.log(`Querying Claude with permission mode: ${permissionMode}`);
3795
+ let teamSettings;
3796
+ try {
3797
+ const client = await getGraphQLClient();
3798
+ const projectData = await client.request(cliQueries.Project, { id: task.projectId.toString() });
3799
+ if (projectData.project?.team) {
3800
+ const claude = (await client.request(cliQueries.Team, { id: projectData.project.team.id })).team?.settings?.claude;
3801
+ if (claude) teamSettings = {
3802
+ defaultModel: claude.defaultModel,
3803
+ environmentVariables: claude.environmentVariables,
3804
+ betaFlags: claude.betaFlags
3805
+ };
3806
+ }
3807
+ } catch (error$1) {
3808
+ console.error("Failed to fetch team settings, using defaults:", error$1);
3809
+ }
3810
+ const defaultModel = teamSettings?.defaultModel || "sonnet";
3811
+ const environmentVariables = teamSettings?.environmentVariables || {};
3812
+ const betaFlags = teamSettings?.betaFlags || [];
3813
+ for (const [key, value$1] of Object.entries(environmentVariables)) process.env[key] = value$1;
3814
+ debugLog(`[Task ${task.id}] Querying Claude with permission mode: ${permissionMode}`);
3815
+ debugLog(`[Task ${task.id}] Using model: ${defaultModel}`);
3816
+ if (betaFlags.length > 0) debugLog(`[Task ${task.id}] Beta flags: ${betaFlags.join(", ")}`);
3596
3817
  return query({
3597
3818
  prompt,
3598
3819
  options: {
3599
3820
  abortController,
3600
3821
  resume: sessionId,
3601
- model: "claude-sonnet-4-5",
3822
+ model: defaultModel,
3602
3823
  includePartialMessages: false,
3603
3824
  mcpServers: { "jive-tasks": mcpServer },
3604
- permissionMode,
3825
+ permissionMode: mapPermissionMode(permissionMode),
3826
+ ...betaFlags.length > 0 && { betas: betaFlags },
3605
3827
  canUseTool: async (toolName, input) => {
3606
3828
  if (toolName === "AskUserQuestion") {
3607
3829
  const result = await task.requestUserQuestions(input);
@@ -3629,6 +3851,15 @@ async function queryClaude(prompt, mcpServer, opts) {
3629
3851
  }
3630
3852
  });
3631
3853
  }
3854
+ function mapPermissionMode(permissionMode) {
3855
+ switch (permissionMode) {
3856
+ case "DEFAULT": return "default";
3857
+ case "BYPASS_PERMISSIONS": return "bypassPermissions";
3858
+ default:
3859
+ console.error(`[Task] Invalid permission mode: ${permissionMode}`);
3860
+ return;
3861
+ }
3862
+ }
3632
3863
 
3633
3864
  //#endregion
3634
3865
  //#region src/runner/TasksMCPServer.ts
@@ -3866,6 +4097,7 @@ function createTasksSdkServer(task) {
3866
4097
  type: "text",
3867
4098
  text: `Failed to set up cloudflare tunnel, error: ${result.error}`
3868
4099
  }] };
4100
+ task.disableIdleTimeout();
3869
4101
  await setTaskUrl(args.taskId, `https://${result.host}`);
3870
4102
  return { content: [{
3871
4103
  type: "text",
@@ -4013,6 +4245,11 @@ var Task = class {
4013
4245
  tunnel = null;
4014
4246
  fileWatcher = null;
4015
4247
  lastFilePosition = 0;
4248
+ currentPendingLineUuid = null;
4249
+ idleTimeoutMs;
4250
+ lastActivityTime = Date.now();
4251
+ idleWatchdogInterval = null;
4252
+ idleTimeoutDisabled = false;
4016
4253
  get defaultBranch() {
4017
4254
  const tasksConfig = getTasksConfigSync();
4018
4255
  if (!tasksConfig) {
@@ -4021,16 +4258,19 @@ var Task = class {
4021
4258
  }
4022
4259
  return tasksConfig.git.default_branch;
4023
4260
  }
4261
+ get projectId() {
4262
+ return this.ctx.projectId;
4263
+ }
4024
4264
  constructor(ctx) {
4025
4265
  this.id = ctx.taskId;
4026
4266
  this.ctx = ctx;
4027
4267
  this.mcpServer = createTasksSdkServer(this);
4268
+ const timeoutMinutes = parseInt(process.env.JIVE_TASK_IDLE_TIMEOUT ?? "5", 10);
4269
+ this.idleTimeoutMs = isNaN(timeoutMinutes) ? 300 * 1e3 : timeoutMinutes * 60 * 1e3;
4270
+ this.debugLog(`Idle timeout configured: ${this.idleTimeoutMs / 6e4} minutes`);
4028
4271
  }
4029
4272
  debugLog(message) {
4030
- console.log(JSON.stringify({
4031
- type: "debug_log",
4032
- message
4033
- }).replace(/\n/g, "\\n"));
4273
+ debugLog(`[Task ${this.id}] ${message}`);
4034
4274
  }
4035
4275
  startHeartbeat() {
4036
4276
  this.heartbeatInterval = setInterval(() => {
@@ -4043,6 +4283,59 @@ var Task = class {
4043
4283
  this.heartbeatInterval = null;
4044
4284
  }
4045
4285
  }
4286
+ updateLastActivity() {
4287
+ this.lastActivityTime = Date.now();
4288
+ this.debugLog(`Activity recorded at ${new Date(this.lastActivityTime).toISOString()}`);
4289
+ }
4290
+ startIdleWatchdog() {
4291
+ this.idleWatchdogInterval = setInterval(() => {
4292
+ this.checkIdleTimeout();
4293
+ }, 3e4);
4294
+ this.debugLog("Idle watchdog started");
4295
+ }
4296
+ stopIdleWatchdog() {
4297
+ if (this.idleWatchdogInterval) {
4298
+ clearInterval(this.idleWatchdogInterval);
4299
+ this.idleWatchdogInterval = null;
4300
+ this.debugLog("Idle watchdog stopped");
4301
+ }
4302
+ }
4303
+ checkIdleTimeout() {
4304
+ if (this.idleTimeoutDisabled) {
4305
+ this.debugLog("Idle timeout is disabled, skipping check");
4306
+ return;
4307
+ }
4308
+ const idleDuration = Date.now() - this.lastActivityTime;
4309
+ const idleMinutes = Math.floor(idleDuration / 6e4);
4310
+ const idleSeconds = Math.floor(idleDuration % 6e4 / 1e3);
4311
+ this.debugLog(`Idle check: ${idleMinutes}m ${idleSeconds}s (threshold: ${this.idleTimeoutMs / 6e4}m)`);
4312
+ if (idleDuration >= this.idleTimeoutMs) {
4313
+ this.debugLog(`Idle timeout reached (${idleDuration}ms >= ${this.idleTimeoutMs}ms)`);
4314
+ this.handleIdleTimeout();
4315
+ }
4316
+ }
4317
+ async handleIdleTimeout() {
4318
+ this.debugLog("Task idle timeout - initiating graceful shutdown");
4319
+ this.stopIdleWatchdog();
4320
+ this.sendToTaskRunner({
4321
+ type: "process_status_changed",
4322
+ payload: {
4323
+ status: "exited",
4324
+ reason: "idle_timeout",
4325
+ idleDuration: Date.now() - this.lastActivityTime
4326
+ }
4327
+ });
4328
+ if (this.claudeAbortController) {
4329
+ this.debugLog("Aborting ongoing Claude query due to timeout");
4330
+ this.claudeAbortController.abort();
4331
+ this.claudeAbortController = null;
4332
+ }
4333
+ this.stopHeartbeat();
4334
+ this.stopSessionFileWatcher();
4335
+ this.cleanupCloudflareTunnel();
4336
+ this.debugLog("Task process exiting due to idle timeout");
4337
+ process.exit(0);
4338
+ }
4046
4339
  async start() {
4047
4340
  this.listenForMessages();
4048
4341
  this.sendStatusUpdate("starting");
@@ -4057,12 +4350,14 @@ var Task = class {
4057
4350
  }
4058
4351
  this.debugLog(`✓ Git repository and session file setup complete`);
4059
4352
  this.startHeartbeat();
4353
+ this.startIdleWatchdog();
4060
4354
  this.sendStatusUpdate("idle");
4061
4355
  this.listenForProcessExit();
4062
4356
  }
4063
4357
  listenForProcessExit() {
4064
4358
  const cleanup = () => {
4065
4359
  this.stopHeartbeat();
4360
+ this.stopIdleWatchdog();
4066
4361
  this.stopSessionFileWatcher();
4067
4362
  this.cleanupCloudflareTunnel();
4068
4363
  this.sendStatusUpdate("exited");
@@ -4199,6 +4494,7 @@ var Task = class {
4199
4494
  this.debugLog(`Received permission response for unknown request: ${payload.requestId}`);
4200
4495
  return;
4201
4496
  }
4497
+ this.updateLastActivity();
4202
4498
  clearTimeout(pending.timeout);
4203
4499
  pending.resolve(payload.approved);
4204
4500
  this.pendingPermissions.delete(payload.requestId);
@@ -4212,11 +4508,13 @@ var Task = class {
4212
4508
  this.debugLog(`Received question response for unknown request: ${payload.requestId}`);
4213
4509
  return;
4214
4510
  }
4511
+ this.updateLastActivity();
4215
4512
  clearTimeout(pending.timeout);
4216
4513
  pending.resolve(payload.answers);
4217
4514
  this.pendingQuestions.delete(payload.requestId);
4218
4515
  }
4219
4516
  processMessage(inputMessage) {
4517
+ this.updateLastActivity();
4220
4518
  switch (inputMessage.type) {
4221
4519
  case "stop":
4222
4520
  this.handleStop();
@@ -4264,6 +4562,14 @@ var Task = class {
4264
4562
  });
4265
4563
  }
4266
4564
  /**
4565
+ * Disable the idle timeout for this task.
4566
+ * Useful when the task is serving an application and should not timeout while waiting for user testing.
4567
+ */
4568
+ disableIdleTimeout() {
4569
+ this.idleTimeoutDisabled = true;
4570
+ this.debugLog("Idle timeout disabled");
4571
+ }
4572
+ /**
4267
4573
  * Set up Cloudflare tunnel for exposing local services to the internet.
4268
4574
  * Sets HOST and PORT environment variables for use by the task.
4269
4575
  */
@@ -4309,12 +4615,12 @@ var Task = class {
4309
4615
  }
4310
4616
  convertToHttpsUrl(repository, gitAuth) {
4311
4617
  if (repository.startsWith("https://")) return repository;
4312
- if (gitAuth.provider === "github" && repository.includes("git@github.com:")) {
4618
+ if (gitAuth.provider === "GITHUB" && repository.includes("git@github.com:")) {
4313
4619
  const httpsUrl = `https://github.com/${repository.replace("git@github.com:", "")}`;
4314
4620
  this.debugLog(`Converting SSH URL to HTTPS: ${repository} -> ${httpsUrl}`);
4315
4621
  return httpsUrl;
4316
4622
  }
4317
- if (gitAuth.provider === "gitlab" && repository.includes("git@gitlab.com:")) {
4623
+ if (gitAuth.provider === "GITLAB" && repository.includes("git@gitlab.com:")) {
4318
4624
  const httpsUrl = `https://gitlab.com/${repository.replace("git@gitlab.com:", "")}`;
4319
4625
  this.debugLog(`Converting SSH URL to HTTPS: ${repository} -> ${httpsUrl}`);
4320
4626
  return httpsUrl;
@@ -4361,9 +4667,6 @@ Host gitlab.com
4361
4667
  this.debugLog("✓ SSH authentication configured with deploy key");
4362
4668
  }
4363
4669
  /**
4364
- * Clean up SSH key files and environment variables
4365
- */
4366
- /**
4367
4670
  * Stop the Cloudflare tunnel and clean up resources
4368
4671
  */
4369
4672
  cleanupCloudflareTunnel() {
@@ -4382,8 +4685,8 @@ Host gitlab.com
4382
4685
  const { gitAuth } = this.ctx;
4383
4686
  await execAsync$1("git init");
4384
4687
  await execAsync$1("git config user.name \"Claude Agent | Jive\"");
4385
- await execAsync$1("git config user.email \"claude@tryjive.app\"");
4386
- if (gitAuth) if (gitAuth.method === "ssh") {
4688
+ await execAsync$1("git config user.email \"claude@getjive.app\"");
4689
+ if (gitAuth) if (gitAuth.method === "SSH") {
4387
4690
  await this.setupSshAuthentication(gitAuth);
4388
4691
  if (gitAuth.repositoryUrl) {
4389
4692
  repository = gitAuth.repositoryUrl;
@@ -4444,7 +4747,7 @@ Host gitlab.com
4444
4747
  sentLineIds = /* @__PURE__ */ new Set();
4445
4748
  async ensureSessionFile() {
4446
4749
  if (!await exists(this.claudeSessionFilePath)) await mkdir(path$1.dirname(this.claudeSessionFilePath), { recursive: true });
4447
- const messages = await getApiClient().getSessionLines(this.ctx.taskId, this.ctx.sessionId);
4750
+ const messages = await getApiClient().getSessionLines(this.ctx.taskId, this.ctx.sessionId, true);
4448
4751
  const sessionLines = [...messages.length === 0 ? makeInitialLines(this.ctx.claudeSessionId) : [], ...messages.map((m) => m.payload)];
4449
4752
  this.sentLineIds = new Set(sessionLines.map((l$1) => l$1.uuid ?? null));
4450
4753
  const jsonlContent = sessionLines.map((m) => JSON.stringify(m)).join("\n");
@@ -4486,18 +4789,22 @@ Host gitlab.com
4486
4789
  }
4487
4790
  });
4488
4791
  this.fileWatcher.on("change", async () => {
4792
+ this.updateLastActivity();
4489
4793
  const newLines = await this.readNewSessionLines();
4490
4794
  for (const line of newLines) try {
4491
4795
  const parsed = JSON.parse(line);
4492
4796
  const uuid = parsed.uuid ?? null;
4493
4797
  if (this.sentLineIds.has(uuid)) continue;
4494
4798
  this.sentLineIds.add(uuid);
4799
+ const pendingLineUuid = parsed.type === "user" && this.currentPendingLineUuid ? this.currentPendingLineUuid : void 0;
4800
+ if (pendingLineUuid) this.currentPendingLineUuid = null;
4495
4801
  this.sendToTaskRunner({
4496
4802
  type: "session_changed",
4497
4803
  payload: {
4498
4804
  sessionId: this.ctx.sessionId,
4499
4805
  line: parsed,
4500
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
4806
+ timestamp: parsed.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
4807
+ pendingLineUuid
4501
4808
  }
4502
4809
  });
4503
4810
  } catch (parseError) {
@@ -4517,6 +4824,7 @@ Host gitlab.com
4517
4824
  this.lastFilePosition = 0;
4518
4825
  }
4519
4826
  async handleUserMessage(payload) {
4827
+ if (payload.pendingLineUuid) this.currentPendingLineUuid = payload.pendingLineUuid;
4520
4828
  await this.queryClaude(payload.content, this.ctx.permissionMode);
4521
4829
  }
4522
4830
  claudeAbortController = null;
@@ -4525,11 +4833,12 @@ Host gitlab.com
4525
4833
  this.claudeAbortController = null;
4526
4834
  this.sendStatusUpdate("idle");
4527
4835
  }
4528
- async queryClaude(prompt, mode = "bypassPermissions") {
4836
+ async queryClaude(prompt, mode = "BYPASS_PERMISSIONS") {
4529
4837
  if (this.status !== "idle") {
4530
4838
  this.debugLog(`WARNING: queryClaude called while status is '${this.status}'`);
4531
4839
  return;
4532
4840
  }
4841
+ this.updateLastActivity();
4533
4842
  this.sendStatusUpdate("working");
4534
4843
  this.claudeAbortController = new AbortController();
4535
4844
  await this.startSessionFileWatcher();
@@ -4702,18 +5011,43 @@ async function setupRunnerCommand(options) {
4702
5011
  }
4703
5012
  await requireAuth();
4704
5013
  try {
4705
- const answers = await prompts([{
4706
- type: options.name ? null : "text",
4707
- name: "name",
4708
- message: "Runner name:",
4709
- initial: `runner-${Date.now()}`,
4710
- validate: (value$1) => value$1.length > 0 || "Name is required"
4711
- }, {
4712
- type: "password",
4713
- name: "anthropicApiKey",
4714
- message: "Anthropic API key (https://console.anthropic.com/settings/keys):",
4715
- validate: (value$1) => value$1.startsWith("sk-ant-") || "Invalid API key format"
4716
- }]);
5014
+ const answers = await prompts([
5015
+ {
5016
+ type: options.name ? null : "text",
5017
+ name: "name",
5018
+ message: "Runner name:",
5019
+ initial: `runner-${Date.now()}`,
5020
+ validate: (value$1) => value$1.length > 0 || "Name is required"
5021
+ },
5022
+ {
5023
+ type: "password",
5024
+ name: "anthropicApiKey",
5025
+ message: "Anthropic API key (https://console.anthropic.com/settings/keys):",
5026
+ validate: (value$1) => value$1.startsWith("sk-ant-") || "Invalid API key format"
5027
+ },
5028
+ {
5029
+ type: "number",
5030
+ name: "dockerCpus",
5031
+ message: "Docker CPU cores:",
5032
+ initial: 2,
5033
+ min: 1
5034
+ },
5035
+ {
5036
+ type: "text",
5037
+ name: "dockerMemory",
5038
+ message: "Docker memory limit (e.g., 2g, 4g, 512m):",
5039
+ initial: "2g",
5040
+ validate: (val) => /^\d+[kmg]$/i.test(val) || "Format: number + unit (k/m/g)"
5041
+ },
5042
+ {
5043
+ type: "number",
5044
+ name: "maxConcurrentTasks",
5045
+ message: "Maximum concurrent tasks:",
5046
+ initial: 5,
5047
+ min: 1,
5048
+ max: 50
5049
+ }
5050
+ ]);
4717
5051
  if (!answers.name && !options.name) {
4718
5052
  console.log(chalk.yellow("Setup cancelled"));
4719
5053
  return;
@@ -4736,7 +5070,14 @@ async function setupRunnerCommand(options) {
4736
5070
  name: runnerName,
4737
5071
  type: runnerType,
4738
5072
  teamId: team$1.id.toString(),
4739
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
5073
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5074
+ resourceLimits: {
5075
+ maxConcurrentTasks: answers.maxConcurrentTasks,
5076
+ docker: {
5077
+ cpus: answers.dockerCpus.toString(),
5078
+ memory: answers.dockerMemory
5079
+ }
5080
+ }
4740
5081
  });
4741
5082
  await updateCredentials({ anthropicApiKey });
4742
5083
  spinner.succeed(`Runner '${runnerName}' configured!`);
@@ -5106,7 +5447,7 @@ const taskCommands = { resume: resumeCommand };
5106
5447
 
5107
5448
  //#endregion
5108
5449
  //#region package.json
5109
- var version = "0.0.30";
5450
+ var version = "0.0.32";
5110
5451
 
5111
5452
  //#endregion
5112
5453
  //#region src/index.ts
@@ -5131,4 +5472,4 @@ program.command("task").description("Manage tasks").command("resume").descriptio
5131
5472
  program.parse();
5132
5473
 
5133
5474
  //#endregion
5134
- export { };
5475
+ export { queries as a, mutations as i, cliMutations as n, cliQueries as r, getGraphQLClient as t };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@jive-ai/cli",
4
- "version": "0.0.30",
4
+ "version": "0.0.32",
5
5
  "main": "index.js",
6
6
  "files": [
7
7
  "dist",
@@ -15,8 +15,11 @@
15
15
  "test": "echo \"Error: no test specified\" && exit 1",
16
16
  "typecheck": "tsc --noEmit",
17
17
  "build": "tsdown && npm pack && npm install -g jive-ai-cli-*.tgz",
18
- "docker:build": "touch jive-ai-cli-0.0.0.tgz && docker build -t jive-task-runner:latest . && rm -f jive-ai-cli-*.tgz",
19
- "docker:build:local": "bun run build && docker build --build-arg USE_LOCAL=true -t jive-task-runner:latest .",
18
+ "docker:build": "touch jive-ai-cli-0.0.0.tgz && docker build -t jive-ai/task:latest . && rm -f jive-ai-cli-*.tgz",
19
+ "docker:build:local": "bun run build && docker build --build-arg USE_LOCAL=true -t jive-ai/task:latest .",
20
+ "docker:build:public": "touch jive-ai-cli-0.0.0.tgz && docker buildx build --platform linux/amd64,linux/arm64 -t jive-ai/task:latest -t jive-ai/task:$npm_package_version . && rm -f jive-ai-cli-*.tgz",
21
+ "docker:push": "docker buildx build --platform linux/amd64,linux/arm64 --push -t jive-ai/task:latest -t jive-ai/task:$npm_package_version .",
22
+ "docker:publish": "touch jive-ai-cli-0.0.0.tgz && docker buildx build --platform linux/amd64,linux/arm64 --push -t jive-ai/task:latest -t jive-ai/task:$npm_package_version . && rm -f jive-ai-cli-*.tgz",
20
23
  "prepublishOnly": "npm run typecheck && npm run build"
21
24
  },
22
25
  "author": "",
@@ -31,7 +34,7 @@
31
34
  "tsx": "^4.20.6"
32
35
  },
33
36
  "dependencies": {
34
- "@anthropic-ai/claude-agent-sdk": "^0.1.46",
37
+ "@anthropic-ai/claude-agent-sdk": "^0.2.7",
35
38
  "@modelcontextprotocol/sdk": "^1.22.0",
36
39
  "chalk": "^5.6.2",
37
40
  "change-case": "^5.4.4",
@@ -50,6 +53,6 @@
50
53
  "typescript": "^5.9.3",
51
54
  "uuid": "^13.0.0",
52
55
  "ws": "^8.18.0",
53
- "zod": "^3.25.76"
56
+ "zod": "^4.3.5"
54
57
  }
55
58
  }