@knowsuchagency/fulcrum 1.4.0 → 1.5.0

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/bin/fulcrum.js CHANGED
@@ -43866,12 +43866,16 @@ function registerTools(server, client) {
43866
43866
  return handleToolError(err);
43867
43867
  }
43868
43868
  });
43869
- server.tool("get_task", "Get details of a specific task by ID", {
43869
+ server.tool("get_task", "Get details of a specific task by ID, including dependencies and attachments", {
43870
43870
  id: exports_external.string().describe("Task ID (UUID)")
43871
43871
  }, async ({ id }) => {
43872
43872
  try {
43873
- const task = await client.getTask(id);
43874
- return formatSuccess(task);
43873
+ const [task, dependencies, attachments] = await Promise.all([
43874
+ client.getTask(id),
43875
+ client.getTaskDependencies(id),
43876
+ client.listTaskAttachments(id)
43877
+ ]);
43878
+ return formatSuccess({ ...task, dependencies, attachments });
43875
43879
  } catch (err) {
43876
43880
  return handleToolError(err);
43877
43881
  }
@@ -46168,15 +46172,62 @@ var STATUS_MAP = {
46168
46172
  cancel: "CANCELED",
46169
46173
  "in-progress": "IN_PROGRESS"
46170
46174
  };
46171
- function formatTask(task) {
46175
+ function formatTask(task, dependencies, attachments) {
46172
46176
  console.log(`${task.title}`);
46173
- console.log(` ID: ${task.id}`);
46174
- console.log(` Status: ${task.status}`);
46175
- console.log(` Repo: ${task.repoName}`);
46177
+ console.log(` ID: ${task.id}`);
46178
+ console.log(` Status: ${task.status}`);
46179
+ if (task.description) {
46180
+ console.log(` Description: ${task.description}`);
46181
+ }
46182
+ if (task.repoName)
46183
+ console.log(` Repo: ${task.repoName}`);
46176
46184
  if (task.branch)
46177
- console.log(` Branch: ${task.branch}`);
46185
+ console.log(` Branch: ${task.branch}`);
46186
+ if (task.worktreePath)
46187
+ console.log(` Worktree: ${task.worktreePath}`);
46178
46188
  if (task.prUrl)
46179
- console.log(` PR: ${task.prUrl}`);
46189
+ console.log(` PR: ${task.prUrl}`);
46190
+ if (task.links && task.links.length > 0) {
46191
+ console.log(` Links: ${task.links.map((l2) => l2.label || l2.url).join(", ")}`);
46192
+ }
46193
+ if (task.labels && task.labels.length > 0) {
46194
+ console.log(` Labels: ${task.labels.join(", ")}`);
46195
+ }
46196
+ if (task.dueDate)
46197
+ console.log(` Due: ${task.dueDate}`);
46198
+ if (task.projectId)
46199
+ console.log(` Project: ${task.projectId}`);
46200
+ console.log(` Agent: ${task.agent}`);
46201
+ if (task.aiMode)
46202
+ console.log(` AI Mode: ${task.aiMode}`);
46203
+ if (task.agentOptions && Object.keys(task.agentOptions).length > 0) {
46204
+ console.log(` Options: ${JSON.stringify(task.agentOptions)}`);
46205
+ }
46206
+ if (dependencies) {
46207
+ if (dependencies.isBlocked) {
46208
+ console.log(` Blocked: Yes`);
46209
+ }
46210
+ if (dependencies.dependsOn.length > 0) {
46211
+ console.log(` Depends on: ${dependencies.dependsOn.length} task(s)`);
46212
+ for (const dep of dependencies.dependsOn) {
46213
+ if (dep.task) {
46214
+ console.log(` - ${dep.task.title} [${dep.task.status}]`);
46215
+ }
46216
+ }
46217
+ }
46218
+ if (dependencies.dependents.length > 0) {
46219
+ console.log(` Blocking: ${dependencies.dependents.length} task(s)`);
46220
+ }
46221
+ }
46222
+ if (attachments && attachments.length > 0) {
46223
+ console.log(` Attachments: ${attachments.length} file(s)`);
46224
+ }
46225
+ if (task.notes) {
46226
+ console.log(` Notes: ${task.notes}`);
46227
+ }
46228
+ console.log(` Created: ${task.createdAt}`);
46229
+ if (task.startedAt)
46230
+ console.log(` Started: ${task.startedAt}`);
46180
46231
  }
46181
46232
  async function findCurrentTask(client, pathOverride) {
46182
46233
  if (process.env.FULCRUM_TASK_ID) {
@@ -46204,9 +46255,17 @@ async function handleCurrentTaskCommand(action, rest, flags) {
46204
46255
  if (!action) {
46205
46256
  const task2 = await findCurrentTask(client, pathOverride);
46206
46257
  if (isJsonOutput()) {
46207
- output(task2);
46258
+ const [dependencies, attachments] = await Promise.all([
46259
+ client.getTaskDependencies(task2.id),
46260
+ client.listTaskAttachments(task2.id)
46261
+ ]);
46262
+ output({ ...task2, dependencies, attachments });
46208
46263
  } else {
46209
- formatTask(task2);
46264
+ const [dependencies, attachments] = await Promise.all([
46265
+ client.getTaskDependencies(task2.id),
46266
+ client.listTaskAttachments(task2.id)
46267
+ ]);
46268
+ formatTask(task2, dependencies, attachments);
46210
46269
  }
46211
46270
  return;
46212
46271
  }
@@ -46297,22 +46356,62 @@ init_client();
46297
46356
  import { basename as basename3 } from "path";
46298
46357
  init_errors();
46299
46358
  var VALID_STATUSES = ["TO_DO", "IN_PROGRESS", "IN_REVIEW", "CANCELED"];
46300
- function formatTask2(task) {
46359
+ function formatTask2(task, dependencies, attachments) {
46301
46360
  console.log(`${task.title}`);
46302
- console.log(` ID: ${task.id}`);
46303
- console.log(` Status: ${task.status}`);
46361
+ console.log(` ID: ${task.id}`);
46362
+ console.log(` Status: ${task.status}`);
46363
+ if (task.description) {
46364
+ console.log(` Description: ${task.description}`);
46365
+ }
46304
46366
  if (task.repoName)
46305
- console.log(` Repo: ${task.repoName}`);
46367
+ console.log(` Repo: ${task.repoName}`);
46306
46368
  if (task.branch)
46307
- console.log(` Branch: ${task.branch}`);
46369
+ console.log(` Branch: ${task.branch}`);
46370
+ if (task.worktreePath)
46371
+ console.log(` Worktree: ${task.worktreePath}`);
46308
46372
  if (task.prUrl)
46309
- console.log(` PR: ${task.prUrl}`);
46310
- if (task.projectId)
46311
- console.log(` Project: ${task.projectId}`);
46312
- if (task.labels && task.labels.length > 0)
46313
- console.log(` Labels: ${task.labels.join(", ")}`);
46373
+ console.log(` PR: ${task.prUrl}`);
46374
+ if (task.links && task.links.length > 0) {
46375
+ console.log(` Links: ${task.links.map((l2) => l2.label || l2.url).join(", ")}`);
46376
+ }
46377
+ if (task.labels && task.labels.length > 0) {
46378
+ console.log(` Labels: ${task.labels.join(", ")}`);
46379
+ }
46314
46380
  if (task.dueDate)
46315
- console.log(` Due: ${task.dueDate}`);
46381
+ console.log(` Due: ${task.dueDate}`);
46382
+ if (task.projectId)
46383
+ console.log(` Project: ${task.projectId}`);
46384
+ console.log(` Agent: ${task.agent}`);
46385
+ if (task.aiMode)
46386
+ console.log(` AI Mode: ${task.aiMode}`);
46387
+ if (task.agentOptions && Object.keys(task.agentOptions).length > 0) {
46388
+ console.log(` Options: ${JSON.stringify(task.agentOptions)}`);
46389
+ }
46390
+ if (dependencies) {
46391
+ if (dependencies.isBlocked) {
46392
+ console.log(` Blocked: Yes`);
46393
+ }
46394
+ if (dependencies.dependsOn.length > 0) {
46395
+ console.log(` Depends on: ${dependencies.dependsOn.length} task(s)`);
46396
+ for (const dep of dependencies.dependsOn) {
46397
+ if (dep.task) {
46398
+ console.log(` - ${dep.task.title} [${dep.task.status}]`);
46399
+ }
46400
+ }
46401
+ }
46402
+ if (dependencies.dependents.length > 0) {
46403
+ console.log(` Blocking: ${dependencies.dependents.length} task(s)`);
46404
+ }
46405
+ }
46406
+ if (attachments && attachments.length > 0) {
46407
+ console.log(` Attachments: ${attachments.length} file(s)`);
46408
+ }
46409
+ if (task.notes) {
46410
+ console.log(` Notes: ${task.notes}`);
46411
+ }
46412
+ console.log(` Created: ${task.createdAt}`);
46413
+ if (task.startedAt)
46414
+ console.log(` Started: ${task.startedAt}`);
46316
46415
  }
46317
46416
  function formatTaskList(tasks) {
46318
46417
  if (tasks.length === 0) {
@@ -46394,10 +46493,14 @@ async function handleTasksCommand(action, positional, flags) {
46394
46493
  throw new CliError("MISSING_ID", "Task ID required", ExitCodes.INVALID_ARGS);
46395
46494
  }
46396
46495
  const task = await client.getTask(id);
46496
+ const [dependencies, attachments] = await Promise.all([
46497
+ client.getTaskDependencies(id),
46498
+ client.listTaskAttachments(id)
46499
+ ]);
46397
46500
  if (isJsonOutput()) {
46398
- output(task);
46501
+ output({ ...task, dependencies, attachments });
46399
46502
  } else {
46400
- formatTask2(task);
46503
+ formatTask2(task, dependencies, attachments);
46401
46504
  }
46402
46505
  break;
46403
46506
  }
@@ -46649,8 +46752,98 @@ Labels:`);
46649
46752
  }
46650
46753
  break;
46651
46754
  }
46755
+ case "attachments": {
46756
+ const [subAction, taskIdOrFile, fileOrAttachmentId] = positional;
46757
+ if (!subAction || subAction === "help") {
46758
+ console.log("Usage:");
46759
+ console.log(" fulcrum tasks attachments list <task-id>");
46760
+ console.log(" fulcrum tasks attachments upload <task-id> <file-path>");
46761
+ console.log(" fulcrum tasks attachments delete <task-id> <attachment-id>");
46762
+ console.log(" fulcrum tasks attachments path <task-id> <attachment-id>");
46763
+ break;
46764
+ }
46765
+ if (subAction === "list") {
46766
+ const taskId = taskIdOrFile;
46767
+ if (!taskId) {
46768
+ throw new CliError("MISSING_ID", "Task ID required", ExitCodes.INVALID_ARGS);
46769
+ }
46770
+ const attachments = await client.listTaskAttachments(taskId);
46771
+ if (isJsonOutput()) {
46772
+ output(attachments);
46773
+ } else {
46774
+ if (attachments.length === 0) {
46775
+ console.log("No attachments");
46776
+ } else {
46777
+ console.log(`
46778
+ Attachments (${attachments.length}):`);
46779
+ for (const att of attachments) {
46780
+ console.log(` ${att.filename}`);
46781
+ console.log(` ID: ${att.id}`);
46782
+ console.log(` Type: ${att.mimeType}`);
46783
+ console.log(` Size: ${att.size} bytes`);
46784
+ }
46785
+ }
46786
+ }
46787
+ break;
46788
+ }
46789
+ if (subAction === "upload") {
46790
+ const taskId = taskIdOrFile;
46791
+ const filePath = fileOrAttachmentId;
46792
+ if (!taskId) {
46793
+ throw new CliError("MISSING_ID", "Task ID required", ExitCodes.INVALID_ARGS);
46794
+ }
46795
+ if (!filePath) {
46796
+ throw new CliError("MISSING_FILE", "File path required", ExitCodes.INVALID_ARGS);
46797
+ }
46798
+ const attachment = await client.uploadTaskAttachment(taskId, filePath);
46799
+ if (isJsonOutput()) {
46800
+ output(attachment);
46801
+ } else {
46802
+ console.log(`Uploaded: ${attachment.filename}`);
46803
+ console.log(` ID: ${attachment.id}`);
46804
+ }
46805
+ break;
46806
+ }
46807
+ if (subAction === "delete") {
46808
+ const taskId = taskIdOrFile;
46809
+ const attachmentId = fileOrAttachmentId;
46810
+ if (!taskId) {
46811
+ throw new CliError("MISSING_ID", "Task ID required", ExitCodes.INVALID_ARGS);
46812
+ }
46813
+ if (!attachmentId) {
46814
+ throw new CliError("MISSING_ATTACHMENT_ID", "Attachment ID required", ExitCodes.INVALID_ARGS);
46815
+ }
46816
+ await client.deleteTaskAttachment(taskId, attachmentId);
46817
+ if (isJsonOutput()) {
46818
+ output({ success: true, deleted: attachmentId });
46819
+ } else {
46820
+ console.log(`Deleted attachment: ${attachmentId}`);
46821
+ }
46822
+ break;
46823
+ }
46824
+ if (subAction === "path") {
46825
+ const taskId = taskIdOrFile;
46826
+ const attachmentId = fileOrAttachmentId;
46827
+ if (!taskId) {
46828
+ throw new CliError("MISSING_ID", "Task ID required", ExitCodes.INVALID_ARGS);
46829
+ }
46830
+ if (!attachmentId) {
46831
+ throw new CliError("MISSING_ATTACHMENT_ID", "Attachment ID required", ExitCodes.INVALID_ARGS);
46832
+ }
46833
+ const result = await client.getTaskAttachmentPath(taskId, attachmentId);
46834
+ if (isJsonOutput()) {
46835
+ output(result);
46836
+ } else {
46837
+ console.log(`Path: ${result.path}`);
46838
+ console.log(`Filename: ${result.filename}`);
46839
+ console.log(`Type: ${result.mimeType}`);
46840
+ }
46841
+ break;
46842
+ }
46843
+ throw new CliError("UNKNOWN_SUBACTION", `Unknown attachments action: ${subAction}. Valid: list, upload, delete, path`, ExitCodes.INVALID_ARGS);
46844
+ }
46652
46845
  default:
46653
- throw new CliError("UNKNOWN_ACTION", `Unknown action: ${action}. Valid: list, get, create, update, move, delete, add-label, remove-label, set-due-date, add-dependency, remove-dependency, list-dependencies, labels`, ExitCodes.INVALID_ARGS);
46846
+ throw new CliError("UNKNOWN_ACTION", `Unknown action: ${action}. Valid: list, get, create, update, move, delete, add-label, remove-label, set-due-date, add-dependency, remove-dependency, list-dependencies, labels, attachments`, ExitCodes.INVALID_ARGS);
46654
46847
  }
46655
46848
  }
46656
46849
 
@@ -47618,7 +47811,7 @@ async function handleFsCommand(action, positional, flags) {
47618
47811
  // cli/src/commands/up.ts
47619
47812
  import { spawn } from "child_process";
47620
47813
  import { existsSync as existsSync4 } from "fs";
47621
- import { dirname as dirname2, join as join4 } from "path";
47814
+ import { dirname as dirname3, join as join4 } from "path";
47622
47815
  import { fileURLToPath } from "url";
47623
47816
  init_errors();
47624
47817
 
@@ -47902,22 +48095,38 @@ function installUv() {
47902
48095
 
47903
48096
  // cli/src/commands/claude.ts
47904
48097
  init_errors();
48098
+ import { spawnSync as spawnSync2 } from "child_process";
47905
48099
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, existsSync as existsSync3, rmSync, readFileSync as readFileSync4 } from "fs";
47906
48100
  import { homedir as homedir2 } from "os";
47907
- import { join as join3 } from "path";
48101
+ import { dirname as dirname2, join as join3 } from "path";
47908
48102
 
47909
- // plugins/fulcrum/.claude-plugin/plugin.json
47910
- var plugin_default = `{
48103
+ // plugins/fulcrum/.claude-plugin/marketplace.json
48104
+ var marketplace_default = `{
47911
48105
  "name": "fulcrum",
47912
- "description": "Fulcrum task orchestration for Claude Code",
47913
- "version": "1.4.0",
47914
- "author": {
48106
+ "owner": {
47915
48107
  "name": "Fulcrum"
47916
48108
  },
47917
- "hooks": "./hooks/hooks.json",
47918
- "mcpServers": "./.mcp.json",
47919
- "skills": "./skills/",
47920
- "commands": "./commands/"
48109
+ "plugins": [
48110
+ {
48111
+ "name": "fulcrum",
48112
+ "source": "./",
48113
+ "description": "Task orchestration for Claude Code",
48114
+ "version": "1.5.0",
48115
+ "skills": [
48116
+ "./skills/fulcrum"
48117
+ ],
48118
+ "keywords": [
48119
+ "fulcrum",
48120
+ "task",
48121
+ "orchestration",
48122
+ "worktree"
48123
+ ]
48124
+ }
48125
+ ],
48126
+ "metadata": {
48127
+ "description": "Fulcrum task orchestration plugin marketplace",
48128
+ "version": "1.0.0"
48129
+ }
47921
48130
  }
47922
48131
  `;
47923
48132
 
@@ -47984,38 +48193,25 @@ Send a notification: \`fulcrum notify $ARGUMENTS\`
47984
48193
  Format: fulcrum notify "Title" "Message body"
47985
48194
  `;
47986
48195
 
47987
- // plugins/fulcrum/commands/linear.md
47988
- var linear_default = `---
47989
- description: Link a Linear ticket to the current fulcrum task
47990
- ---
47991
- Link the Linear ticket to this task: \`fulcrum current-task linear $ARGUMENTS\`
47992
- `;
47993
-
47994
- // plugins/fulcrum/commands/review.md
47995
- var review_default = `---
47996
- description: Mark the current fulcrum task as ready for review
47997
- ---
47998
- Mark this task ready for review: \`fulcrum current-task review\`
47999
-
48000
- This sends a notification to the user.
48001
- `;
48002
-
48003
- // plugins/fulcrum/skills/vibora/SKILL.md
48196
+ // plugins/fulcrum/skills/fulcrum/SKILL.md
48004
48197
  var SKILL_default = `---
48005
48198
  name: fulcrum
48006
- description: Fulcrum is a terminal-first tool for orchestrating AI coding agents across isolated git worktrees. Use this skill when working in a Fulcrum task worktree or managing tasks.
48199
+ description: AI orchestration and task management platform. Use this skill when working in a Fulcrum task worktree, managing tasks/projects, or interacting with the Fulcrum server.
48007
48200
  ---
48008
48201
 
48009
- # Fulcrum - AI Agent Orchestration
48202
+ # Fulcrum - AI Orchestration Platform
48010
48203
 
48011
48204
  ## Overview
48012
48205
 
48013
- Fulcrum is a terminal-first tool for orchestrating AI coding agents (like Claude Code) across isolated git worktrees. Each task runs in its own worktree, enabling parallel work on multiple features or fixes without branch switching.
48206
+ Fulcrum is an AI orchestration and task management platform. It provides task tracking, project management, and tools for AI agents to work autonomously across isolated git worktrees.
48014
48207
 
48015
- **Philosophy:**
48016
- - Agents run natively in terminals - no abstraction layer or wrapper APIs
48017
- - Tasks create isolated git worktrees for clean separation
48018
- - Persistent terminals organized in tabs across tasks
48208
+ **Key Features:**
48209
+ - Task management with kanban boards and status tracking
48210
+ - Project and repository organization
48211
+ - Git worktree isolation for parallel development
48212
+ - Docker Compose app deployment
48213
+ - Multi-channel notifications
48214
+ - MCP tools for AI agent integration
48019
48215
 
48020
48216
  ## When to Use This Skill
48021
48217
 
@@ -48023,9 +48219,9 @@ Use the Fulcrum CLI when:
48023
48219
  - **Working in a task worktree** - Use \`current-task\` commands to manage your current task
48024
48220
  - **Updating task status** - Mark tasks as in-progress, ready for review, done, or canceled
48025
48221
  - **Linking PRs** - Associate a GitHub PR with the current task
48026
- - **Linking Linear tickets** - Connect a Linear issue to the current task
48027
48222
  - **Linking URLs** - Attach any relevant URLs (design docs, specs, external resources) to the task
48028
48223
  - **Sending notifications** - Alert the user when work is complete or needs attention
48224
+ - **Managing projects and repositories** - Create, update, and organize projects
48029
48225
 
48030
48226
  Use the Fulcrum MCP tools when:
48031
48227
  - **Executing commands remotely** - Run shell commands on the Fulcrum server from Claude Desktop
@@ -48038,7 +48234,7 @@ Use the Fulcrum MCP tools when:
48038
48234
  When running inside a Fulcrum task worktree, use these commands to manage the current task:
48039
48235
 
48040
48236
  \`\`\`bash
48041
- # Get current task info (JSON output)
48237
+ # Get current task info (comprehensive output with dependencies, attachments, etc.)
48042
48238
  fulcrum current-task
48043
48239
 
48044
48240
  # Update task status
@@ -48075,7 +48271,7 @@ fulcrum tasks list --label="bug" # Filter by label
48075
48271
  fulcrum tasks labels # Show all labels with counts
48076
48272
  fulcrum tasks labels --search="comm" # Find labels matching substring
48077
48273
 
48078
- # Get a specific task
48274
+ # Get a specific task (includes dependencies and attachments)
48079
48275
  fulcrum tasks get <task-id>
48080
48276
 
48081
48277
  # Create a new task
@@ -48090,6 +48286,25 @@ fulcrum tasks move <task-id> --status=IN_REVIEW
48090
48286
  # Delete a task
48091
48287
  fulcrum tasks delete <task-id>
48092
48288
  fulcrum tasks delete <task-id> --delete-worktree # Also delete worktree
48289
+
48290
+ # Labels
48291
+ fulcrum tasks add-label <task-id> <label>
48292
+ fulcrum tasks remove-label <task-id> <label>
48293
+
48294
+ # Due dates
48295
+ fulcrum tasks set-due-date <task-id> 2026-01-25 # Set due date
48296
+ fulcrum tasks set-due-date <task-id> none # Clear due date
48297
+
48298
+ # Dependencies
48299
+ fulcrum tasks add-dependency <task-id> <depends-on-task-id>
48300
+ fulcrum tasks remove-dependency <task-id> <dependency-id>
48301
+ fulcrum tasks list-dependencies <task-id>
48302
+
48303
+ # Attachments
48304
+ fulcrum tasks attachments list <task-id>
48305
+ fulcrum tasks attachments upload <task-id> <file-path>
48306
+ fulcrum tasks attachments delete <task-id> <attachment-id>
48307
+ fulcrum tasks attachments path <task-id> <attachment-id> # Get local file path
48093
48308
  \`\`\`
48094
48309
 
48095
48310
  ### notifications
@@ -48123,7 +48338,28 @@ fulcrum notifications set slack webhookUrl <url>
48123
48338
  fulcrum up # Start Fulcrum server daemon
48124
48339
  fulcrum down # Stop Fulcrum server
48125
48340
  fulcrum status # Check if server is running
48126
- fulcrum health # Check server health
48341
+ fulcrum doctor # Check all dependencies and versions
48342
+ \`\`\`
48343
+
48344
+ ### Configuration
48345
+
48346
+ \`\`\`bash
48347
+ fulcrum config list # List all config values
48348
+ fulcrum config get <key> # Get a specific value
48349
+ fulcrum config set <key> <value> # Set a value
48350
+ fulcrum config reset <key> # Reset to default
48351
+ \`\`\`
48352
+
48353
+ ### Agent Plugin Installation
48354
+
48355
+ \`\`\`bash
48356
+ # Claude Code integration
48357
+ fulcrum claude install # Install Fulcrum plugin for Claude Code
48358
+ fulcrum claude uninstall # Uninstall plugin
48359
+
48360
+ # OpenCode integration
48361
+ fulcrum opencode install # Install Fulcrum plugin for OpenCode
48362
+ fulcrum opencode uninstall # Uninstall plugin
48127
48363
  \`\`\`
48128
48364
 
48129
48365
  ### Git Operations
@@ -48131,7 +48367,15 @@ fulcrum health # Check server health
48131
48367
  \`\`\`bash
48132
48368
  fulcrum git status # Git status for current worktree
48133
48369
  fulcrum git diff # Git diff for current worktree
48370
+ fulcrum git diff --staged # Show staged changes only
48371
+ fulcrum git branches --repo=/path/to/repo # List branches
48372
+ \`\`\`
48373
+
48374
+ ### Worktrees
48375
+
48376
+ \`\`\`bash
48134
48377
  fulcrum worktrees list # List all worktrees
48378
+ fulcrum worktrees delete --path=/path/to/worktree # Delete a worktree
48135
48379
  \`\`\`
48136
48380
 
48137
48381
  ### projects
@@ -48314,10 +48558,11 @@ These flags work with most commands:
48314
48558
 
48315
48559
  - \`--port=<port>\` - Server port (default: 7777)
48316
48560
  - \`--url=<url>\` - Override full server URL
48317
- - \`--pretty\` - Pretty-print JSON output for human readability
48561
+ - \`--json\` - Output as JSON for programmatic use
48318
48562
 
48319
48563
  ## Task Statuses
48320
48564
 
48565
+ - \`TO_DO\` - Task not yet started
48321
48566
  - \`IN_PROGRESS\` - Task is being worked on
48322
48567
  - \`IN_REVIEW\` - Task is complete and awaiting review
48323
48568
  - \`DONE\` - Task is finished
@@ -48354,7 +48599,7 @@ search_tools { category: "filesystem" }
48354
48599
  ### Task Tools
48355
48600
 
48356
48601
  - \`list_tasks\` - List tasks with flexible filtering (search, labels, statuses, date range, overdue)
48357
- - \`get_task\` - Get task details by ID
48602
+ - \`get_task\` - Get task details by ID (includes dependencies and attachments)
48358
48603
  - \`create_task\` - Create a new task with worktree
48359
48604
  - \`update_task\` - Update task metadata
48360
48605
  - \`delete_task\` - Delete a task
@@ -48362,10 +48607,24 @@ search_tools { category: "filesystem" }
48362
48607
  - \`add_task_link\` - Add URL link to task
48363
48608
  - \`remove_task_link\` - Remove link from task
48364
48609
  - \`list_task_links\` - List all task links
48365
- - \`add_task_label\` - Add a label to a task (returns similar labels to catch typos)
48366
- - \`remove_task_label\` - Remove a label from a task
48610
+ - \`add_task_tag\` - Add a label to a task (returns similar labels to catch typos)
48611
+ - \`remove_task_tag\` - Remove a label from a task
48367
48612
  - \`set_task_due_date\` - Set or clear task due date
48368
- - \`list_labels\` - List all unique labels in use with optional search
48613
+ - \`list_tags\` - List all unique labels in use with optional search
48614
+
48615
+ #### Task Dependencies
48616
+
48617
+ - \`get_task_dependencies\` - Get dependencies and dependents of a task, and whether it is blocked
48618
+ - \`add_task_dependency\` - Add a dependency (task cannot start until dependency is done)
48619
+ - \`remove_task_dependency\` - Remove a dependency
48620
+ - \`get_task_dependency_graph\` - Get all tasks and dependencies as a graph for visualization
48621
+
48622
+ #### Task Attachments
48623
+
48624
+ - \`list_task_attachments\` - List all file attachments for a task
48625
+ - \`upload_task_attachment\` - Upload a file to a task from a local path
48626
+ - \`delete_task_attachment\` - Delete a file attachment from a task
48627
+ - \`get_task_attachment_path\` - Get the local file path for a task attachment
48369
48628
 
48370
48629
  #### Task Search and Filtering
48371
48630
 
@@ -48402,6 +48661,21 @@ This helps handle typos and variations - search first, then use the exact label
48402
48661
  - \`update_project\` - Update name, description, status
48403
48662
  - \`delete_project\` - Delete project and optionally directory/app
48404
48663
  - \`scan_projects\` - Scan directory for git repos
48664
+
48665
+ #### Project Tags
48666
+
48667
+ - \`add_project_tag\` - Add a tag to a project (creates new if needed)
48668
+ - \`remove_project_tag\` - Remove a tag from a project
48669
+
48670
+ #### Project Attachments
48671
+
48672
+ - \`list_project_attachments\` - List all file attachments for a project
48673
+ - \`upload_project_attachment\` - Upload a file to a project from a local path
48674
+ - \`delete_project_attachment\` - Delete a file attachment from a project
48675
+ - \`get_project_attachment_path\` - Get the local file path for a project attachment
48676
+
48677
+ #### Project Links
48678
+
48405
48679
  - \`list_project_links\` - List all URL links attached to a project
48406
48680
  - \`add_project_link\` - Add a URL link to a project (auto-detects type)
48407
48681
  - \`remove_project_link\` - Remove a URL link from a project
@@ -48524,87 +48798,47 @@ Clean up a session when you're done to free resources.
48524
48798
  `;
48525
48799
 
48526
48800
  // cli/src/commands/claude.ts
48527
- var PLUGIN_DIR = join3(homedir2(), ".claude", "plugins", "fulcrum");
48801
+ var MARKETPLACE_DIR = join3(homedir2(), ".fulcrum", "claude-plugin");
48802
+ var MARKETPLACE_NAME = "fulcrum";
48803
+ var PLUGIN_NAME = "fulcrum";
48804
+ var PLUGIN_ID = `${PLUGIN_NAME}@${MARKETPLACE_NAME}`;
48528
48805
  var PLUGIN_FILES = [
48529
- { path: ".claude-plugin/plugin.json", content: plugin_default },
48806
+ { path: ".claude-plugin/marketplace.json", content: marketplace_default },
48530
48807
  { path: "hooks/hooks.json", content: hooks_default },
48531
48808
  { path: ".mcp.json", content: _mcp_default },
48532
48809
  { path: "commands/pr.md", content: pr_default },
48533
48810
  { path: "commands/task-info.md", content: task_info_default },
48534
48811
  { path: "commands/notify.md", content: notify_default },
48535
- { path: "commands/linear.md", content: linear_default },
48536
- { path: "commands/review.md", content: review_default },
48537
- { path: "skills/vibora/SKILL.md", content: SKILL_default }
48812
+ { path: "skills/fulcrum/SKILL.md", content: SKILL_default }
48538
48813
  ];
48539
- var PLUGIN_ID = "fulcrum@fulcrum";
48540
- var INSTALLED_PLUGINS_PATH = join3(homedir2(), ".claude", "plugins", "installed_plugins.json");
48541
- var CLAUDE_SETTINGS_PATH = join3(homedir2(), ".claude", "settings.json");
48542
- function getPluginVersion() {
48814
+ var LEGACY_PLUGIN_DIR = join3(homedir2(), ".claude", "plugins", "fulcrum");
48815
+ var LEGACY_CACHE_DIR = join3(homedir2(), ".claude", "plugins", "cache", "fulcrum");
48816
+ function runClaude(args) {
48817
+ const result = spawnSync2("claude", args, { encoding: "utf-8" });
48818
+ return {
48819
+ success: result.status === 0,
48820
+ output: (result.stdout || "") + (result.stderr || "")
48821
+ };
48822
+ }
48823
+ function getBundledVersion() {
48543
48824
  try {
48544
- const parsed = JSON.parse(plugin_default);
48545
- return parsed.version || "1.0.0";
48825
+ const parsed = JSON.parse(marketplace_default);
48826
+ return parsed.plugins?.[0]?.version || "1.0.0";
48546
48827
  } catch {
48547
48828
  return "1.0.0";
48548
48829
  }
48549
48830
  }
48550
- function registerPlugin() {
48551
- const version3 = getPluginVersion();
48552
- const now = new Date().toISOString();
48553
- let data = { version: 2, plugins: {} };
48554
- if (existsSync3(INSTALLED_PLUGINS_PATH)) {
48555
- try {
48556
- data = JSON.parse(readFileSync4(INSTALLED_PLUGINS_PATH, "utf-8"));
48557
- } catch {}
48558
- }
48559
- data.plugins = data.plugins || {};
48560
- data.plugins[PLUGIN_ID] = [
48561
- {
48562
- scope: "user",
48563
- installPath: PLUGIN_DIR,
48564
- version: version3,
48565
- installedAt: now,
48566
- lastUpdated: now
48567
- }
48568
- ];
48569
- const dir = INSTALLED_PLUGINS_PATH.substring(0, INSTALLED_PLUGINS_PATH.lastIndexOf("/"));
48570
- mkdirSync3(dir, { recursive: true });
48571
- writeFileSync3(INSTALLED_PLUGINS_PATH, JSON.stringify(data, null, 2), "utf-8");
48572
- }
48573
- function enablePlugin() {
48574
- let data = {};
48575
- if (existsSync3(CLAUDE_SETTINGS_PATH)) {
48576
- try {
48577
- data = JSON.parse(readFileSync4(CLAUDE_SETTINGS_PATH, "utf-8"));
48578
- } catch {}
48831
+ function getInstalledVersion() {
48832
+ const installedMarketplace = join3(MARKETPLACE_DIR, ".claude-plugin", "marketplace.json");
48833
+ if (!existsSync3(installedMarketplace)) {
48834
+ return null;
48579
48835
  }
48580
- const enabledPlugins = data.enabledPlugins || {};
48581
- enabledPlugins[PLUGIN_ID] = true;
48582
- data.enabledPlugins = enabledPlugins;
48583
- const dir = CLAUDE_SETTINGS_PATH.substring(0, CLAUDE_SETTINGS_PATH.lastIndexOf("/"));
48584
- mkdirSync3(dir, { recursive: true });
48585
- writeFileSync3(CLAUDE_SETTINGS_PATH, JSON.stringify(data, null, 2), "utf-8");
48586
- }
48587
- function unregisterPlugin() {
48588
- if (!existsSync3(INSTALLED_PLUGINS_PATH))
48589
- return;
48590
48836
  try {
48591
- const data = JSON.parse(readFileSync4(INSTALLED_PLUGINS_PATH, "utf-8"));
48592
- if (data.plugins && data.plugins[PLUGIN_ID]) {
48593
- delete data.plugins[PLUGIN_ID];
48594
- writeFileSync3(INSTALLED_PLUGINS_PATH, JSON.stringify(data, null, 2), "utf-8");
48595
- }
48596
- } catch {}
48597
- }
48598
- function disablePlugin() {
48599
- if (!existsSync3(CLAUDE_SETTINGS_PATH))
48600
- return;
48601
- try {
48602
- const data = JSON.parse(readFileSync4(CLAUDE_SETTINGS_PATH, "utf-8"));
48603
- if (data.enabledPlugins && data.enabledPlugins[PLUGIN_ID] !== undefined) {
48604
- delete data.enabledPlugins[PLUGIN_ID];
48605
- writeFileSync3(CLAUDE_SETTINGS_PATH, JSON.stringify(data, null, 2), "utf-8");
48606
- }
48607
- } catch {}
48837
+ const installed = JSON.parse(readFileSync4(installedMarketplace, "utf-8"));
48838
+ return installed.plugins?.[0]?.version || null;
48839
+ } catch {
48840
+ return null;
48841
+ }
48608
48842
  }
48609
48843
  async function handleClaudeCommand(action) {
48610
48844
  if (action === "install") {
@@ -48618,38 +48852,39 @@ async function handleClaudeCommand(action) {
48618
48852
  throw new CliError("INVALID_ACTION", "Unknown action. Usage: fulcrum claude install | fulcrum claude uninstall", ExitCodes.INVALID_ARGS);
48619
48853
  }
48620
48854
  function needsPluginUpdate() {
48621
- const installedPluginJson = join3(PLUGIN_DIR, ".claude-plugin", "plugin.json");
48622
- if (!existsSync3(installedPluginJson)) {
48623
- return true;
48624
- }
48625
- try {
48626
- const installed = JSON.parse(readFileSync4(installedPluginJson, "utf-8"));
48627
- const bundled = JSON.parse(plugin_default);
48628
- return installed.version !== bundled.version;
48629
- } catch {
48855
+ const installedVersion = getInstalledVersion();
48856
+ if (!installedVersion) {
48630
48857
  return true;
48631
48858
  }
48859
+ const bundledVersion = getBundledVersion();
48860
+ return installedVersion !== bundledVersion;
48632
48861
  }
48633
48862
  async function installClaudePlugin(options = {}) {
48634
48863
  const { silent = false } = options;
48635
48864
  const log = silent ? () => {} : console.log;
48636
48865
  try {
48637
48866
  log("Installing Claude Code plugin...");
48638
- if (existsSync3(PLUGIN_DIR)) {
48639
- log("\u2022 Removing existing plugin installation...");
48640
- rmSync(PLUGIN_DIR, { recursive: true });
48867
+ if (existsSync3(MARKETPLACE_DIR)) {
48868
+ rmSync(MARKETPLACE_DIR, { recursive: true });
48641
48869
  }
48642
48870
  for (const file2 of PLUGIN_FILES) {
48643
- const fullPath = join3(PLUGIN_DIR, file2.path);
48644
- const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
48645
- mkdirSync3(dir, { recursive: true });
48871
+ const fullPath = join3(MARKETPLACE_DIR, file2.path);
48872
+ mkdirSync3(dirname2(fullPath), { recursive: true });
48646
48873
  writeFileSync3(fullPath, file2.content, "utf-8");
48647
48874
  }
48648
- log("\u2713 Installed plugin files at " + PLUGIN_DIR);
48649
- registerPlugin();
48650
- log("\u2713 Registered plugin in installed_plugins.json");
48651
- enablePlugin();
48652
- log("\u2713 Enabled plugin in settings.json");
48875
+ log("\u2713 Created plugin files at " + MARKETPLACE_DIR);
48876
+ runClaude(["plugin", "marketplace", "remove", MARKETPLACE_NAME]);
48877
+ const addResult = runClaude(["plugin", "marketplace", "add", MARKETPLACE_DIR]);
48878
+ if (!addResult.success) {
48879
+ throw new Error("Failed to add marketplace: " + addResult.output);
48880
+ }
48881
+ log("\u2713 Registered marketplace");
48882
+ const installResult = runClaude(["plugin", "install", PLUGIN_ID, "--scope", "user"]);
48883
+ if (!installResult.success) {
48884
+ throw new Error("Failed to install plugin: " + installResult.output);
48885
+ }
48886
+ log("\u2713 Installed plugin");
48887
+ cleanupLegacyPaths(log);
48653
48888
  log("");
48654
48889
  log("Installation complete! Restart Claude Code to apply changes.");
48655
48890
  } catch (err) {
@@ -48658,32 +48893,35 @@ async function installClaudePlugin(options = {}) {
48658
48893
  }
48659
48894
  async function uninstallClaudePlugin() {
48660
48895
  try {
48661
- let didSomething = false;
48662
- if (existsSync3(PLUGIN_DIR)) {
48663
- rmSync(PLUGIN_DIR, { recursive: true });
48664
- console.log("\u2713 Removed plugin files from " + PLUGIN_DIR);
48665
- didSomething = true;
48666
- }
48667
- unregisterPlugin();
48668
- console.log("\u2713 Unregistered plugin from installed_plugins.json");
48669
- disablePlugin();
48670
- console.log("\u2713 Disabled plugin in settings.json");
48671
- if (didSomething) {
48672
- console.log("");
48673
- console.log("Uninstall complete! Restart Claude Code to apply changes.");
48674
- } else {
48675
- console.log("");
48676
- console.log("Plugin files were not found, but registration entries have been cleaned up.");
48677
- }
48896
+ runClaude(["plugin", "uninstall", PLUGIN_ID]);
48897
+ console.log("\u2713 Uninstalled plugin");
48898
+ runClaude(["plugin", "marketplace", "remove", MARKETPLACE_NAME]);
48899
+ console.log("\u2713 Removed marketplace");
48900
+ if (existsSync3(MARKETPLACE_DIR)) {
48901
+ rmSync(MARKETPLACE_DIR, { recursive: true });
48902
+ console.log("\u2713 Removed plugin files from " + MARKETPLACE_DIR);
48903
+ }
48904
+ cleanupLegacyPaths(console.log);
48905
+ console.log("");
48906
+ console.log("Uninstall complete! Restart Claude Code to apply changes.");
48678
48907
  } catch (err) {
48679
48908
  throw new CliError("UNINSTALL_FAILED", `Failed to uninstall Claude plugin: ${err instanceof Error ? err.message : String(err)}`, ExitCodes.ERROR);
48680
48909
  }
48681
48910
  }
48911
+ function cleanupLegacyPaths(log) {
48912
+ const legacyPaths = [LEGACY_PLUGIN_DIR, LEGACY_CACHE_DIR];
48913
+ for (const path of legacyPaths) {
48914
+ if (existsSync3(path)) {
48915
+ rmSync(path, { recursive: true });
48916
+ log("\u2713 Removed legacy files from " + path);
48917
+ }
48918
+ }
48919
+ }
48682
48920
  // package.json
48683
48921
  var package_default = {
48684
48922
  name: "@knowsuchagency/fulcrum",
48685
48923
  private: true,
48686
- version: "1.4.0",
48924
+ version: "1.5.0",
48687
48925
  description: "Harness Attention. Orchestrate Agents. Ship.",
48688
48926
  license: "PolyForm-Perimeter-1.0.0",
48689
48927
  type: "module",
@@ -48776,14 +49014,14 @@ var package_default = {
48776
49014
  // cli/src/commands/up.ts
48777
49015
  function getPackageRoot() {
48778
49016
  const currentFile = fileURLToPath(import.meta.url);
48779
- let dir = dirname2(currentFile);
49017
+ let dir = dirname3(currentFile);
48780
49018
  for (let i2 = 0;i2 < 5; i2++) {
48781
49019
  if (existsSync4(join4(dir, "server", "index.js"))) {
48782
49020
  return dir;
48783
49021
  }
48784
- dir = dirname2(dir);
49022
+ dir = dirname3(dir);
48785
49023
  }
48786
- return dirname2(dirname2(dirname2(currentFile)));
49024
+ return dirname3(dirname3(dirname3(currentFile)));
48787
49025
  }
48788
49026
  async function handleUpCommand(flags) {
48789
49027
  const autoYes = flags.yes === "true" || flags.y === "true";
@@ -49572,8 +49810,8 @@ export const FulcrumPlugin: Plugin = async ({ $, directory }) => {
49572
49810
  // cli/src/commands/opencode.ts
49573
49811
  var OPENCODE_DIR = join5(homedir3(), ".opencode");
49574
49812
  var OPENCODE_CONFIG_PATH = join5(OPENCODE_DIR, "opencode.json");
49575
- var PLUGIN_DIR2 = join5(homedir3(), ".config", "opencode", "plugin");
49576
- var PLUGIN_PATH = join5(PLUGIN_DIR2, "fulcrum.ts");
49813
+ var PLUGIN_DIR = join5(homedir3(), ".config", "opencode", "plugin");
49814
+ var PLUGIN_PATH = join5(PLUGIN_DIR, "fulcrum.ts");
49577
49815
  var FULCRUM_MCP_CONFIG = {
49578
49816
  type: "local",
49579
49817
  command: ["fulcrum", "mcp"],
@@ -49593,7 +49831,7 @@ async function handleOpenCodeCommand(action) {
49593
49831
  async function installOpenCodeIntegration() {
49594
49832
  try {
49595
49833
  console.log("Installing OpenCode plugin...");
49596
- mkdirSync4(PLUGIN_DIR2, { recursive: true });
49834
+ mkdirSync4(PLUGIN_DIR, { recursive: true });
49597
49835
  writeFileSync4(PLUGIN_PATH, fulcrum_opencode_default, "utf-8");
49598
49836
  console.log("\u2713 Installed plugin at " + PLUGIN_PATH);
49599
49837
  console.log("Configuring MCP server...");
@@ -50351,6 +50589,127 @@ var tasksListDependenciesCommand = defineCommand({
50351
50589
  await handleTasksCommand("list-dependencies", [args.id], toFlags(args));
50352
50590
  }
50353
50591
  });
50592
+ var tasksLabelsCommand = defineCommand({
50593
+ meta: {
50594
+ name: "labels",
50595
+ description: "List all labels in use across tasks"
50596
+ },
50597
+ args: {
50598
+ ...globalArgs,
50599
+ search: {
50600
+ type: "string",
50601
+ description: "Filter labels by substring match"
50602
+ }
50603
+ },
50604
+ async run({ args }) {
50605
+ if (args.json)
50606
+ setJsonOutput(true);
50607
+ await handleTasksCommand("labels", [], toFlags(args));
50608
+ }
50609
+ });
50610
+ var tasksAttachmentsListCommand = defineCommand({
50611
+ meta: {
50612
+ name: "list",
50613
+ description: "List attachments for a task"
50614
+ },
50615
+ args: {
50616
+ ...globalArgs,
50617
+ id: {
50618
+ type: "positional",
50619
+ description: "Task ID",
50620
+ required: true
50621
+ }
50622
+ },
50623
+ async run({ args }) {
50624
+ if (args.json)
50625
+ setJsonOutput(true);
50626
+ await handleTasksCommand("attachments", ["list", args.id], toFlags(args));
50627
+ }
50628
+ });
50629
+ var tasksAttachmentsUploadCommand = defineCommand({
50630
+ meta: {
50631
+ name: "upload",
50632
+ description: "Upload a file to a task"
50633
+ },
50634
+ args: {
50635
+ ...globalArgs,
50636
+ id: {
50637
+ type: "positional",
50638
+ description: "Task ID",
50639
+ required: true
50640
+ },
50641
+ file: {
50642
+ type: "positional",
50643
+ description: "File path to upload",
50644
+ required: true
50645
+ }
50646
+ },
50647
+ async run({ args }) {
50648
+ if (args.json)
50649
+ setJsonOutput(true);
50650
+ await handleTasksCommand("attachments", ["upload", args.id, args.file], toFlags(args));
50651
+ }
50652
+ });
50653
+ var tasksAttachmentsDeleteCommand = defineCommand({
50654
+ meta: {
50655
+ name: "delete",
50656
+ description: "Delete an attachment from a task"
50657
+ },
50658
+ args: {
50659
+ ...globalArgs,
50660
+ id: {
50661
+ type: "positional",
50662
+ description: "Task ID",
50663
+ required: true
50664
+ },
50665
+ "attachment-id": {
50666
+ type: "positional",
50667
+ description: "Attachment ID",
50668
+ required: true
50669
+ }
50670
+ },
50671
+ async run({ args }) {
50672
+ if (args.json)
50673
+ setJsonOutput(true);
50674
+ await handleTasksCommand("attachments", ["delete", args.id, args["attachment-id"]], toFlags(args));
50675
+ }
50676
+ });
50677
+ var tasksAttachmentsPathCommand = defineCommand({
50678
+ meta: {
50679
+ name: "path",
50680
+ description: "Get local file path for an attachment"
50681
+ },
50682
+ args: {
50683
+ ...globalArgs,
50684
+ id: {
50685
+ type: "positional",
50686
+ description: "Task ID",
50687
+ required: true
50688
+ },
50689
+ "attachment-id": {
50690
+ type: "positional",
50691
+ description: "Attachment ID",
50692
+ required: true
50693
+ }
50694
+ },
50695
+ async run({ args }) {
50696
+ if (args.json)
50697
+ setJsonOutput(true);
50698
+ await handleTasksCommand("attachments", ["path", args.id, args["attachment-id"]], toFlags(args));
50699
+ }
50700
+ });
50701
+ var tasksAttachmentsCommand = defineCommand({
50702
+ meta: {
50703
+ name: "attachments",
50704
+ description: "Manage task attachments"
50705
+ },
50706
+ subCommands: {
50707
+ list: tasksAttachmentsListCommand,
50708
+ upload: tasksAttachmentsUploadCommand,
50709
+ delete: tasksAttachmentsDeleteCommand,
50710
+ path: tasksAttachmentsPathCommand
50711
+ }
50712
+ });
50354
50713
  var tasksCommand = defineCommand({
50355
50714
  meta: {
50356
50715
  name: "tasks",
@@ -50368,7 +50727,9 @@ var tasksCommand = defineCommand({
50368
50727
  "set-due-date": tasksSetDueDateCommand,
50369
50728
  "add-dependency": tasksAddDependencyCommand,
50370
50729
  "remove-dependency": tasksRemoveDependencyCommand,
50371
- "list-dependencies": tasksListDependenciesCommand
50730
+ "list-dependencies": tasksListDependenciesCommand,
50731
+ labels: tasksLabelsCommand,
50732
+ attachments: tasksAttachmentsCommand
50372
50733
  }
50373
50734
  });
50374
50735
  var projectsListCommand = defineCommand({
@@ -122,7 +122,7 @@ ${n.map(([a,s])=>{const o=s.theme?.[r]||s.color;return o?` --color-${a}: ${o};`
122
122
 
123
123
  ${e}`,d=Cp(u);return`opencode --agent ${bgt(o)} --prompt ${d}${l}`},notFoundPatterns:[/opencode: command not found/,/opencode: not found/,/'opencode' is not recognized/,/command not found: opencode/],processPattern:/\bopencode\b/i},d6={claude:ygt,opencode:vgt};function xgt(e,t){return d6[e].buildCommand(t)}function _gt(e,t){if(t)return d6[t].notFoundPatterns.some(r=>r.test(e))?t:null;for(const[n,r]of Object.entries(d6))if(r.notFoundPatterns.some(i=>i.test(e)))return n;return null}function Kae({taskName:e,cwd:t,taskId:n,className:r,agent:i="claude",aiMode:a,description:s,startupScript:o,agentOptions:l,opencodeModel:u,serverPort:d=7777,autoFocus:c=!1}){const p=w.useRef(null),m=w.useRef(null),x=w.useRef(!1),_=w.useRef(c),b=w.useRef(null),v=w.useRef(!1),y=w.useRef(!1),[E,T]=w.useState(null),[k,C]=w.useState(!1),[O,A]=w.useState(!1),[N,I]=w.useState(!1),[D,P]=w.useState(null),{resolvedTheme:L}=Wf(),j=L==="dark",F=j?dme:cme,{data:$}=gfe(),{data:V}=bfe(),Y=w.useRef($),B=w.useRef(V);w.useEffect(()=>{Y.current=$},[$]),w.useEffect(()=>{B.current=V},[V]),w.useEffect(()=>{Mt.taskTerminal.debug("cwd changed, resetting refs",{cwd:t}),v.current=!1,y.current=!1,x.current=!1,T(null),C(!1)},[t]);const{setTerminalFocused:z}=U8(),{terminals:H,terminalsLoaded:q,connected:U,createTerminal:X,attachXterm:J,resizeTerminal:ne,setupImagePaste:Z,writeToTerminal:K,consumePendingStartup:ee,clearStartingUp:ae}=wB(),se=w.useRef(J),ie=w.useRef(Z),me=w.useRef(K),ye=w.useRef(ee),ue=w.useRef(ae);w.useEffect(()=>{se.current=J},[J]),w.useEffect(()=>{ie.current=Z},[Z]),w.useEffect(()=>{me.current=K},[K]),w.useEffect(()=>{ye.current=ee},[ee]),w.useEffect(()=>{ue.current=ae},[ae]),w.useEffect(()=>{_.current=c},[c]);const Ee=E?H.find(Le=>Le.id===E):null,be=Ee?.status;w.useEffect(()=>{if(!p.current||m.current)return;const Le=new sme.Terminal({cursorBlink:!0,fontSize:14,fontFamily:"monospace",theme:F,scrollback:1e4,rightClickSelectsWord:!0,scrollOnUserInput:!1}),Ce=new ome.FitAddon,Pe=new lme.WebLinksAddon;Le.loadAddon(Ce),Le.loadAddon(Pe),Le.open(p.current);const xe=ume(Le);m.current=Le,b.current=Ce,I(!0),requestAnimationFrame(()=>{Ce.fit()});const _e=setTimeout(()=>{Ce.fit(),Le.refresh(0,Le.rows-1)},100),Ae=()=>z(!0),pe=()=>z(!1);return Le.textarea&&(Le.textarea.addEventListener("focus",Ae),Le.textarea.addEventListener("blur",pe)),()=>{clearTimeout(_e),xe(),Le.textarea&&(Le.textarea.removeEventListener("focus",Ae),Le.textarea.removeEventListener("blur",pe)),z(!1),Le.dispose(),m.current=null,b.current=null,I(!1)}},[z]);const Oe=w.useCallback(()=>{if(!b.current||!m.current)return;b.current.fit();const{cols:Le,rows:Ce}=m.current;E&&ne(E,Le,Ce)},[E,ne]);w.useEffect(()=>{if(!p.current)return;const Le=()=>{requestAnimationFrame(Oe)};window.addEventListener("resize",Le);const Ce=()=>{document.visibilityState==="visible"&&requestAnimationFrame(()=>{Oe(),m.current&&m.current.refresh(0,m.current.rows-1)})};document.addEventListener("visibilitychange",Ce);const Pe=new ResizeObserver(Le);Pe.observe(p.current);const xe=new IntersectionObserver(_e=>{_e[0]?.isIntersecting&&requestAnimationFrame(()=>{Oe(),m.current&&m.current.refresh(0,m.current.rows-1)})},{threshold:.1});return xe.observe(p.current),()=>{window.removeEventListener("resize",Le),document.removeEventListener("visibilitychange",Ce),Pe.disconnect(),xe.disconnect()}},[Oe]),w.useEffect(()=>{if(!U||!t||!N||!q){Mt.taskTerminal.debug("Terminal effect: waiting for conditions",{connected:U,cwd:t,xtermOpened:N,terminalsLoaded:q});return}Mt.taskTerminal.info("Looking for terminal",{cwd:t,terminalCount:H.length,availableTerminals:H.map(Ce=>({id:Ce.id,name:Ce.name,cwd:Ce.cwd,tabId:Ce.tabId}))});const Le=H.find(Ce=>Ce.cwd===t);if(Le){Mt.taskTerminal.info("Found existing terminal",{terminalId:Le.id,name:Le.name,cwd:t}),T(Le.id);return}if(!v.current&&m.current){Mt.taskTerminal.info("Creating new terminal",{reason:"no_existing_terminal_for_cwd",cwd:t,taskName:e,taskId:n,agent:i}),v.current=!0,C(!0);const{cols:Ce,rows:Pe}=m.current;X({name:e,cols:Ce,rows:Pe,cwd:t,taskId:n,startup:{startupScript:o,agent:i,agentOptions:l,opencodeModel:u,aiMode:a,description:s,taskName:e,serverPort:d}})}else v.current&&Mt.taskTerminal.debug("Terminal creation already in progress",{cwd:t,createdTerminalRef:v.current})},[U,t,N,q,H,e,X]),w.useEffect(()=>{if(!t)return;const Le=H.find(Pe=>Pe.cwd===t);if(!Le)return;const Ce=E&&H.some(Pe=>Pe.id===E);(!E||!Ce)&&(Mt.taskTerminal.debug("setting terminalId",{newId:Le.id,prevId:E,reason:E?"tempId replaced":"initial",cwd:t,terminalCount:H.length}),T(Le.id),C(!1),E&&!Ce&&(y.current=!1))},[H,t,E]),w.useEffect(()=>{if(Mt.taskTerminal.debug("attach effect",{terminalId:E,hasTermRef:!!m.current,hasContainerRef:!!p.current,attachedRef:y.current}),!E||!m.current||!p.current||y.current)return;Mt.taskTerminal.debug("attach effect passed guards, calling attachXterm",{terminalId:E});const Le=xe=>{requestAnimationFrame(Oe);const _e=ye.current(xe);if(Mt.taskTerminal.debug("onAttached checking pending startup",{terminalId:xe,hasPendingStartup:!!_e}),!_e){if(_.current&&!x.current&&m.current){const fe=()=>{const ge=m.current;!x.current&&ge&&(ge.focus(),ge.textarea===document.activeElement&&(x.current=!0))};setTimeout(fe,50),setTimeout(fe,200),setTimeout(fe,500)}return}Mt.taskTerminal.info("onAttached: running startup commands",{terminalId:xe}),A(!0);const{startupScript:Ae,agent:pe="claude",agentOptions:Me,opencodeModel:we,aiMode:Ve,description:Xe,taskName:ht,serverPort:ot}=_e;Ae&&setTimeout(()=>{const fe="FULCRUM_STARTUP_"+Date.now(),ge=`source /dev/stdin <<'${fe}'
124
124
  ${Ae}
125
- ${fe}`;me.current(xe,ge+"\r")},100);const lt=ot??7777,ze=lt!==7777?` --port=${lt}`:"",yt=`You are working in a Fulcrum task worktree. Commit after completing each logical unit of work (feature, fix, refactor) to preserve progress. When you finish working and need user input, run: fulcrum current-task review${ze}. When linking a PR: fulcrum current-task pr <url>${ze}. When linking a URL: fulcrum current-task link <url>${ze}. For notifications: fulcrum notify "Title" "Message"${ze}.`,Ie=Xe?`${ht}: ${Xe}`:ht,Ue=xgt(pe,{prompt:Ie,systemPrompt:yt,mode:Ve==="plan"?"plan":"default",additionalOptions:Me??{},opencodeModel:we,opencodeDefaultAgent:Y.current,opencodePlanAgent:B.current});setTimeout(()=>{if(me.current(xe,Ue+"\r"),A(!1),ue.current(xe),_.current&&!x.current&&m.current){const fe=()=>{const ge=m.current;!x.current&&ge&&(ge.focus(),ge.textarea===document.activeElement&&(x.current=!0))};setTimeout(fe,50),setTimeout(fe,200),setTimeout(fe,500)}},Ae?5e3:100)},Ce=se.current(E,m.current,{onAttached:Le}),Pe=ie.current(p.current,E);return y.current=!0,Mt.taskTerminal.debug("attachedRef set to true",{terminalId:E}),()=>{Mt.taskTerminal.debug("cleanup running, setting attachedRef to false",{terminalId:E}),Ce(),Pe(),y.current=!1}},[E,Oe]),w.useEffect(()=>{m.current&&(m.current.options.theme=F,m.current.refresh(0,m.current.rows-1))},[F]),w.useEffect(()=>{if(!c||x.current||!m.current||!E||k||O)return;const Le=[0,50,150,300,500],Ce=[];return Le.forEach(Pe=>{const xe=setTimeout(()=>{const _e=m.current;!x.current&&_e&&(_e.focus(),_e.textarea===document.activeElement&&(x.current=!0))},Pe);Ce.push(xe)}),()=>{Ce.forEach(clearTimeout)}},[c,k,O,E]),w.useEffect(()=>{if(!m.current||D)return;const Le=m.current,Ce=()=>{const xe=Le.buffer.active;for(let _e=Math.max(0,xe.cursorY-3);_e<=xe.cursorY;_e++){const Ae=xe.getLine(_e);if(Ae){const pe=Ae.translateToString(),Me=_gt(pe,i);if(Me){P(Me);return}}}},Pe=Le.onLineFeed(Ce);return()=>{Pe.dispose()}},[D,i]);const je=w.useCallback(Le=>{E&&me.current(E,Le)},[E]);return t?f.jsxs("div",{className:"flex h-full min-h-0 flex-col",children:[!U&&f.jsx("div",{className:"shrink-0 px-2 py-1 bg-muted-foreground/20 text-muted-foreground text-xs",children:"Connecting to terminal server..."}),be==="error"&&f.jsx("div",{className:"shrink-0 px-2 py-1 bg-destructive/20 text-destructive text-xs",children:"Terminal failed to start. The worktree directory may not exist."}),be==="exited"&&f.jsxs("div",{className:"shrink-0 px-2 py-1 bg-muted text-muted-foreground text-xs",children:["Terminal exited (code: ",Ee?.exitCode,")"]}),f.jsxs("div",{className:"relative min-h-0 min-w-0 flex-1",children:[f.jsx("div",{ref:p,className:Ne("h-full w-full overflow-hidden p-2 bg-terminal-background",r)}),k&&!E&&f.jsx("div",{className:"absolute inset-0 flex items-center justify-center bg-terminal-background",children:f.jsxs("div",{className:"flex flex-col items-center gap-3",children:[f.jsx(re,{icon:Ot,size:24,strokeWidth:2,className:Ne("animate-spin",j?"text-white/50":"text-black/50")}),f.jsx("span",{className:Ne("font-mono text-sm",j?"text-white/50":"text-black/50"),children:"Initializing terminal..."})]})}),O&&f.jsx("div",{className:"pointer-events-none absolute inset-0 z-10 flex items-center justify-center bg-terminal-background/90",children:f.jsxs("div",{className:"flex flex-col items-center gap-3",children:[f.jsx(re,{icon:Ot,size:24,strokeWidth:2,className:Ne("animate-spin",j?"text-white/60":"text-black/60")}),f.jsxs("span",{className:Ne("font-mono text-sm",j?"text-white/60":"text-black/60"),children:["Starting ",bo[i],"..."]})]})}),D&&f.jsx("div",{className:"pointer-events-none absolute bottom-4 left-4 right-4 z-10",children:f.jsxs("div",{className:Ne("pointer-events-auto flex items-start gap-3 rounded-lg border p-4","bg-amber-500/10 border-amber-500/30"),children:[f.jsx(re,{icon:fr,size:18,strokeWidth:2,className:"shrink-0 mt-0.5 text-amber-600 dark:text-amber-400"}),f.jsxs("div",{className:"flex-1 space-y-2",children:[f.jsxs("p",{className:"text-sm font-medium text-amber-600 dark:text-amber-400",children:[bo[D]," CLI not found"]}),f.jsxs("p",{className:"text-xs text-amber-600/80 dark:text-amber-400/80",children:["Install it with:"," ",f.jsx("code",{className:"rounded bg-amber-500/20 px-1.5 py-0.5 font-mono",children:Efe[D]})]}),f.jsx("a",{href:Sfe[D],target:"_blank",rel:"noopener noreferrer",className:"inline-block text-xs font-medium text-amber-600 hover:text-amber-700 dark:text-amber-400 dark:hover:text-amber-300 underline",children:"View documentation"})]}),f.jsx("button",{onClick:()=>P(null),className:"shrink-0 p-1 rounded text-amber-600 hover:text-amber-700 hover:bg-amber-500/20 dark:text-amber-400 dark:hover:text-amber-300",children:f.jsx(re,{icon:Bn,size:14,strokeWidth:2})})]})}),f.jsx("button",{onClick:()=>m.current?.scrollToBottom(),className:Ne("absolute top-2 right-5 p-1 transition-colors",j?"text-white/50 hover:text-white/80":"text-black/50 hover:text-black/80"),children:f.jsx(re,{icon:Tce,size:20,strokeWidth:2})})]}),f.jsx("div",{className:"h-2 shrink-0 bg-terminal-background"}),f.jsx(HC,{onSend:je})]}):f.jsx("div",{className:Ne("flex h-full items-center justify-center text-muted-foreground text-sm bg-terminal-background",r),children:"No worktree path configured for this task"})}function Egt(e){const{viewState:t,setDiffOptions:n}=SB(e),{collapsedFiles:r}=t.diffOptions,i=w.useCallback(l=>{const u=r.includes(l);n({collapsedFiles:u?r.filter(d=>d!==l):[...r,l]})},[r,n]),a=w.useCallback(l=>{n({collapsedFiles:l})},[n]),s=w.useCallback(()=>{n({collapsedFiles:[]})},[n]),o=w.useCallback(l=>r.includes(l),[r]);return{options:t.diffOptions,setOption:(l,u)=>{n({[l]:u})},setOptions:n,toggleFileCollapse:i,collapseAll:a,expandAll:s,isFileCollapsed:o}}function Sgt(e){const t=[];let n=null,r=0,i=0;for(const a of e.split(`
125
+ ${fe}`;me.current(xe,ge+"\r")},100);const lt=ot??7777,ze=lt!==7777?` --port=${lt}`:"",yt=`You are working in a Fulcrum task worktree. Reference the fulcrum skill for complete CLI documentation (attachments, dependencies, notifications, etc.). Commit after completing each logical unit of work (feature, fix, refactor) to preserve progress. When you finish working and need user input, run: fulcrum current-task review${ze}. When linking a PR: fulcrum current-task pr <url>${ze}. When linking a URL: fulcrum current-task link <url>${ze}. For notifications: fulcrum notify "Title" "Message"${ze}.`,Ie=Xe?`${ht}: ${Xe}`:ht,Ue=xgt(pe,{prompt:Ie,systemPrompt:yt,mode:Ve==="plan"?"plan":"default",additionalOptions:Me??{},opencodeModel:we,opencodeDefaultAgent:Y.current,opencodePlanAgent:B.current});setTimeout(()=>{if(me.current(xe,Ue+"\r"),A(!1),ue.current(xe),_.current&&!x.current&&m.current){const fe=()=>{const ge=m.current;!x.current&&ge&&(ge.focus(),ge.textarea===document.activeElement&&(x.current=!0))};setTimeout(fe,50),setTimeout(fe,200),setTimeout(fe,500)}},Ae?5e3:100)},Ce=se.current(E,m.current,{onAttached:Le}),Pe=ie.current(p.current,E);return y.current=!0,Mt.taskTerminal.debug("attachedRef set to true",{terminalId:E}),()=>{Mt.taskTerminal.debug("cleanup running, setting attachedRef to false",{terminalId:E}),Ce(),Pe(),y.current=!1}},[E,Oe]),w.useEffect(()=>{m.current&&(m.current.options.theme=F,m.current.refresh(0,m.current.rows-1))},[F]),w.useEffect(()=>{if(!c||x.current||!m.current||!E||k||O)return;const Le=[0,50,150,300,500],Ce=[];return Le.forEach(Pe=>{const xe=setTimeout(()=>{const _e=m.current;!x.current&&_e&&(_e.focus(),_e.textarea===document.activeElement&&(x.current=!0))},Pe);Ce.push(xe)}),()=>{Ce.forEach(clearTimeout)}},[c,k,O,E]),w.useEffect(()=>{if(!m.current||D)return;const Le=m.current,Ce=()=>{const xe=Le.buffer.active;for(let _e=Math.max(0,xe.cursorY-3);_e<=xe.cursorY;_e++){const Ae=xe.getLine(_e);if(Ae){const pe=Ae.translateToString(),Me=_gt(pe,i);if(Me){P(Me);return}}}},Pe=Le.onLineFeed(Ce);return()=>{Pe.dispose()}},[D,i]);const je=w.useCallback(Le=>{E&&me.current(E,Le)},[E]);return t?f.jsxs("div",{className:"flex h-full min-h-0 flex-col",children:[!U&&f.jsx("div",{className:"shrink-0 px-2 py-1 bg-muted-foreground/20 text-muted-foreground text-xs",children:"Connecting to terminal server..."}),be==="error"&&f.jsx("div",{className:"shrink-0 px-2 py-1 bg-destructive/20 text-destructive text-xs",children:"Terminal failed to start. The worktree directory may not exist."}),be==="exited"&&f.jsxs("div",{className:"shrink-0 px-2 py-1 bg-muted text-muted-foreground text-xs",children:["Terminal exited (code: ",Ee?.exitCode,")"]}),f.jsxs("div",{className:"relative min-h-0 min-w-0 flex-1",children:[f.jsx("div",{ref:p,className:Ne("h-full w-full overflow-hidden p-2 bg-terminal-background",r)}),k&&!E&&f.jsx("div",{className:"absolute inset-0 flex items-center justify-center bg-terminal-background",children:f.jsxs("div",{className:"flex flex-col items-center gap-3",children:[f.jsx(re,{icon:Ot,size:24,strokeWidth:2,className:Ne("animate-spin",j?"text-white/50":"text-black/50")}),f.jsx("span",{className:Ne("font-mono text-sm",j?"text-white/50":"text-black/50"),children:"Initializing terminal..."})]})}),O&&f.jsx("div",{className:"pointer-events-none absolute inset-0 z-10 flex items-center justify-center bg-terminal-background/90",children:f.jsxs("div",{className:"flex flex-col items-center gap-3",children:[f.jsx(re,{icon:Ot,size:24,strokeWidth:2,className:Ne("animate-spin",j?"text-white/60":"text-black/60")}),f.jsxs("span",{className:Ne("font-mono text-sm",j?"text-white/60":"text-black/60"),children:["Starting ",bo[i],"..."]})]})}),D&&f.jsx("div",{className:"pointer-events-none absolute bottom-4 left-4 right-4 z-10",children:f.jsxs("div",{className:Ne("pointer-events-auto flex items-start gap-3 rounded-lg border p-4","bg-amber-500/10 border-amber-500/30"),children:[f.jsx(re,{icon:fr,size:18,strokeWidth:2,className:"shrink-0 mt-0.5 text-amber-600 dark:text-amber-400"}),f.jsxs("div",{className:"flex-1 space-y-2",children:[f.jsxs("p",{className:"text-sm font-medium text-amber-600 dark:text-amber-400",children:[bo[D]," CLI not found"]}),f.jsxs("p",{className:"text-xs text-amber-600/80 dark:text-amber-400/80",children:["Install it with:"," ",f.jsx("code",{className:"rounded bg-amber-500/20 px-1.5 py-0.5 font-mono",children:Efe[D]})]}),f.jsx("a",{href:Sfe[D],target:"_blank",rel:"noopener noreferrer",className:"inline-block text-xs font-medium text-amber-600 hover:text-amber-700 dark:text-amber-400 dark:hover:text-amber-300 underline",children:"View documentation"})]}),f.jsx("button",{onClick:()=>P(null),className:"shrink-0 p-1 rounded text-amber-600 hover:text-amber-700 hover:bg-amber-500/20 dark:text-amber-400 dark:hover:text-amber-300",children:f.jsx(re,{icon:Bn,size:14,strokeWidth:2})})]})}),f.jsx("button",{onClick:()=>m.current?.scrollToBottom(),className:Ne("absolute top-2 right-5 p-1 transition-colors",j?"text-white/50 hover:text-white/80":"text-black/50 hover:text-black/80"),children:f.jsx(re,{icon:Tce,size:20,strokeWidth:2})})]}),f.jsx("div",{className:"h-2 shrink-0 bg-terminal-background"}),f.jsx(HC,{onSend:je})]}):f.jsx("div",{className:Ne("flex h-full items-center justify-center text-muted-foreground text-sm bg-terminal-background",r),children:"No worktree path configured for this task"})}function Egt(e){const{viewState:t,setDiffOptions:n}=SB(e),{collapsedFiles:r}=t.diffOptions,i=w.useCallback(l=>{const u=r.includes(l);n({collapsedFiles:u?r.filter(d=>d!==l):[...r,l]})},[r,n]),a=w.useCallback(l=>{n({collapsedFiles:l})},[n]),s=w.useCallback(()=>{n({collapsedFiles:[]})},[n]),o=w.useCallback(l=>r.includes(l),[r]);return{options:t.diffOptions,setOption:(l,u)=>{n({[l]:u})},setOptions:n,toggleFileCollapse:i,collapseAll:a,expandAll:s,isFileCollapsed:o}}function Sgt(e){const t=[];let n=null,r=0,i=0;for(const a of e.split(`
126
126
  `))if(a.startsWith("diff --git"))n={path:a.match(/diff --git a\/(.+?) b\//)?.[1]??"unknown",lines:[],additions:0,deletions:0},t.push(n),n.lines.push({type:"header",content:a});else if(a.startsWith("index ")||a.startsWith("---")||a.startsWith("+++"))n?.lines.push({type:"header",content:a});else if(a.startsWith("@@")){const s=a.match(/@@ -(\d+),?\d* \+(\d+),?\d* @@/);s&&(r=parseInt(s[1],10),i=parseInt(s[2],10)),n?.lines.push({type:"hunk",content:a})}else a.startsWith("+")?n&&(n.additions++,n.lines.push({type:"added",content:a.slice(1),newLineNumber:i++})):a.startsWith("-")?n&&(n.deletions++,n.lines.push({type:"removed",content:a.slice(1),oldLineNumber:r++})):a.startsWith(" ")&&n?.lines.push({type:"context",content:a.slice(1),oldLineNumber:r++,newLineNumber:i++});return t}function wgt({file:e,wrap:t,isCollapsed:n,onToggle:r}){return f.jsxs(g9,{open:!n,onOpenChange:()=>r(),children:[f.jsx(b9,{asChild:!0,children:f.jsxs("div",{className:"flex items-center gap-2 px-2 py-1.5 bg-card border-b border-border cursor-pointer hover:bg-muted select-none",children:[f.jsx(re,{icon:n?$u:gd,size:12,strokeWidth:2,className:"text-muted-foreground shrink-0"}),f.jsx("span",{className:"font-mono text-xs text-foreground truncate flex-1",children:e.path}),f.jsxs("span",{className:"text-xs text-muted-foreground shrink-0",children:[e.additions>0&&f.jsxs("span",{className:"text-accent",children:["+",e.additions]}),e.additions>0&&e.deletions>0&&" ",e.deletions>0&&f.jsxs("span",{className:"text-destructive",children:["-",e.deletions]})]})]})}),f.jsx(y9,{children:f.jsx("div",{className:"font-mono text-xs",children:e.lines.slice(1).map((i,a)=>f.jsxs("div",{className:Ne("flex px-2 py-0.5",i.type==="added"&&"bg-accent/10",i.type==="removed"&&"bg-destructive/10",i.type==="header"&&"bg-muted/50 text-muted-foreground",i.type==="hunk"&&"bg-accent/10 text-accent"),children:[(i.type==="added"||i.type==="removed"||i.type==="context")&&f.jsxs(f.Fragment,{children:[f.jsx("span",{className:"w-10 shrink-0 select-none pr-2 text-right text-muted-foreground",children:i.oldLineNumber??""}),f.jsx("span",{className:"w-10 shrink-0 select-none pr-2 text-right text-muted-foreground",children:i.newLineNumber??""})]}),f.jsxs("span",{className:Ne("w-4 shrink-0 select-none text-center",i.type==="added"&&"text-accent",i.type==="removed"&&"text-destructive"),children:[i.type==="added"&&"+",i.type==="removed"&&"-"]}),f.jsx("span",{className:Ne("flex-1",t?"whitespace-pre-wrap break-all":"whitespace-pre",i.type==="added"&&"text-accent",i.type==="removed"&&"text-destructive"),children:i.content})]},a))})})]})}function Xae({taskId:e,worktreePath:t,baseBranch:n}){const{options:r,setOption:i,toggleFileCollapse:a,collapseAll:s,expandAll:o,isFileCollapsed:l}=Egt(e),{wrap:u,ignoreWhitespace:d,includeUntracked:c,collapsedFiles:p}=r,{data:m,isLoading:x,error:_}=cMe(t,{ignoreWhitespace:d,includeUntracked:c,baseBranch:n}),b=w.useMemo(()=>m?.diff?Sgt(m.diff):[],[m?.diff]),v=w.useMemo(()=>b.map(C=>C.path),[b]),y=b.length>0&&p.length===b.length,E=w.useMemo(()=>b.reduce((C,O)=>C+O.additions,0),[b]),T=w.useMemo(()=>b.reduce((C,O)=>C+O.deletions,0),[b]);if(w.useEffect(()=>{const C=O=>{if(O.shiftKey&&O.key==="C"&&!O.ctrlKey&&!O.metaKey&&!O.altKey){const A=O.target;if(A.tagName==="INPUT"||A.tagName==="TEXTAREA"||A.isContentEditable)return;O.preventDefault(),y?o():s(v)}};return window.addEventListener("keydown",C),()=>window.removeEventListener("keydown",C)},[y,v,s,o]),!t)return f.jsx("div",{className:"flex h-full items-center justify-center text-muted-foreground text-sm",children:"No worktree selected"});if(x)return f.jsx("div",{className:"flex h-full items-center justify-center text-muted-foreground text-sm",children:"Loading diff..."});if(_)return f.jsx("div",{className:"flex h-full items-center justify-center text-destructive text-sm",children:_.message});const k=m?.files?.some(C=>C.status==="untracked")??!1;return b.length===0?f.jsxs("div",{className:"flex h-full flex-col items-center justify-center text-muted-foreground text-sm gap-2",children:[f.jsx("p",{children:"No changes detected"}),m?.files&&m.files.length>0&&f.jsxs("div",{className:"text-xs",children:[f.jsx("p",{className:"text-center mb-2",children:"Modified files:"}),f.jsxs("div",{className:"flex flex-col gap-1",children:[m.files.map(C=>f.jsxs("div",{className:"flex gap-2",children:[f.jsxs("span",{className:Ne("w-4 text-center",C.status==="added"&&"text-accent",C.status==="deleted"&&"text-destructive",C.status==="modified"&&"text-muted-foreground",C.status==="untracked"&&"text-muted-foreground"),children:[C.status==="added"&&"A",C.status==="deleted"&&"D",C.status==="modified"&&"M",C.status==="untracked"&&"?"]}),f.jsx("span",{children:C.path})]},C.path)),k&&f.jsxs("label",{className:"flex items-center gap-2 cursor-pointer text-muted-foreground hover:text-foreground mt-1",children:[f.jsx("input",{type:"checkbox",checked:c,onChange:C=>i("includeUntracked",C.target.checked),className:"w-4 h-3"}),f.jsx("span",{children:"Show untracked files"})]})]})]})]}):f.jsxs("div",{className:"flex flex-col h-full bg-background",children:[f.jsxs("div",{className:"flex items-center gap-3 px-2 py-1.5 bg-card border-b border-border text-xs",children:[m?.branch&&f.jsxs("span",{className:"text-muted-foreground",children:[m.branch,m.isBranchDiff&&m.baseBranch&&f.jsxs("span",{className:"opacity-70",children:[" (vs ",m.baseBranch,")"]})]}),(E>0||T>0)&&f.jsxs("span",{className:"text-muted-foreground",children:[f.jsxs("span",{className:"text-accent",children:["+",E]})," ",f.jsxs("span",{className:"text-destructive",children:["-",T]})]}),f.jsx("div",{className:"flex-1"}),f.jsxs("button",{onClick:()=>y?o():s(v),className:"flex items-center gap-1 px-1.5 py-0.5 text-muted-foreground hover:text-foreground rounded hover:bg-muted/50",title:y?"Expand all (Shift+C)":"Collapse all (Shift+C)",children:[f.jsx(re,{icon:y?Hce:Fce,size:12,strokeWidth:2}),f.jsx("span",{className:"hidden sm:inline",children:y?"Expand":"Collapse"})]}),f.jsxs("label",{className:"flex items-center gap-1.5 cursor-pointer text-muted-foreground hover:text-foreground",children:[f.jsx("input",{type:"checkbox",checked:u,onChange:C=>i("wrap",C.target.checked),className:"w-3 h-3"}),"Wrap"]}),f.jsxs("label",{className:"flex items-center gap-1.5 cursor-pointer text-muted-foreground hover:text-foreground",children:[f.jsx("input",{type:"checkbox",checked:d,onChange:C=>i("ignoreWhitespace",C.target.checked),className:"w-3 h-3"}),"Ignore whitespace"]}),f.jsxs("label",{className:"flex items-center gap-1.5 cursor-pointer text-muted-foreground hover:text-foreground",children:[f.jsx("input",{type:"checkbox",checked:c,onChange:C=>i("includeUntracked",C.target.checked),className:"w-3 h-3"}),"Untracked"]})]}),f.jsx(sm,{className:"flex-1 min-h-0",children:b.map(C=>f.jsx(wgt,{file:C,wrap:u,isCollapsed:l(C.path),onToggle:()=>a(C.path)},C.path))})]})}function Cgt(e){const{viewState:t,setBrowserUrl:n}=SB(e);return{url:t.browserUrl,setUrl:n}}function Zae({taskId:e}){const{url:t,setUrl:n}=Cgt(e),[r,i]=w.useState(t),[a,s]=w.useState(0);w.useEffect(()=>{i(t)},[t]);const o=w.useCallback(()=>{s(u=>u+1)},[]),l=w.useCallback(u=>{u.preventDefault();let d=r.trim();!d.startsWith("http://")&&!d.startsWith("https://")&&(d=`http://${d}`),n(d),s(c=>c+1)},[r,n]);return f.jsxs("div",{className:"flex h-full flex-col bg-background",children:[f.jsxs("div",{className:"flex shrink-0 items-center gap-1 border-b border-border bg-card px-2 py-1.5",children:[f.jsx(Te,{variant:"ghost",size:"icon-xs",disabled:!0,children:f.jsx(re,{icon:rd,size:14,strokeWidth:2})}),f.jsx(Te,{variant:"ghost",size:"icon-xs",disabled:!0,children:f.jsx(re,{icon:$u,size:14,strokeWidth:2})}),f.jsx(Te,{variant:"ghost",size:"icon-xs",onClick:o,children:f.jsx(re,{icon:iA,size:14,strokeWidth:2})}),f.jsx("form",{onSubmit:l,className:"flex-1",children:f.jsx($t,{value:r,onChange:u=>i(u.target.value),className:"h-7 bg-background text-xs",placeholder:"Enter URL..."})})]}),f.jsx("div",{className:"flex-1 overflow-hidden bg-card",children:f.jsx("iframe",{src:t,className:"h-full w-full border-0",title:"Browser Preview",sandbox:"allow-scripts allow-same-origin allow-forms allow-popups"},a)})]})}const Tgt="";function Qae(e){const t=[];function n(r){for(const i of r)t.push(`${i.type}:${i.path}`),i.children&&n(i.children)}return n(e),t.sort().join("|")}function kgt({worktreePath:e,currentTree:t,onTreeChanged:n,pollInterval:r=5e3,enabled:i=!0}){const a=w.useRef(null),s=w.useRef(null),o=w.useRef(n);w.useEffect(()=>{o.current=n},[n]),w.useEffect(()=>{t&&(s.current=Qae(t))},[t]);const l=w.useCallback(async()=>{if(e)try{const u=await ft(`${Tgt}/api/fs/tree?root=${encodeURIComponent(e)}`),d=Qae(u.entries);s.current!==null&&d!==s.current&&(s.current=d,o.current(u.entries))}catch{}},[e]);w.useEffect(()=>{if(!i||!e||!t){a.current&&(clearInterval(a.current),a.current=null);return}return a.current=setInterval(l,r),()=>{a.current&&(clearInterval(a.current),a.current=null)}},[i,e,t,r,l]),w.useEffect(()=>{s.current=null},[e])}function Agt(e){const t=[];function n(r){for(const i of r)i.type==="file"?t.push({name:i.name,path:i.path}):i.children&&n(i.children)}return n(e),t}function r1e(e){const t=e.split(".").pop()?.toLowerCase()||"";return["png","jpg","jpeg","gif","svg","webp","ico"].includes(t)?NNe:["ts","tsx","js","jsx","json","css","html","md","yaml","yml","toml","sh","py","rs","go","sql"].includes(t)?ENe:Dce}function i1e({entry:e,depth:t,selectedFile:n,expandedDirs:r,onSelectFile:i,onToggleDir:a}){const s=r.includes(e.path),o=n===e.path,l=e.type==="directory",u=w.useCallback(()=>{l?a(e.path):i(e.path)},[l,e.path,i,a]);return f.jsxs("div",{children:[f.jsxs("div",{className:Ne("flex items-center gap-1.5 px-2 py-0.5 cursor-pointer text-sm hover:bg-muted/50",o&&"bg-primary/10 text-primary"),style:{paddingLeft:`${t*12+8}px`},onClick:u,children:[f.jsx(re,{icon:l?s?Kj:Ya:r1e(e.name),size:14,strokeWidth:2,className:Ne("shrink-0",l?"text-accent":"text-muted-foreground")}),f.jsx("span",{className:"break-all",children:e.name})]}),l&&s&&e.children&&f.jsx("div",{children:e.children.map(d=>f.jsx(i1e,{entry:d,depth:t+1,selectedFile:n,expandedDirs:r,onSelectFile:i,onToggleDir:a},d.path))})]})}function Ogt({entries:e,selectedFile:t,expandedDirs:n,onSelectFile:r,onToggleDir:i,onCollapseAll:a}){const{t:s}=Nt("repositories"),[o,l]=w.useState(""),u=w.useMemo(()=>Agt(e),[e]),d=w.useMemo(()=>{if(!o.trim())return null;const p=o.trim();return u.map(m=>({...m,score:Math.max(Cs(m.name,p),Cs(m.path,p))})).filter(m=>m.score>0).sort((m,x)=>x.score-m.score)},[u,o]),c=w.useCallback(()=>{l("")},[]);return e.length===0?f.jsx("div",{className:"flex items-center justify-center h-full text-muted-foreground text-sm",children:s("detailView.fileTree.noFiles")}):f.jsxs("div",{className:"flex flex-col h-full bg-background",children:[f.jsxs("div",{className:"flex shrink-0 items-center justify-between px-2 py-1 border-b border-border bg-card",children:[f.jsx("span",{className:"text-xs text-muted-foreground",children:s("detailView.fileTree.title")}),f.jsx("button",{onClick:a,className:"p-1 text-muted-foreground hover:text-foreground rounded hover:bg-muted/50",title:s("detailView.fileTree.collapseAll"),children:f.jsx(re,{icon:Fce,size:14,strokeWidth:2})})]}),f.jsx("div",{className:"shrink-0 px-2 py-1.5 border-b border-border bg-card",children:f.jsxs("div",{className:"relative",children:[f.jsx("input",{type:"text",value:o,onChange:p=>l(p.target.value),placeholder:s("detailView.fileTree.searchPlaceholder"),className:"w-full text-sm bg-muted/50 border border-border rounded px-2 py-1 pr-7 placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"}),o&&f.jsx("button",{onClick:c,className:"absolute right-1.5 top-1/2 -translate-y-1/2 p-0.5 text-muted-foreground hover:text-foreground rounded hover:bg-muted",children:f.jsx(re,{icon:Bn,size:12,strokeWidth:2})})]})}),f.jsx("div",{className:"py-1 flex-1 overflow-auto",children:d?d.length>0?d.map(p=>f.jsxs("div",{className:Ne("flex items-center gap-1.5 px-2 py-0.5 cursor-pointer text-sm hover:bg-muted/50",t===p.path&&"bg-primary/10 text-primary"),onClick:()=>r(p.path),children:[f.jsx(re,{icon:r1e(p.name),size:14,strokeWidth:2,className:"shrink-0 text-muted-foreground"}),f.jsx("span",{className:"truncate",title:p.path,children:p.name}),f.jsx("span",{className:"text-xs text-muted-foreground truncate ml-auto",children:p.path.split("/").slice(0,-1).join("/")})]},p.path)):f.jsx("div",{className:"px-2 py-4 text-center text-sm text-muted-foreground",children:"No matching files"}):e.map(p=>f.jsx(i1e,{entry:p,depth:0,selectedFile:t,expandedDirs:n,onSelectFile:r,onToggleDir:i},p.path))})]})}const Rgt="";function Ngt({worktreePath:e,filePath:t,currentMtime:n,isDirty:r,pollInterval:i=3e3,enabled:a=!0}){const[s,o]=w.useState(!1),l=w.useRef(null),u=w.useRef(null),d=w.useCallback(async()=>{if(!(!e||!t||!n))try{const p=new URLSearchParams({path:t,root:e}),m=await ft(`${Rgt}/api/fs/file-stat?${p}`);m.exists&&m.mtime!==n&&u.current!==m.mtime&&(u.current=m.mtime,o(!0))}catch{}},[e,t,n]);w.useEffect(()=>{if(!a||!t||!n){l.current&&(clearInterval(l.current),l.current=null);return}u.current=null;const p=setTimeout(d,500);return l.current=setInterval(d,i),()=>{clearTimeout(p),l.current&&(clearInterval(l.current),l.current=null)}},[a,t,n,i,d]),w.useEffect(()=>{o(!1),u.current=null},[t]);const c=w.useCallback(()=>{o(!1)},[]);return{hasExternalChange:s,isConflict:s&&r,resetExternalChange:c}}function Jae(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n<t;n++)r[n]=e[n];return r}function Igt(e){if(Array.isArray(e))return e}function Dgt(e,t,n){return(t=$gt(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function Lgt(e,t){var n=e==null?null:typeof Symbol<"u"&&e[Symbol.iterator]||e["@@iterator"];if(n!=null){var r,i,a,s,o=[],l=!0,u=!1;try{if(a=(n=n.call(e)).next,t!==0)for(;!(l=(r=a.call(n)).done)&&(o.push(r.value),o.length!==t);l=!0);}catch(d){u=!0,i=d}finally{try{if(!l&&n.return!=null&&(s=n.return(),Object(s)!==s))return}finally{if(u)throw i}}return o}}function Pgt(){throw new TypeError(`Invalid attempt to destructure non-iterable instance.
127
127
  In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function ese(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function tse(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]!=null?arguments[t]:{};t%2?ese(Object(n),!0).forEach(function(r){Dgt(e,r,n[r])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):ese(Object(n)).forEach(function(r){Object.defineProperty(e,r,Object.getOwnPropertyDescriptor(n,r))})}return e}function Mgt(e,t){if(e==null)return{};var n,r,i=jgt(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r<a.length;r++)n=a[r],t.indexOf(n)===-1&&{}.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}function jgt(e,t){if(e==null)return{};var n={};for(var r in e)if({}.hasOwnProperty.call(e,r)){if(t.indexOf(r)!==-1)continue;n[r]=e[r]}return n}function Fgt(e,t){return Igt(e)||Lgt(e,t)||Ugt(e,t)||Pgt()}function Bgt(e,t){if(typeof e!="object"||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t);if(typeof r!="object")return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}function $gt(e){var t=Bgt(e,"string");return typeof t=="symbol"?t:t+""}function Ugt(e,t){if(e){if(typeof e=="string")return Jae(e,t);var n={}.toString.call(e).slice(8,-1);return n==="Object"&&e.constructor&&(n=e.constructor.name),n==="Map"||n==="Set"?Array.from(e):n==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?Jae(e,t):void 0}}function zgt(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function nse(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(i){return Object.getOwnPropertyDescriptor(e,i).enumerable})),n.push.apply(n,r)}return n}function rse(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]!=null?arguments[t]:{};t%2?nse(Object(n),!0).forEach(function(r){zgt(e,r,n[r])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):nse(Object(n)).forEach(function(r){Object.defineProperty(e,r,Object.getOwnPropertyDescriptor(n,r))})}return e}function Hgt(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return function(r){return t.reduceRight(function(i,a){return a(i)},r)}}function R0(e){return function t(){for(var n=this,r=arguments.length,i=new Array(r),a=0;a<r;a++)i[a]=arguments[a];return i.length>=e.length?e.apply(this,i):function(){for(var s=arguments.length,o=new Array(s),l=0;l<s;l++)o[l]=arguments[l];return t.apply(n,[].concat(i,o))}}}function ek(e){return{}.toString.call(e).includes("Object")}function Wgt(e){return!Object.keys(e).length}function Dx(e){return typeof e=="function"}function Vgt(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function qgt(e,t){return ek(t)||jf("changeType"),Object.keys(t).some(function(n){return!Vgt(e,n)})&&jf("changeField"),t}function Ggt(e){Dx(e)||jf("selectorType")}function Ygt(e){Dx(e)||ek(e)||jf("handlerType"),ek(e)&&Object.values(e).some(function(t){return!Dx(t)})&&jf("handlersType")}function Kgt(e){e||jf("initialIsRequired"),ek(e)||jf("initialType"),Wgt(e)&&jf("initialContent")}function Xgt(e,t){throw new Error(e[t]||e.default)}var Zgt={initialIsRequired:"initial state is required",initialType:"initial state should be an object",initialContent:"initial state shouldn't be an empty object",handlerType:"handler should be an object or a function",handlersType:"all handlers should be a functions",selectorType:"selector should be a function",changeType:"provided value of changes should be an object",changeField:'it seams you want to change a field in the state which is not specified in the "initial" state',default:"an unknown error accured in `state-local` package"},jf=R0(Xgt)(Zgt),dw={changes:qgt,selector:Ggt,handler:Ygt,initial:Kgt};function Qgt(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{};dw.initial(e),dw.handler(t);var n={current:e},r=R0(tbt)(n,t),i=R0(ebt)(n),a=R0(dw.changes)(e),s=R0(Jgt)(n);function o(){var u=arguments.length>0&&arguments[0]!==void 0?arguments[0]:function(d){return d};return dw.selector(u),u(n.current)}function l(u){Hgt(r,i,a,s)(u)}return[o,l]}function Jgt(e,t){return Dx(t)?t(e.current):t}function ebt(e,t){return e.current=rse(rse({},e.current),t),t}function tbt(e,t,n){return Dx(t)?t(e.current):Object.keys(n).forEach(function(r){var i;return(i=t[r])===null||i===void 0?void 0:i.call(t,e.current[r])}),n}var nbt={create:Qgt},rbt={paths:{vs:"https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs"}};function ibt(e){return function t(){for(var n=this,r=arguments.length,i=new Array(r),a=0;a<r;a++)i[a]=arguments[a];return i.length>=e.length?e.apply(this,i):function(){for(var s=arguments.length,o=new Array(s),l=0;l<s;l++)o[l]=arguments[l];return t.apply(n,[].concat(i,o))}}}function abt(e){return{}.toString.call(e).includes("Object")}function sbt(e){return e||ise("configIsRequired"),abt(e)||ise("configType"),e.urls?(obt(),{paths:{vs:e.urls.monacoBase}}):e}function obt(){console.warn(a1e.deprecation)}function lbt(e,t){throw new Error(e[t]||e.default)}var a1e={configIsRequired:"the configuration object is required",configType:"the configuration object should be an object",default:"an unknown error accured in `@monaco-editor/loader` package",deprecation:`Deprecation warning!
128
128
  You are using deprecated way of configuration.
package/dist/index.html CHANGED
@@ -5,7 +5,7 @@
5
5
  <link rel="icon" type="image/png" href="/logo.png" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>Project Fulcrum</title>
8
- <script type="module" crossorigin src="/assets/index-J3L6hPet.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-fIJu9jIH.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-_aUlx8a1.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knowsuchagency/fulcrum",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Harness Attention. Orchestrate Agents. Ship.",
5
5
  "license": "PolyForm-Perimeter-1.0.0",
6
6
  "repository": {