@knowsuchagency/fulcrum 1.6.1 → 1.7.1

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.
Files changed (3) hide show
  1. package/bin/fulcrum.js +56 -2555
  2. package/package.json +1 -1
  3. package/server/index.js +100 -65
package/bin/fulcrum.js CHANGED
@@ -43763,7 +43763,7 @@ var init_registry = __esm(() => {
43763
43763
  });
43764
43764
 
43765
43765
  // cli/src/mcp/tools.ts
43766
- import { basename as basename3 } from "path";
43766
+ import { basename as basename2 } from "path";
43767
43767
  function registerTools(server, client) {
43768
43768
  server.tool("search_tools", "Search for available Fulcrum MCP tools by keyword or category. Use this to discover tools for projects, apps, files, tasks, and more.", {
43769
43769
  query: exports_external.optional(exports_external.string()).describe("Search query to match against tool names, descriptions, and keywords"),
@@ -43893,7 +43893,7 @@ function registerTools(server, client) {
43893
43893
  dueDate: exports_external.optional(exports_external.string()).describe("Due date in YYYY-MM-DD format")
43894
43894
  }, async ({ title, repoPath, baseBranch, branch, description, status, projectId, repositoryId, tags, dueDate }) => {
43895
43895
  try {
43896
- const repoName = repoPath ? basename3(repoPath) : null;
43896
+ const repoName = repoPath ? basename2(repoPath) : null;
43897
43897
  const task = await client.createTask({
43898
43898
  title,
43899
43899
  repoPath: repoPath ?? null,
@@ -46475,2391 +46475,102 @@ var currentTaskCommand = defineCommand({
46475
46475
  }
46476
46476
  });
46477
46477
 
46478
- // cli/src/commands/tasks.ts
46479
- import { basename as basename2 } from "path";
46478
+ // cli/src/commands/config.ts
46480
46479
  init_client();
46481
46480
  init_errors();
46482
- var VALID_STATUSES = ["TO_DO", "IN_PROGRESS", "IN_REVIEW", "CANCELED"];
46483
- function formatTask2(task, dependencies, attachments) {
46484
- console.log(`${task.title}`);
46485
- console.log(` ID: ${task.id}`);
46486
- console.log(` Status: ${task.status}`);
46487
- if (task.description) {
46488
- console.log(` Description: ${task.description}`);
46489
- }
46490
- if (task.repoName)
46491
- console.log(` Repo: ${task.repoName}`);
46492
- if (task.branch)
46493
- console.log(` Branch: ${task.branch}`);
46494
- if (task.worktreePath)
46495
- console.log(` Worktree: ${task.worktreePath}`);
46496
- if (task.prUrl)
46497
- console.log(` PR: ${task.prUrl}`);
46498
- if (task.links && task.links.length > 0) {
46499
- console.log(` Links: ${task.links.map((l2) => l2.label || l2.url).join(", ")}`);
46500
- }
46501
- if (task.labels && task.labels.length > 0) {
46502
- console.log(` Labels: ${task.labels.join(", ")}`);
46503
- }
46504
- if (task.dueDate)
46505
- console.log(` Due: ${task.dueDate}`);
46506
- if (task.projectId)
46507
- console.log(` Project: ${task.projectId}`);
46508
- console.log(` Agent: ${task.agent}`);
46509
- if (task.aiMode)
46510
- console.log(` AI Mode: ${task.aiMode}`);
46511
- if (task.agentOptions && Object.keys(task.agentOptions).length > 0) {
46512
- console.log(` Options: ${JSON.stringify(task.agentOptions)}`);
46513
- }
46514
- if (dependencies) {
46515
- if (dependencies.isBlocked) {
46516
- console.log(` Blocked: Yes`);
46517
- }
46518
- if (dependencies.dependsOn.length > 0) {
46519
- console.log(` Depends on: ${dependencies.dependsOn.length} task(s)`);
46520
- for (const dep of dependencies.dependsOn) {
46521
- if (dep.task) {
46522
- console.log(` - ${dep.task.title} [${dep.task.status}]`);
46523
- }
46524
- }
46525
- }
46526
- if (dependencies.dependents.length > 0) {
46527
- console.log(` Blocking: ${dependencies.dependents.length} task(s)`);
46528
- }
46529
- }
46530
- if (attachments && attachments.length > 0) {
46531
- console.log(` Attachments: ${attachments.length} file(s)`);
46532
- }
46533
- if (task.notes) {
46534
- console.log(` Notes: ${task.notes}`);
46535
- }
46536
- console.log(` Created: ${task.createdAt}`);
46537
- if (task.startedAt)
46538
- console.log(` Started: ${task.startedAt}`);
46539
- }
46540
- function formatTaskList(tasks) {
46541
- if (tasks.length === 0) {
46542
- console.log("No tasks found");
46543
- return;
46544
- }
46545
- const byStatus = {
46546
- TO_DO: tasks.filter((t2) => t2.status === "TO_DO"),
46547
- IN_PROGRESS: tasks.filter((t2) => t2.status === "IN_PROGRESS"),
46548
- IN_REVIEW: tasks.filter((t2) => t2.status === "IN_REVIEW"),
46549
- DONE: tasks.filter((t2) => t2.status === "DONE"),
46550
- CANCELED: tasks.filter((t2) => t2.status === "CANCELED")
46551
- };
46552
- for (const [status, statusTasks] of Object.entries(byStatus)) {
46553
- if (statusTasks.length === 0)
46554
- continue;
46555
- console.log(`
46556
- ${status} (${statusTasks.length})`);
46557
- for (const task of statusTasks) {
46558
- const branch = task.branch ? ` [${task.branch}]` : "";
46559
- const labels = task.labels && task.labels.length > 0 ? ` {${task.labels.join(", ")}}` : "";
46560
- const dueDate = task.dueDate ? ` (due: ${task.dueDate})` : "";
46561
- console.log(` ${task.title}${branch}${labels}${dueDate}`);
46562
- const repoInfo = task.repoName ? ` \xB7 ${task.repoName}` : "";
46563
- console.log(` ${task.id}${repoInfo}`);
46564
- }
46565
- }
46566
- }
46567
- async function handleTasksCommand(action, positional, flags) {
46481
+ async function handleConfigCommand(action, positional, flags) {
46568
46482
  const client = new FulcrumClient(flags.url, flags.port);
46569
46483
  switch (action) {
46570
46484
  case "list": {
46571
- let statusFilter;
46572
- if (flags.status) {
46573
- const status = flags.status.toUpperCase();
46574
- if (!VALID_STATUSES.includes(status) && status !== "DONE") {
46575
- throw new CliError("INVALID_STATUS", `Invalid status: ${flags.status}. Valid: ${[...VALID_STATUSES, "DONE"].join(", ")}`, ExitCodes.INVALID_ARGS);
46576
- }
46577
- statusFilter = status;
46578
- }
46579
- let tasks = await client.listTasks();
46580
- if (statusFilter) {
46581
- tasks = tasks.filter((t2) => t2.status === statusFilter);
46582
- }
46583
- if (flags.repo) {
46584
- const repoFilter = flags.repo.toLowerCase();
46585
- tasks = tasks.filter((t2) => t2.repoName && t2.repoName.toLowerCase().includes(repoFilter) || t2.repoPath && t2.repoPath.toLowerCase().includes(repoFilter));
46586
- }
46587
- if (flags["project-id"]) {
46588
- tasks = tasks.filter((t2) => t2.projectId === flags["project-id"]);
46589
- }
46590
- if (flags.orphans === "true" || flags.orphans === "") {
46591
- tasks = tasks.filter((t2) => t2.projectId === null);
46592
- }
46593
- if (flags.label) {
46594
- const labelFilter = flags.label.toLowerCase();
46595
- tasks = tasks.filter((t2) => t2.labels && t2.labels.some((l2) => l2.toLowerCase() === labelFilter));
46596
- }
46597
- if (flags.search) {
46598
- const searchLower = flags.search.toLowerCase();
46599
- tasks = tasks.filter((t2) => {
46600
- if (t2.title.toLowerCase().includes(searchLower))
46601
- return true;
46602
- if (t2.labels && t2.labels.some((l2) => l2.toLowerCase().includes(searchLower)))
46603
- return true;
46604
- return false;
46605
- });
46606
- }
46607
- if (isJsonOutput()) {
46608
- output(tasks);
46609
- } else {
46610
- formatTaskList(tasks);
46611
- }
46612
- break;
46613
- }
46614
- case "get": {
46615
- const [id] = positional;
46616
- if (!id) {
46617
- throw new CliError("MISSING_ID", "Task ID required", ExitCodes.INVALID_ARGS);
46618
- }
46619
- const task = await client.getTask(id);
46620
- const [dependencies, attachments] = await Promise.all([
46621
- client.getTaskDependencies(id),
46622
- client.listTaskAttachments(id)
46623
- ]);
46624
- if (isJsonOutput()) {
46625
- output({ ...task, dependencies, attachments });
46626
- } else {
46627
- formatTask2(task, dependencies, attachments);
46628
- }
46629
- break;
46630
- }
46631
- case "create": {
46632
- const title = flags.title;
46633
- const repoPath = flags.repo || flags["repo-path"];
46634
- const baseBranch = flags["base-branch"] || "main";
46635
- const branch = flags.branch;
46636
- const description = flags.description || "";
46637
- const projectId = flags["project-id"];
46638
- const repositoryId = flags["repository-id"];
46639
- const labelsStr = flags.labels;
46640
- const dueDate = flags["due-date"];
46641
- const status = flags.status?.toUpperCase() || "IN_PROGRESS";
46642
- if (!title) {
46643
- throw new CliError("MISSING_TITLE", "--title is required", ExitCodes.INVALID_ARGS);
46644
- }
46645
- const labels = labelsStr ? labelsStr.split(",").map((l2) => l2.trim()).filter(Boolean) : undefined;
46646
- if (flags.status && !VALID_STATUSES.includes(status)) {
46647
- throw new CliError("INVALID_STATUS", `Invalid status: ${flags.status}. Valid: ${VALID_STATUSES.join(", ")}`, ExitCodes.INVALID_ARGS);
46648
- }
46649
- const repoName = repoPath ? flags["repo-name"] || basename2(repoPath) : undefined;
46650
- const task = await client.createTask({
46651
- title,
46652
- description,
46653
- repoPath: repoPath || null,
46654
- repoName: repoName || null,
46655
- baseBranch: repoPath ? baseBranch : null,
46656
- branch: branch || null,
46657
- worktreePath: flags["worktree-path"] || null,
46658
- status,
46659
- projectId: projectId || null,
46660
- repositoryId: repositoryId || null,
46661
- labels,
46662
- dueDate: dueDate || null
46663
- });
46664
- if (isJsonOutput()) {
46665
- output(task);
46666
- } else {
46667
- console.log(`Created task: ${task.title}`);
46668
- console.log(` ID: ${task.id}`);
46669
- console.log(` Status: ${task.status}`);
46670
- if (task.worktreePath)
46671
- console.log(` Worktree: ${task.worktreePath}`);
46672
- if (task.labels && task.labels.length > 0)
46673
- console.log(` Labels: ${task.labels.join(", ")}`);
46674
- if (task.dueDate)
46675
- console.log(` Due: ${task.dueDate}`);
46676
- }
46677
- break;
46678
- }
46679
- case "update": {
46680
- const [id] = positional;
46681
- if (!id) {
46682
- throw new CliError("MISSING_ID", "Task ID required", ExitCodes.INVALID_ARGS);
46683
- }
46684
- const updates = {};
46685
- if (flags.title !== undefined)
46686
- updates.title = flags.title;
46687
- if (flags.description !== undefined)
46688
- updates.description = flags.description;
46689
- if (Object.keys(updates).length === 0) {
46690
- throw new CliError("NO_UPDATES", "No updates provided. Use --title or --description", ExitCodes.INVALID_ARGS);
46691
- }
46692
- const task = await client.updateTask(id, updates);
46693
- if (isJsonOutput()) {
46694
- output(task);
46695
- } else {
46696
- console.log(`Updated task: ${task.title}`);
46697
- }
46698
- break;
46699
- }
46700
- case "move": {
46701
- const [id] = positional;
46702
- if (!id) {
46703
- throw new CliError("MISSING_ID", "Task ID required", ExitCodes.INVALID_ARGS);
46704
- }
46705
- const status = flags.status?.toUpperCase() || "";
46706
- if (!status || !VALID_STATUSES.includes(status)) {
46707
- throw new CliError("INVALID_STATUS", `--status is required. Valid: ${VALID_STATUSES.join(", ")}`, ExitCodes.INVALID_ARGS);
46708
- }
46709
- const position = flags.position ? parseInt(flags.position, 10) : undefined;
46710
- const task = await client.moveTask(id, status, position);
46711
- if (isJsonOutput()) {
46712
- output(task);
46713
- } else {
46714
- console.log(`Moved task to ${status}: ${task.title}`);
46715
- }
46716
- break;
46717
- }
46718
- case "delete": {
46719
- const [id] = positional;
46720
- if (!id) {
46721
- throw new CliError("MISSING_ID", "Task ID required", ExitCodes.INVALID_ARGS);
46722
- }
46723
- const deleteLinkedWorktree = flags["delete-worktree"] === "true" || flags["delete-worktree"] === "";
46724
- await client.deleteTask(id, deleteLinkedWorktree);
46725
- if (isJsonOutput()) {
46726
- output({ deleted: id });
46727
- } else {
46728
- console.log(`Deleted task: ${id}`);
46729
- }
46730
- break;
46731
- }
46732
- case "add-label": {
46733
- const [id, label] = positional;
46734
- if (!id) {
46735
- throw new CliError("MISSING_ID", "Task ID required", ExitCodes.INVALID_ARGS);
46736
- }
46737
- if (!label) {
46738
- throw new CliError("MISSING_LABEL", "Label required", ExitCodes.INVALID_ARGS);
46739
- }
46740
- const result = await client.addTaskLabel(id, label);
46741
- if (isJsonOutput()) {
46742
- output(result);
46743
- } else {
46744
- console.log(`Added label "${label}" to task`);
46745
- console.log(` Labels: ${result.labels.join(", ")}`);
46746
- }
46747
- break;
46748
- }
46749
- case "remove-label": {
46750
- const [id, label] = positional;
46751
- if (!id) {
46752
- throw new CliError("MISSING_ID", "Task ID required", ExitCodes.INVALID_ARGS);
46753
- }
46754
- if (!label) {
46755
- throw new CliError("MISSING_LABEL", "Label required", ExitCodes.INVALID_ARGS);
46756
- }
46757
- const result = await client.removeTaskLabel(id, label);
46758
- if (isJsonOutput()) {
46759
- output(result);
46760
- } else {
46761
- console.log(`Removed label "${label}" from task`);
46762
- console.log(` Labels: ${result.labels.length > 0 ? result.labels.join(", ") : "(none)"}`);
46763
- }
46764
- break;
46765
- }
46766
- case "set-due-date": {
46767
- const [id, date] = positional;
46768
- if (!id) {
46769
- throw new CliError("MISSING_ID", "Task ID required", ExitCodes.INVALID_ARGS);
46770
- }
46771
- const dueDate = date && date !== "null" && date !== "none" ? date : null;
46772
- const result = await client.setTaskDueDate(id, dueDate);
46485
+ const config = await client.getAllConfig();
46773
46486
  if (isJsonOutput()) {
46774
- output(result);
46487
+ output(config);
46775
46488
  } else {
46776
- if (result.dueDate) {
46777
- console.log(`Set due date: ${result.dueDate}`);
46778
- } else {
46779
- console.log("Cleared due date");
46489
+ console.log("Configuration:");
46490
+ for (const [key, value] of Object.entries(config)) {
46491
+ const displayValue = value === null ? "(not set)" : value;
46492
+ console.log(` ${key}: ${displayValue}`);
46780
46493
  }
46781
46494
  }
46782
46495
  break;
46783
46496
  }
46784
- case "add-dependency": {
46785
- const [id, dependsOnId] = positional;
46786
- if (!id) {
46787
- throw new CliError("MISSING_ID", "Task ID required", ExitCodes.INVALID_ARGS);
46788
- }
46789
- if (!dependsOnId) {
46790
- throw new CliError("MISSING_DEPENDS_ON", "Depends-on task ID required", ExitCodes.INVALID_ARGS);
46497
+ case "get": {
46498
+ const [key] = positional;
46499
+ if (!key) {
46500
+ throw new CliError("MISSING_KEY", "Config key is required", ExitCodes.INVALID_ARGS);
46791
46501
  }
46792
- const result = await client.addTaskDependency(id, dependsOnId);
46502
+ const config = await client.getConfig(key);
46793
46503
  if (isJsonOutput()) {
46794
- output(result);
46504
+ output(config);
46795
46505
  } else {
46796
- console.log(`Added dependency: task ${id} now depends on ${dependsOnId}`);
46506
+ const value = config.value === null ? "(not set)" : config.value;
46507
+ console.log(`${key}: ${value}`);
46797
46508
  }
46798
46509
  break;
46799
46510
  }
46800
- case "remove-dependency": {
46801
- const [id, depId] = positional;
46802
- if (!id) {
46803
- throw new CliError("MISSING_ID", "Task ID required", ExitCodes.INVALID_ARGS);
46804
- }
46805
- if (!depId) {
46806
- throw new CliError("MISSING_DEP_ID", "Dependency ID required", ExitCodes.INVALID_ARGS);
46807
- }
46808
- await client.removeTaskDependency(id, depId);
46809
- if (isJsonOutput()) {
46810
- output({ success: true });
46811
- } else {
46812
- console.log("Removed dependency");
46511
+ case "set": {
46512
+ const [key, value] = positional;
46513
+ if (!key) {
46514
+ throw new CliError("MISSING_KEY", "Config key is required", ExitCodes.INVALID_ARGS);
46813
46515
  }
46814
- break;
46815
- }
46816
- case "list-dependencies": {
46817
- const [id] = positional;
46818
- if (!id) {
46819
- throw new CliError("MISSING_ID", "Task ID required", ExitCodes.INVALID_ARGS);
46516
+ if (value === undefined) {
46517
+ throw new CliError("MISSING_VALUE", "Config value is required", ExitCodes.INVALID_ARGS);
46820
46518
  }
46821
- const result = await client.getTaskDependencies(id);
46519
+ const parsedValue = /^\d+$/.test(value) ? parseInt(value, 10) : value;
46520
+ const config = await client.setConfig(key, parsedValue);
46822
46521
  if (isJsonOutput()) {
46823
- output(result);
46522
+ output(config);
46824
46523
  } else {
46825
- console.log(`Dependencies for task ${id}:`);
46826
- console.log(` Blocked: ${result.isBlocked ? "Yes" : "No"}`);
46827
- console.log(`
46828
- Depends on (${result.dependsOn.length}):`);
46829
- for (const dep of result.dependsOn) {
46830
- const task = dep.task;
46831
- if (task) {
46832
- console.log(` ${task.title} [${task.status}]`);
46833
- console.log(` ${task.id}`);
46834
- }
46835
- }
46836
- console.log(`
46837
- Dependents (${result.dependents.length}):`);
46838
- for (const dep of result.dependents) {
46839
- const task = dep.task;
46840
- if (task) {
46841
- console.log(` ${task.title} [${task.status}]`);
46842
- console.log(` ${task.id}`);
46843
- }
46844
- }
46524
+ console.log(`Set ${key} = ${config.value}`);
46845
46525
  }
46846
46526
  break;
46847
46527
  }
46848
- case "labels": {
46849
- const tasks = await client.listTasks();
46850
- const labelCounts = new Map;
46851
- for (const task of tasks) {
46852
- if (task.labels) {
46853
- for (const label of task.labels) {
46854
- labelCounts.set(label, (labelCounts.get(label) || 0) + 1);
46855
- }
46856
- }
46857
- }
46858
- let labels = Array.from(labelCounts.entries());
46859
- if (flags.search) {
46860
- const searchLower = flags.search.toLowerCase();
46861
- labels = labels.filter(([name]) => name.toLowerCase().includes(searchLower));
46528
+ case "reset": {
46529
+ const [key] = positional;
46530
+ if (!key) {
46531
+ throw new CliError("MISSING_KEY", "Config key is required", ExitCodes.INVALID_ARGS);
46862
46532
  }
46863
- labels.sort((a2, b2) => b2[1] - a2[1]);
46533
+ const config = await client.resetConfig(key);
46864
46534
  if (isJsonOutput()) {
46865
- output(labels.map(([name, count]) => ({ name, count })));
46535
+ output(config);
46866
46536
  } else {
46867
- if (labels.length === 0) {
46868
- console.log("No labels found");
46869
- } else {
46870
- console.log(`
46871
- Labels:`);
46872
- for (const [name, count] of labels) {
46873
- console.log(` ${name} (${count})`);
46874
- }
46875
- }
46537
+ console.log(`Reset ${key} to default: ${config.value}`);
46876
46538
  }
46877
46539
  break;
46878
46540
  }
46879
- case "attachments": {
46880
- const [subAction, taskIdOrFile, fileOrAttachmentId] = positional;
46881
- if (!subAction || subAction === "help") {
46882
- console.log("Usage:");
46883
- console.log(" fulcrum tasks attachments list <task-id>");
46884
- console.log(" fulcrum tasks attachments upload <task-id> <file-path>");
46885
- console.log(" fulcrum tasks attachments delete <task-id> <attachment-id>");
46886
- console.log(" fulcrum tasks attachments path <task-id> <attachment-id>");
46887
- break;
46888
- }
46889
- if (subAction === "list") {
46890
- const taskId = taskIdOrFile;
46891
- if (!taskId) {
46892
- throw new CliError("MISSING_ID", "Task ID required", ExitCodes.INVALID_ARGS);
46893
- }
46894
- const attachments = await client.listTaskAttachments(taskId);
46895
- if (isJsonOutput()) {
46896
- output(attachments);
46897
- } else {
46898
- if (attachments.length === 0) {
46899
- console.log("No attachments");
46900
- } else {
46901
- console.log(`
46902
- Attachments (${attachments.length}):`);
46903
- for (const att of attachments) {
46904
- console.log(` ${att.filename}`);
46905
- console.log(` ID: ${att.id}`);
46906
- console.log(` Type: ${att.mimeType}`);
46907
- console.log(` Size: ${att.size} bytes`);
46908
- }
46909
- }
46910
- }
46911
- break;
46912
- }
46913
- if (subAction === "upload") {
46914
- const taskId = taskIdOrFile;
46915
- const filePath = fileOrAttachmentId;
46916
- if (!taskId) {
46917
- throw new CliError("MISSING_ID", "Task ID required", ExitCodes.INVALID_ARGS);
46918
- }
46919
- if (!filePath) {
46920
- throw new CliError("MISSING_FILE", "File path required", ExitCodes.INVALID_ARGS);
46921
- }
46922
- const attachment = await client.uploadTaskAttachment(taskId, filePath);
46923
- if (isJsonOutput()) {
46924
- output(attachment);
46925
- } else {
46926
- console.log(`Uploaded: ${attachment.filename}`);
46927
- console.log(` ID: ${attachment.id}`);
46928
- }
46929
- break;
46930
- }
46931
- if (subAction === "delete") {
46932
- const taskId = taskIdOrFile;
46933
- const attachmentId = fileOrAttachmentId;
46934
- if (!taskId) {
46935
- throw new CliError("MISSING_ID", "Task ID required", ExitCodes.INVALID_ARGS);
46936
- }
46937
- if (!attachmentId) {
46938
- throw new CliError("MISSING_ATTACHMENT_ID", "Attachment ID required", ExitCodes.INVALID_ARGS);
46939
- }
46940
- await client.deleteTaskAttachment(taskId, attachmentId);
46941
- if (isJsonOutput()) {
46942
- output({ success: true, deleted: attachmentId });
46943
- } else {
46944
- console.log(`Deleted attachment: ${attachmentId}`);
46945
- }
46946
- break;
46947
- }
46948
- if (subAction === "path") {
46949
- const taskId = taskIdOrFile;
46950
- const attachmentId = fileOrAttachmentId;
46951
- if (!taskId) {
46952
- throw new CliError("MISSING_ID", "Task ID required", ExitCodes.INVALID_ARGS);
46953
- }
46954
- if (!attachmentId) {
46955
- throw new CliError("MISSING_ATTACHMENT_ID", "Attachment ID required", ExitCodes.INVALID_ARGS);
46956
- }
46957
- const result = await client.getTaskAttachmentPath(taskId, attachmentId);
46958
- if (isJsonOutput()) {
46959
- output(result);
46960
- } else {
46961
- console.log(`Path: ${result.path}`);
46962
- console.log(`Filename: ${result.filename}`);
46963
- console.log(`Type: ${result.mimeType}`);
46964
- }
46965
- break;
46966
- }
46967
- throw new CliError("UNKNOWN_SUBACTION", `Unknown attachments action: ${subAction}. Valid: list, upload, delete, path`, ExitCodes.INVALID_ARGS);
46968
- }
46969
46541
  default:
46970
- 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);
46542
+ throw new CliError("UNKNOWN_ACTION", `Unknown action: ${action}. Valid: list, get, set, reset`, ExitCodes.INVALID_ARGS);
46971
46543
  }
46972
46544
  }
46973
- var tasksListCommand = defineCommand({
46974
- meta: { name: "list", description: "List tasks" },
46975
- args: {
46976
- ...globalArgs,
46977
- status: { type: "string", description: "Filter by status" },
46978
- repo: { type: "string", description: "Filter by repo name/path" },
46979
- "project-id": { type: "string", description: "Filter by project ID" },
46980
- orphans: { type: "boolean", description: "Show only orphan tasks" },
46981
- label: { type: "string", description: "Filter by label" },
46982
- search: { type: "string", description: "Search in title and labels" }
46983
- },
46545
+ var configListCommand = defineCommand({
46546
+ meta: { name: "list", description: "List all config values" },
46547
+ args: globalArgs,
46984
46548
  async run({ args }) {
46985
46549
  setupJsonOutput(args);
46986
- await handleTasksCommand("list", [], toFlags(args));
46550
+ await handleConfigCommand("list", [], toFlags(args));
46987
46551
  }
46988
46552
  });
46989
- var tasksGetCommand = defineCommand({
46990
- meta: { name: "get", description: "Get task details" },
46553
+ var configGetCommand = defineCommand({
46554
+ meta: { name: "get", description: "Get a config value" },
46991
46555
  args: {
46992
46556
  ...globalArgs,
46993
- id: { type: "positional", description: "Task ID", required: true }
46557
+ key: { type: "positional", description: "Config key", required: true }
46994
46558
  },
46995
46559
  async run({ args }) {
46996
46560
  setupJsonOutput(args);
46997
- await handleTasksCommand("get", [args.id], toFlags(args));
46561
+ await handleConfigCommand("get", [args.key], toFlags(args));
46998
46562
  }
46999
46563
  });
47000
- var tasksCreateCommand = defineCommand({
47001
- meta: { name: "create", description: "Create a task" },
46564
+ var configSetCommand = defineCommand({
46565
+ meta: { name: "set", description: "Set a config value" },
47002
46566
  args: {
47003
46567
  ...globalArgs,
47004
- title: { type: "string", description: "Task title", required: true },
47005
- repo: { type: "string", alias: "repo-path", description: "Repository path" },
47006
- "base-branch": { type: "string", description: "Base branch (default: main)" },
47007
- branch: { type: "string", description: "Branch name" },
47008
- description: { type: "string", description: "Task description" },
47009
- "project-id": { type: "string", description: "Project ID" },
47010
- "repository-id": { type: "string", description: "Repository ID" },
47011
- labels: { type: "string", description: "Comma-separated labels" },
47012
- "due-date": { type: "string", description: "Due date (YYYY-MM-DD)" },
47013
- status: { type: "string", description: "Initial status (default: IN_PROGRESS)" }
46568
+ key: { type: "positional", description: "Config key", required: true },
46569
+ value: { type: "positional", description: "Config value", required: true }
47014
46570
  },
47015
46571
  async run({ args }) {
47016
46572
  setupJsonOutput(args);
47017
- await handleTasksCommand("create", [], toFlags(args));
47018
- }
47019
- });
47020
- var tasksUpdateCommand = defineCommand({
47021
- meta: { name: "update", description: "Update task metadata" },
47022
- args: {
47023
- ...globalArgs,
47024
- id: { type: "positional", description: "Task ID", required: true },
47025
- title: { type: "string", description: "New title" },
47026
- description: { type: "string", description: "New description" }
47027
- },
47028
- async run({ args }) {
47029
- setupJsonOutput(args);
47030
- await handleTasksCommand("update", [args.id], toFlags(args));
47031
- }
47032
- });
47033
- var tasksMoveCommand = defineCommand({
47034
- meta: { name: "move", description: "Move task to a status" },
47035
- args: {
47036
- ...globalArgs,
47037
- id: { type: "positional", description: "Task ID", required: true },
47038
- status: { type: "string", description: "Target status", required: true },
47039
- position: { type: "string", description: "Position in column" }
47040
- },
47041
- async run({ args }) {
47042
- setupJsonOutput(args);
47043
- await handleTasksCommand("move", [args.id], toFlags(args));
47044
- }
47045
- });
47046
- var tasksDeleteCommand = defineCommand({
47047
- meta: { name: "delete", description: "Delete a task" },
47048
- args: {
47049
- ...globalArgs,
47050
- id: { type: "positional", description: "Task ID", required: true },
47051
- "delete-worktree": { type: "boolean", description: "Also delete the worktree" }
47052
- },
47053
- async run({ args }) {
47054
- setupJsonOutput(args);
47055
- await handleTasksCommand("delete", [args.id], toFlags(args));
47056
- }
47057
- });
47058
- var tasksAddLabelCommand = defineCommand({
47059
- meta: { name: "add-label", description: "Add a label to a task" },
47060
- args: {
47061
- ...globalArgs,
47062
- id: { type: "positional", description: "Task ID", required: true },
47063
- label: { type: "positional", description: "Label to add", required: true }
47064
- },
47065
- async run({ args }) {
47066
- setupJsonOutput(args);
47067
- await handleTasksCommand("add-label", [args.id, args.label], toFlags(args));
47068
- }
47069
- });
47070
- var tasksRemoveLabelCommand = defineCommand({
47071
- meta: { name: "remove-label", description: "Remove a label from a task" },
47072
- args: {
47073
- ...globalArgs,
47074
- id: { type: "positional", description: "Task ID", required: true },
47075
- label: { type: "positional", description: "Label to remove", required: true }
47076
- },
47077
- async run({ args }) {
47078
- setupJsonOutput(args);
47079
- await handleTasksCommand("remove-label", [args.id, args.label], toFlags(args));
47080
- }
47081
- });
47082
- var tasksSetDueDateCommand = defineCommand({
47083
- meta: { name: "set-due-date", description: "Set or clear due date" },
47084
- args: {
47085
- ...globalArgs,
47086
- id: { type: "positional", description: "Task ID", required: true },
47087
- date: { type: "positional", description: "Due date (YYYY-MM-DD or null)" }
47088
- },
47089
- async run({ args }) {
47090
- setupJsonOutput(args);
47091
- await handleTasksCommand("set-due-date", [args.id, args.date], toFlags(args));
47092
- }
47093
- });
47094
- var tasksAddDependencyCommand = defineCommand({
47095
- meta: { name: "add-dependency", description: "Add a task dependency" },
47096
- args: {
47097
- ...globalArgs,
47098
- id: { type: "positional", description: "Task ID", required: true },
47099
- dependsOn: { type: "positional", description: "Task ID to depend on", required: true }
47100
- },
47101
- async run({ args }) {
47102
- setupJsonOutput(args);
47103
- await handleTasksCommand("add-dependency", [args.id, args.dependsOn], toFlags(args));
47104
- }
47105
- });
47106
- var tasksRemoveDependencyCommand = defineCommand({
47107
- meta: { name: "remove-dependency", description: "Remove a task dependency" },
47108
- args: {
47109
- ...globalArgs,
47110
- id: { type: "positional", description: "Task ID", required: true },
47111
- depId: { type: "positional", description: "Dependency ID to remove", required: true }
47112
- },
47113
- async run({ args }) {
47114
- setupJsonOutput(args);
47115
- await handleTasksCommand("remove-dependency", [args.id, args.depId], toFlags(args));
47116
- }
47117
- });
47118
- var tasksListDependenciesCommand = defineCommand({
47119
- meta: { name: "list-dependencies", description: "List task dependencies" },
47120
- args: {
47121
- ...globalArgs,
47122
- id: { type: "positional", description: "Task ID", required: true }
47123
- },
47124
- async run({ args }) {
47125
- setupJsonOutput(args);
47126
- await handleTasksCommand("list-dependencies", [args.id], toFlags(args));
47127
- }
47128
- });
47129
- var tasksLabelsCommand = defineCommand({
47130
- meta: { name: "labels", description: "List all labels" },
47131
- args: {
47132
- ...globalArgs,
47133
- search: { type: "string", description: "Search filter" }
47134
- },
47135
- async run({ args }) {
47136
- setupJsonOutput(args);
47137
- await handleTasksCommand("labels", [], toFlags(args));
47138
- }
47139
- });
47140
- var tasksAttachmentsCommand = defineCommand({
47141
- meta: { name: "attachments", description: "Manage task attachments" },
47142
- args: {
47143
- ...globalArgs,
47144
- action: { type: "positional", description: "Action: list, upload, delete, path" },
47145
- taskId: { type: "positional", description: "Task ID" },
47146
- fileOrId: { type: "positional", description: "File path or attachment ID" }
47147
- },
47148
- async run({ args }) {
47149
- setupJsonOutput(args);
47150
- const positional = [args.action, args.taskId, args.fileOrId].filter(Boolean);
47151
- await handleTasksCommand("attachments", positional, toFlags(args));
47152
- }
47153
- });
47154
- var tasksCommand = defineCommand({
47155
- meta: { name: "tasks", description: "Manage tasks" },
47156
- subCommands: {
47157
- list: tasksListCommand,
47158
- get: tasksGetCommand,
47159
- create: tasksCreateCommand,
47160
- update: tasksUpdateCommand,
47161
- move: tasksMoveCommand,
47162
- delete: tasksDeleteCommand,
47163
- "add-label": tasksAddLabelCommand,
47164
- "remove-label": tasksRemoveLabelCommand,
47165
- "set-due-date": tasksSetDueDateCommand,
47166
- "add-dependency": tasksAddDependencyCommand,
47167
- "remove-dependency": tasksRemoveDependencyCommand,
47168
- "list-dependencies": tasksListDependenciesCommand,
47169
- labels: tasksLabelsCommand,
47170
- attachments: tasksAttachmentsCommand
47171
- }
47172
- });
47173
-
47174
- // cli/src/commands/projects.ts
47175
- init_client();
47176
- init_errors();
47177
- var VALID_STATUSES2 = ["active", "archived"];
47178
- function formatFileSize(bytes) {
47179
- if (bytes < 1024)
47180
- return `${bytes} B`;
47181
- if (bytes < 1024 * 1024)
47182
- return `${(bytes / 1024).toFixed(1)} KB`;
47183
- return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
47184
- }
47185
- function formatProject(project) {
47186
- console.log(`${project.name}`);
47187
- console.log(` ID: ${project.id}`);
47188
- console.log(` Status: ${project.status}`);
47189
- if (project.description)
47190
- console.log(` Description: ${project.description}`);
47191
- if (project.notes)
47192
- console.log(` Notes: ${project.notes}`);
47193
- if (project.tags && project.tags.length > 0) {
47194
- console.log(` Tags: ${project.tags.map((t2) => t2.name).join(", ")}`);
47195
- }
47196
- if (project.repository) {
47197
- console.log(` Repository: ${project.repository.path}`);
47198
- if (project.repository.remoteUrl) {
47199
- console.log(` Remote: ${project.repository.remoteUrl}`);
47200
- }
47201
- }
47202
- if (project.app) {
47203
- console.log(` App: ${project.app.name} (${project.app.status})`);
47204
- }
47205
- if (project.terminalTab) {
47206
- console.log(` Terminal: ${project.terminalTab.name}`);
47207
- }
47208
- if (project.attachments && project.attachments.length > 0) {
47209
- console.log(` Attachments:`);
47210
- for (const att of project.attachments) {
47211
- console.log(` - ${att.filename} (${formatFileSize(att.size)})`);
47212
- console.log(` ID: ${att.id}`);
47213
- }
47214
- }
47215
- }
47216
- function formatProjectList(projects) {
47217
- if (projects.length === 0) {
47218
- console.log("No projects found");
47219
- return;
47220
- }
47221
- const byStatus = {
47222
- active: projects.filter((p) => p.status === "active"),
47223
- archived: projects.filter((p) => p.status === "archived")
47224
- };
47225
- for (const [status, statusProjects] of Object.entries(byStatus)) {
47226
- if (statusProjects.length === 0)
47227
- continue;
47228
- console.log(`
47229
- ${status.toUpperCase()} (${statusProjects.length})`);
47230
- for (const project of statusProjects) {
47231
- const repoPath = project.repository?.path || "no repository";
47232
- const appStatus = project.app ? ` [${project.app.status}]` : "";
47233
- console.log(` ${project.name}${appStatus}`);
47234
- console.log(` ${project.id} \xB7 ${repoPath}`);
47235
- }
47236
- }
47237
- }
47238
- async function handleProjectsCommand(action, positional, flags) {
47239
- const client = new FulcrumClient(flags.url, flags.port);
47240
- switch (action) {
47241
- case "list": {
47242
- let statusFilter;
47243
- if (flags.status) {
47244
- const status = flags.status.toLowerCase();
47245
- if (!VALID_STATUSES2.includes(status)) {
47246
- throw new CliError("INVALID_STATUS", `Invalid status: ${flags.status}. Valid: ${VALID_STATUSES2.join(", ")}`, ExitCodes.INVALID_ARGS);
47247
- }
47248
- statusFilter = status;
47249
- }
47250
- let projects = await client.listProjects();
47251
- if (statusFilter) {
47252
- projects = projects.filter((p) => p.status === statusFilter);
47253
- }
47254
- if (isJsonOutput()) {
47255
- output(projects);
47256
- } else {
47257
- formatProjectList(projects);
47258
- }
47259
- break;
47260
- }
47261
- case "get": {
47262
- const [id] = positional;
47263
- if (!id) {
47264
- throw new CliError("MISSING_ID", "Project ID required", ExitCodes.INVALID_ARGS);
47265
- }
47266
- const project = await client.getProject(id);
47267
- if (isJsonOutput()) {
47268
- output(project);
47269
- } else {
47270
- formatProject(project);
47271
- }
47272
- break;
47273
- }
47274
- case "create": {
47275
- const name = flags.name;
47276
- if (!name) {
47277
- throw new CliError("MISSING_NAME", "--name is required", ExitCodes.INVALID_ARGS);
47278
- }
47279
- const options = [flags["repository-id"], flags.path, flags.url].filter(Boolean);
47280
- if (options.length === 0) {
47281
- throw new CliError("MISSING_SOURCE", "Must provide --repository-id, --path, or --url", ExitCodes.INVALID_ARGS);
47282
- }
47283
- if (options.length > 1) {
47284
- throw new CliError("CONFLICTING_OPTIONS", "Provide only one of: --repository-id, --path, or --url", ExitCodes.INVALID_ARGS);
47285
- }
47286
- const project = await client.createProject({
47287
- name,
47288
- description: flags.description,
47289
- repositoryId: flags["repository-id"],
47290
- path: flags.path,
47291
- url: flags.url,
47292
- targetDir: flags["target-dir"],
47293
- folderName: flags["folder-name"]
47294
- });
47295
- if (isJsonOutput()) {
47296
- output(project);
47297
- } else {
47298
- console.log(`Created project: ${project.name}`);
47299
- console.log(` ID: ${project.id}`);
47300
- if (project.repository) {
47301
- console.log(` Path: ${project.repository.path}`);
47302
- }
47303
- }
47304
- break;
47305
- }
47306
- case "update": {
47307
- const [id] = positional;
47308
- if (!id) {
47309
- throw new CliError("MISSING_ID", "Project ID required", ExitCodes.INVALID_ARGS);
47310
- }
47311
- const updates = {};
47312
- if (flags.name !== undefined)
47313
- updates.name = flags.name;
47314
- if (flags.description !== undefined)
47315
- updates.description = flags.description;
47316
- if (flags.notes !== undefined)
47317
- updates.notes = flags.notes || null;
47318
- if (flags.status !== undefined) {
47319
- const status = flags.status.toLowerCase();
47320
- if (!VALID_STATUSES2.includes(status)) {
47321
- throw new CliError("INVALID_STATUS", `Invalid status: ${flags.status}. Valid: ${VALID_STATUSES2.join(", ")}`, ExitCodes.INVALID_ARGS);
47322
- }
47323
- updates.status = status;
47324
- }
47325
- if (Object.keys(updates).length === 0) {
47326
- throw new CliError("NO_UPDATES", "No updates provided. Use --name, --description, --notes, or --status", ExitCodes.INVALID_ARGS);
47327
- }
47328
- const project = await client.updateProject(id, updates);
47329
- if (isJsonOutput()) {
47330
- output(project);
47331
- } else {
47332
- console.log(`Updated project: ${project.name}`);
47333
- }
47334
- break;
47335
- }
47336
- case "delete": {
47337
- const [id] = positional;
47338
- if (!id) {
47339
- throw new CliError("MISSING_ID", "Project ID required", ExitCodes.INVALID_ARGS);
47340
- }
47341
- const deleteDirectory = flags["delete-directory"] === "true" || flags["delete-directory"] === "";
47342
- const deleteApp = flags["delete-app"] === "true" || flags["delete-app"] === "";
47343
- const result = await client.deleteProject(id, { deleteDirectory, deleteApp });
47344
- if (isJsonOutput()) {
47345
- output({ deleted: id, ...result });
47346
- } else {
47347
- console.log(`Deleted project: ${id}`);
47348
- if (result.deletedDirectory)
47349
- console.log(" Directory deleted");
47350
- if (result.deletedApp)
47351
- console.log(" App deleted");
47352
- }
47353
- break;
47354
- }
47355
- case "scan": {
47356
- const directory = flags.directory || flags.path;
47357
- const result = await client.scanProjects(directory);
47358
- if (isJsonOutput()) {
47359
- output(result);
47360
- } else {
47361
- console.log(`Scanned: ${result.directory}`);
47362
- console.log(`Found ${result.repositories.length} git repositories:`);
47363
- for (const repo of result.repositories) {
47364
- const status = repo.hasProject ? "[project]" : repo.hasRepository ? "[repo]" : "[new]";
47365
- console.log(` ${status} ${repo.name}`);
47366
- console.log(` ${repo.path}`);
47367
- }
47368
- }
47369
- break;
47370
- }
47371
- case "tags": {
47372
- const [subAction, projectId, tagArg] = positional;
47373
- if (!subAction) {
47374
- throw new CliError("MISSING_SUBACTION", "Subaction required: add, remove", ExitCodes.INVALID_ARGS);
47375
- }
47376
- if (!projectId) {
47377
- throw new CliError("MISSING_ID", "Project ID required", ExitCodes.INVALID_ARGS);
47378
- }
47379
- switch (subAction) {
47380
- case "add": {
47381
- if (!tagArg) {
47382
- throw new CliError("MISSING_TAG", "Tag name required", ExitCodes.INVALID_ARGS);
47383
- }
47384
- const tag = await client.addProjectTag(projectId, tagArg);
47385
- if (isJsonOutput()) {
47386
- output(tag);
47387
- } else {
47388
- console.log(`Added tag "${tag.name}" to project`);
47389
- }
47390
- break;
47391
- }
47392
- case "remove": {
47393
- if (!tagArg) {
47394
- throw new CliError("MISSING_TAG", "Tag ID required", ExitCodes.INVALID_ARGS);
47395
- }
47396
- await client.removeProjectTag(projectId, tagArg);
47397
- if (isJsonOutput()) {
47398
- output({ success: true });
47399
- } else {
47400
- console.log(`Removed tag from project`);
47401
- }
47402
- break;
47403
- }
47404
- default:
47405
- throw new CliError("UNKNOWN_SUBACTION", `Unknown subaction: ${subAction}. Valid: add, remove`, ExitCodes.INVALID_ARGS);
47406
- }
47407
- break;
47408
- }
47409
- case "attachments": {
47410
- const [subAction, projectId, fileOrId] = positional;
47411
- if (!subAction) {
47412
- throw new CliError("MISSING_SUBACTION", "Subaction required: list, upload, download, delete", ExitCodes.INVALID_ARGS);
47413
- }
47414
- if (!projectId) {
47415
- throw new CliError("MISSING_ID", "Project ID required", ExitCodes.INVALID_ARGS);
47416
- }
47417
- switch (subAction) {
47418
- case "list": {
47419
- const attachments = await client.listProjectAttachments(projectId);
47420
- if (isJsonOutput()) {
47421
- output(attachments);
47422
- } else if (attachments.length === 0) {
47423
- console.log("No attachments");
47424
- } else {
47425
- for (const att of attachments) {
47426
- console.log(`${att.filename} (${formatFileSize(att.size)})`);
47427
- console.log(` ID: ${att.id}`);
47428
- console.log(` Type: ${att.mimeType}`);
47429
- }
47430
- }
47431
- break;
47432
- }
47433
- case "upload": {
47434
- if (!fileOrId) {
47435
- throw new CliError("MISSING_FILE", "File path required", ExitCodes.INVALID_ARGS);
47436
- }
47437
- const attachment = await client.uploadProjectAttachment(projectId, fileOrId);
47438
- if (isJsonOutput()) {
47439
- output(attachment);
47440
- } else {
47441
- console.log(`Uploaded: ${attachment.filename}`);
47442
- console.log(` ID: ${attachment.id}`);
47443
- }
47444
- break;
47445
- }
47446
- case "download": {
47447
- if (!fileOrId) {
47448
- throw new CliError("MISSING_ID", "Attachment ID required", ExitCodes.INVALID_ARGS);
47449
- }
47450
- const info = await client.getProjectAttachmentPath(projectId, fileOrId);
47451
- if (isJsonOutput()) {
47452
- output(info);
47453
- } else {
47454
- console.log(`File: ${info.filename}`);
47455
- console.log(`Path: ${info.path}`);
47456
- }
47457
- break;
47458
- }
47459
- case "delete": {
47460
- if (!fileOrId) {
47461
- throw new CliError("MISSING_ID", "Attachment ID required", ExitCodes.INVALID_ARGS);
47462
- }
47463
- await client.deleteProjectAttachment(projectId, fileOrId);
47464
- if (isJsonOutput()) {
47465
- output({ success: true });
47466
- } else {
47467
- console.log(`Deleted attachment: ${fileOrId}`);
47468
- }
47469
- break;
47470
- }
47471
- default:
47472
- throw new CliError("UNKNOWN_SUBACTION", `Unknown subaction: ${subAction}. Valid: list, upload, download, delete`, ExitCodes.INVALID_ARGS);
47473
- }
47474
- break;
47475
- }
47476
- case "links": {
47477
- const [subAction, projectId, urlOrId] = positional;
47478
- if (!subAction) {
47479
- throw new CliError("MISSING_SUBACTION", "Subaction required: list, add, remove", ExitCodes.INVALID_ARGS);
47480
- }
47481
- if (!projectId) {
47482
- throw new CliError("MISSING_ID", "Project ID required", ExitCodes.INVALID_ARGS);
47483
- }
47484
- switch (subAction) {
47485
- case "list": {
47486
- const links = await client.listProjectLinks(projectId);
47487
- if (isJsonOutput()) {
47488
- output(links);
47489
- } else if (links.length === 0) {
47490
- console.log("No links");
47491
- } else {
47492
- for (const link of links) {
47493
- console.log(`${link.label || link.url}`);
47494
- console.log(` ID: ${link.id}`);
47495
- console.log(` URL: ${link.url}`);
47496
- if (link.type)
47497
- console.log(` Type: ${link.type}`);
47498
- }
47499
- }
47500
- break;
47501
- }
47502
- case "add": {
47503
- if (!urlOrId) {
47504
- throw new CliError("MISSING_URL", "URL required", ExitCodes.INVALID_ARGS);
47505
- }
47506
- const link = await client.addProjectLink(projectId, urlOrId, flags.label);
47507
- if (isJsonOutput()) {
47508
- output(link);
47509
- } else {
47510
- console.log(`Added link: ${link.label || link.url}`);
47511
- console.log(` ID: ${link.id}`);
47512
- }
47513
- break;
47514
- }
47515
- case "remove": {
47516
- if (!urlOrId) {
47517
- throw new CliError("MISSING_ID", "Link ID required", ExitCodes.INVALID_ARGS);
47518
- }
47519
- await client.removeProjectLink(projectId, urlOrId);
47520
- if (isJsonOutput()) {
47521
- output({ success: true });
47522
- } else {
47523
- console.log(`Removed link: ${urlOrId}`);
47524
- }
47525
- break;
47526
- }
47527
- default:
47528
- throw new CliError("UNKNOWN_SUBACTION", `Unknown subaction: ${subAction}. Valid: list, add, remove`, ExitCodes.INVALID_ARGS);
47529
- }
47530
- break;
47531
- }
47532
- default:
47533
- throw new CliError("UNKNOWN_ACTION", `Unknown action: ${action}. Valid: list, get, create, update, delete, scan, tags, attachments, links`, ExitCodes.INVALID_ARGS);
47534
- }
47535
- }
47536
- var projectsListCommand = defineCommand({
47537
- meta: { name: "list", description: "List projects" },
47538
- args: {
47539
- ...globalArgs,
47540
- status: { type: "string", description: "Filter by status (active/archived)" }
47541
- },
47542
- async run({ args }) {
47543
- setupJsonOutput(args);
47544
- await handleProjectsCommand("list", [], toFlags(args));
47545
- }
47546
- });
47547
- var projectsGetCommand = defineCommand({
47548
- meta: { name: "get", description: "Get project details" },
47549
- args: {
47550
- ...globalArgs,
47551
- id: { type: "positional", description: "Project ID", required: true }
47552
- },
47553
- async run({ args }) {
47554
- setupJsonOutput(args);
47555
- await handleProjectsCommand("get", [args.id], toFlags(args));
47556
- }
47557
- });
47558
- var projectsCreateCommand = defineCommand({
47559
- meta: { name: "create", description: "Create a project" },
47560
- args: {
47561
- ...globalArgs,
47562
- name: { type: "string", description: "Project name", required: true },
47563
- description: { type: "string", description: "Project description" },
47564
- "repository-id": { type: "string", description: "Use existing repository" },
47565
- path: { type: "string", description: "Local path to repository" },
47566
- url: { type: "string", description: "Git URL to clone" },
47567
- "target-dir": { type: "string", description: "Clone target directory" },
47568
- "folder-name": { type: "string", description: "Clone folder name" }
47569
- },
47570
- async run({ args }) {
47571
- setupJsonOutput(args);
47572
- await handleProjectsCommand("create", [], toFlags(args));
47573
- }
47574
- });
47575
- var projectsUpdateCommand = defineCommand({
47576
- meta: { name: "update", description: "Update project metadata" },
47577
- args: {
47578
- ...globalArgs,
47579
- id: { type: "positional", description: "Project ID", required: true },
47580
- name: { type: "string", description: "New name" },
47581
- description: { type: "string", description: "New description" },
47582
- notes: { type: "string", description: "New notes" },
47583
- status: { type: "string", description: "New status (active/archived)" }
47584
- },
47585
- async run({ args }) {
47586
- setupJsonOutput(args);
47587
- await handleProjectsCommand("update", [args.id], toFlags(args));
47588
- }
47589
- });
47590
- var projectsDeleteCommand = defineCommand({
47591
- meta: { name: "delete", description: "Delete a project" },
47592
- args: {
47593
- ...globalArgs,
47594
- id: { type: "positional", description: "Project ID", required: true },
47595
- "delete-directory": { type: "boolean", description: "Also delete the directory" },
47596
- "delete-app": { type: "boolean", description: "Also delete the app" }
47597
- },
47598
- async run({ args }) {
47599
- setupJsonOutput(args);
47600
- await handleProjectsCommand("delete", [args.id], toFlags(args));
47601
- }
47602
- });
47603
- var projectsScanCommand = defineCommand({
47604
- meta: { name: "scan", description: "Scan directory for git repos" },
47605
- args: {
47606
- ...globalArgs,
47607
- directory: { type: "string", alias: "path", description: "Directory to scan" }
47608
- },
47609
- async run({ args }) {
47610
- setupJsonOutput(args);
47611
- await handleProjectsCommand("scan", [], toFlags(args));
47612
- }
47613
- });
47614
- var projectsTagsCommand = defineCommand({
47615
- meta: { name: "tags", description: "Manage project tags" },
47616
- args: {
47617
- ...globalArgs,
47618
- action: { type: "positional", description: "Action: add, remove" },
47619
- projectId: { type: "positional", description: "Project ID" },
47620
- tag: { type: "positional", description: "Tag name or ID" }
47621
- },
47622
- async run({ args }) {
47623
- setupJsonOutput(args);
47624
- const positional = [args.action, args.projectId, args.tag].filter(Boolean);
47625
- await handleProjectsCommand("tags", positional, toFlags(args));
47626
- }
47627
- });
47628
- var projectsAttachmentsCommand = defineCommand({
47629
- meta: { name: "attachments", description: "Manage project attachments" },
47630
- args: {
47631
- ...globalArgs,
47632
- action: { type: "positional", description: "Action: list, upload, download, delete" },
47633
- projectId: { type: "positional", description: "Project ID" },
47634
- fileOrId: { type: "positional", description: "File path or attachment ID" }
47635
- },
47636
- async run({ args }) {
47637
- setupJsonOutput(args);
47638
- const positional = [args.action, args.projectId, args.fileOrId].filter(Boolean);
47639
- await handleProjectsCommand("attachments", positional, toFlags(args));
47640
- }
47641
- });
47642
- var projectsLinksCommand = defineCommand({
47643
- meta: { name: "links", description: "Manage project links" },
47644
- args: {
47645
- ...globalArgs,
47646
- action: { type: "positional", description: "Action: list, add, remove" },
47647
- projectId: { type: "positional", description: "Project ID" },
47648
- urlOrId: { type: "positional", description: "URL or link ID" },
47649
- label: { type: "string", description: "Link label (for add)" }
47650
- },
47651
- async run({ args }) {
47652
- setupJsonOutput(args);
47653
- const positional = [args.action, args.projectId, args.urlOrId].filter(Boolean);
47654
- await handleProjectsCommand("links", positional, toFlags(args));
47655
- }
47656
- });
47657
- var projectsCommand = defineCommand({
47658
- meta: { name: "projects", description: "Manage projects" },
47659
- subCommands: {
47660
- list: projectsListCommand,
47661
- get: projectsGetCommand,
47662
- create: projectsCreateCommand,
47663
- update: projectsUpdateCommand,
47664
- delete: projectsDeleteCommand,
47665
- scan: projectsScanCommand,
47666
- tags: projectsTagsCommand,
47667
- attachments: projectsAttachmentsCommand,
47668
- links: projectsLinksCommand
47669
- }
47670
- });
47671
-
47672
- // cli/src/commands/repositories.ts
47673
- init_client();
47674
- init_errors();
47675
- var VALID_AGENTS = ["claude", "opencode"];
47676
- function formatRepository(repo) {
47677
- console.log(`${repo.displayName}`);
47678
- console.log(` ID: ${repo.id}`);
47679
- console.log(` Path: ${repo.path}`);
47680
- if (repo.remoteUrl)
47681
- console.log(` Remote: ${repo.remoteUrl}`);
47682
- if (repo.defaultAgent)
47683
- console.log(` Agent: ${repo.defaultAgent}`);
47684
- if (repo.startupScript)
47685
- console.log(` Startup: ${repo.startupScript}`);
47686
- if (repo.copyFiles)
47687
- console.log(` Copy Files: ${repo.copyFiles}`);
47688
- if (repo.isCopierTemplate)
47689
- console.log(` Template: yes`);
47690
- }
47691
- function formatRepositoryList(repos) {
47692
- if (repos.length === 0) {
47693
- console.log("No repositories found");
47694
- return;
47695
- }
47696
- console.log(`
47697
- ${repos.length} REPOSITORIES`);
47698
- for (const repo of repos) {
47699
- const agent = repo.defaultAgent ? ` [${repo.defaultAgent}]` : "";
47700
- console.log(` ${repo.displayName}${agent}`);
47701
- console.log(` ${repo.id} \xB7 ${repo.path}`);
47702
- }
47703
- }
47704
- async function handleRepositoriesCommand(action, positional, flags) {
47705
- const client = new FulcrumClient(flags.url, flags.port);
47706
- switch (action) {
47707
- case "list": {
47708
- const orphans = flags.orphans === "true" || flags.orphans === "";
47709
- const projectId = flags["project-id"];
47710
- const repos = await client.listRepositories({ orphans, projectId });
47711
- if (isJsonOutput()) {
47712
- output(repos);
47713
- } else {
47714
- formatRepositoryList(repos);
47715
- }
47716
- break;
47717
- }
47718
- case "get": {
47719
- const [id] = positional;
47720
- if (!id) {
47721
- throw new CliError("MISSING_ID", "Repository ID required", ExitCodes.INVALID_ARGS);
47722
- }
47723
- const repo = await client.getRepository(id);
47724
- if (isJsonOutput()) {
47725
- output(repo);
47726
- } else {
47727
- formatRepository(repo);
47728
- }
47729
- break;
47730
- }
47731
- case "add": {
47732
- const path = flags.path;
47733
- if (!path) {
47734
- throw new CliError("MISSING_PATH", "--path is required", ExitCodes.INVALID_ARGS);
47735
- }
47736
- const repo = await client.addRepository(path, flags["display-name"]);
47737
- if (isJsonOutput()) {
47738
- output(repo);
47739
- } else {
47740
- console.log(`Added repository: ${repo.displayName}`);
47741
- console.log(` ID: ${repo.id}`);
47742
- console.log(` Path: ${repo.path}`);
47743
- }
47744
- break;
47745
- }
47746
- case "update": {
47747
- const [id] = positional;
47748
- if (!id) {
47749
- throw new CliError("MISSING_ID", "Repository ID required", ExitCodes.INVALID_ARGS);
47750
- }
47751
- const updates = {};
47752
- if (flags["display-name"] !== undefined)
47753
- updates.displayName = flags["display-name"];
47754
- if (flags["startup-script"] !== undefined)
47755
- updates.startupScript = flags["startup-script"] || null;
47756
- if (flags["copy-files"] !== undefined)
47757
- updates.copyFiles = flags["copy-files"] || null;
47758
- if (flags["default-agent"] !== undefined) {
47759
- const agent = flags["default-agent"].toLowerCase();
47760
- if (agent && !VALID_AGENTS.includes(agent)) {
47761
- throw new CliError("INVALID_AGENT", `Invalid agent: ${flags["default-agent"]}. Valid: ${VALID_AGENTS.join(", ")}`, ExitCodes.INVALID_ARGS);
47762
- }
47763
- updates.defaultAgent = agent || null;
47764
- }
47765
- if (Object.keys(updates).length === 0) {
47766
- throw new CliError("NO_UPDATES", "No updates provided. Use --display-name, --startup-script, --copy-files, or --default-agent", ExitCodes.INVALID_ARGS);
47767
- }
47768
- const repo = await client.updateRepository(id, updates);
47769
- if (isJsonOutput()) {
47770
- output(repo);
47771
- } else {
47772
- console.log(`Updated repository: ${repo.displayName}`);
47773
- }
47774
- break;
47775
- }
47776
- case "delete": {
47777
- const [id] = positional;
47778
- if (!id) {
47779
- throw new CliError("MISSING_ID", "Repository ID required", ExitCodes.INVALID_ARGS);
47780
- }
47781
- const result = await client.deleteRepository(id);
47782
- if (isJsonOutput()) {
47783
- output({ deleted: id, ...result });
47784
- } else {
47785
- console.log(`Deleted repository: ${id}`);
47786
- }
47787
- break;
47788
- }
47789
- case "link": {
47790
- const [repoId, projectId] = positional;
47791
- if (!repoId) {
47792
- throw new CliError("MISSING_REPO_ID", "Repository ID required", ExitCodes.INVALID_ARGS);
47793
- }
47794
- if (!projectId) {
47795
- throw new CliError("MISSING_PROJECT_ID", "Project ID required", ExitCodes.INVALID_ARGS);
47796
- }
47797
- const isPrimary = flags["as-primary"] === "true" || flags["as-primary"] === "";
47798
- const force = flags.force === "true" || flags.force === "";
47799
- try {
47800
- const result = await client.linkRepositoryToProject(repoId, projectId, { isPrimary, force });
47801
- if (isJsonOutput()) {
47802
- output(result);
47803
- } else {
47804
- console.log(`Linked repository ${repoId} to project ${projectId}`);
47805
- if (result.isPrimary)
47806
- console.log(" Set as primary repository");
47807
- }
47808
- } catch (err) {
47809
- if (err && typeof err === "object" && "status" in err && err.status === 409) {
47810
- const apiError = err;
47811
- throw new CliError("ALREADY_LINKED", apiError.message || "Repository already linked to another project. Use --force to move it.", ExitCodes.CONFLICT);
47812
- }
47813
- throw err;
47814
- }
47815
- break;
47816
- }
47817
- case "unlink": {
47818
- const [repoId, projectId] = positional;
47819
- if (!repoId) {
47820
- throw new CliError("MISSING_REPO_ID", "Repository ID required", ExitCodes.INVALID_ARGS);
47821
- }
47822
- if (!projectId) {
47823
- throw new CliError("MISSING_PROJECT_ID", "Project ID required", ExitCodes.INVALID_ARGS);
47824
- }
47825
- const result = await client.unlinkRepositoryFromProject(repoId, projectId);
47826
- if (isJsonOutput()) {
47827
- output(result);
47828
- } else {
47829
- console.log(`Unlinked repository ${repoId} from project ${projectId}`);
47830
- }
47831
- break;
47832
- }
47833
- default:
47834
- throw new CliError("UNKNOWN_ACTION", `Unknown action: ${action}. Valid: list, get, add, update, delete, link, unlink`, ExitCodes.INVALID_ARGS);
47835
- }
47836
- }
47837
- var repositoriesListCommand = defineCommand({
47838
- meta: { name: "list", description: "List repositories" },
47839
- args: {
47840
- ...globalArgs,
47841
- orphans: { type: "boolean", description: "Show only orphan repos" },
47842
- "project-id": { type: "string", description: "Filter by project ID" }
47843
- },
47844
- async run({ args }) {
47845
- setupJsonOutput(args);
47846
- await handleRepositoriesCommand("list", [], toFlags(args));
47847
- }
47848
- });
47849
- var repositoriesGetCommand = defineCommand({
47850
- meta: { name: "get", description: "Get repository details" },
47851
- args: {
47852
- ...globalArgs,
47853
- id: { type: "positional", description: "Repository ID", required: true }
47854
- },
47855
- async run({ args }) {
47856
- setupJsonOutput(args);
47857
- await handleRepositoriesCommand("get", [args.id], toFlags(args));
47858
- }
47859
- });
47860
- var repositoriesAddCommand = defineCommand({
47861
- meta: { name: "add", description: "Add a repository" },
47862
- args: {
47863
- ...globalArgs,
47864
- path: { type: "string", description: "Repository path", required: true },
47865
- "display-name": { type: "string", description: "Display name" }
47866
- },
47867
- async run({ args }) {
47868
- setupJsonOutput(args);
47869
- await handleRepositoriesCommand("add", [], toFlags(args));
47870
- }
47871
- });
47872
- var repositoriesUpdateCommand = defineCommand({
47873
- meta: { name: "update", description: "Update repository settings" },
47874
- args: {
47875
- ...globalArgs,
47876
- id: { type: "positional", description: "Repository ID", required: true },
47877
- "display-name": { type: "string", description: "Display name" },
47878
- "startup-script": { type: "string", description: "Startup script" },
47879
- "copy-files": { type: "string", description: "Files to copy pattern" },
47880
- "default-agent": { type: "string", description: "Default agent (claude/opencode)" }
47881
- },
47882
- async run({ args }) {
47883
- setupJsonOutput(args);
47884
- await handleRepositoriesCommand("update", [args.id], toFlags(args));
47885
- }
47886
- });
47887
- var repositoriesDeleteCommand = defineCommand({
47888
- meta: { name: "delete", description: "Delete an orphan repository" },
47889
- args: {
47890
- ...globalArgs,
47891
- id: { type: "positional", description: "Repository ID", required: true }
47892
- },
47893
- async run({ args }) {
47894
- setupJsonOutput(args);
47895
- await handleRepositoriesCommand("delete", [args.id], toFlags(args));
47896
- }
47897
- });
47898
- var repositoriesLinkCommand = defineCommand({
47899
- meta: { name: "link", description: "Link repository to project" },
47900
- args: {
47901
- ...globalArgs,
47902
- repoId: { type: "positional", description: "Repository ID", required: true },
47903
- projectId: { type: "positional", description: "Project ID", required: true },
47904
- "as-primary": { type: "boolean", description: "Set as primary repository" },
47905
- force: { type: "boolean", description: "Force unlink from current project" }
47906
- },
47907
- async run({ args }) {
47908
- setupJsonOutput(args);
47909
- await handleRepositoriesCommand("link", [args.repoId, args.projectId], toFlags(args));
47910
- }
47911
- });
47912
- var repositoriesUnlinkCommand = defineCommand({
47913
- meta: { name: "unlink", description: "Unlink repository from project" },
47914
- args: {
47915
- ...globalArgs,
47916
- repoId: { type: "positional", description: "Repository ID", required: true },
47917
- projectId: { type: "positional", description: "Project ID", required: true }
47918
- },
47919
- async run({ args }) {
47920
- setupJsonOutput(args);
47921
- await handleRepositoriesCommand("unlink", [args.repoId, args.projectId], toFlags(args));
47922
- }
47923
- });
47924
- var repositoriesCommand = defineCommand({
47925
- meta: { name: "repositories", description: "Manage repositories" },
47926
- subCommands: {
47927
- list: repositoriesListCommand,
47928
- get: repositoriesGetCommand,
47929
- add: repositoriesAddCommand,
47930
- update: repositoriesUpdateCommand,
47931
- delete: repositoriesDeleteCommand,
47932
- link: repositoriesLinkCommand,
47933
- unlink: repositoriesUnlinkCommand
47934
- }
47935
- });
47936
-
47937
- // cli/src/commands/apps.ts
47938
- init_client();
47939
- init_errors();
47940
- var VALID_STATUSES3 = ["stopped", "building", "running", "failed"];
47941
- function formatApp(app) {
47942
- console.log(`${app.name}`);
47943
- console.log(` ID: ${app.id}`);
47944
- console.log(` Status: ${app.status}`);
47945
- console.log(` Branch: ${app.branch}`);
47946
- console.log(` Compose: ${app.composeFile}`);
47947
- console.log(` Auto-deploy: ${app.autoDeployEnabled ? "enabled" : "disabled"}`);
47948
- if (app.lastDeployedAt) {
47949
- console.log(` Last deploy: ${new Date(app.lastDeployedAt).toLocaleString()}`);
47950
- }
47951
- if (app.repository) {
47952
- console.log(` Repository: ${app.repository.displayName} (${app.repository.path})`);
47953
- }
47954
- if (app.services && app.services.length > 0) {
47955
- console.log(` Services:`);
47956
- for (const svc of app.services) {
47957
- const exposed = svc.exposed ? `\u2192 ${svc.domain || "exposed"}` : "internal";
47958
- const port = svc.containerPort ? `:${svc.containerPort}` : "";
47959
- console.log(` - ${svc.serviceName}${port} (${exposed})`);
47960
- }
47961
- }
47962
- }
47963
- function formatAppList(apps) {
47964
- if (apps.length === 0) {
47965
- console.log("No apps found");
47966
- return;
47967
- }
47968
- const byStatus = {
47969
- running: apps.filter((a2) => a2.status === "running"),
47970
- building: apps.filter((a2) => a2.status === "building"),
47971
- stopped: apps.filter((a2) => a2.status === "stopped"),
47972
- failed: apps.filter((a2) => a2.status === "failed")
47973
- };
47974
- for (const [status, statusApps] of Object.entries(byStatus)) {
47975
- if (statusApps.length === 0)
47976
- continue;
47977
- console.log(`
47978
- ${status.toUpperCase()} (${statusApps.length})`);
47979
- for (const app of statusApps) {
47980
- const repoName = app.repository?.displayName || "unknown";
47981
- console.log(` ${app.name} (${repoName})`);
47982
- console.log(` ${app.id} \xB7 ${app.branch}`);
47983
- }
47984
- }
47985
- }
47986
- function formatDeploymentList(deployments) {
47987
- if (deployments.length === 0) {
47988
- console.log("No deployments found");
47989
- return;
47990
- }
47991
- console.log(`
47992
- Deployment History (${deployments.length} total):`);
47993
- for (const d2 of deployments.slice(0, 10)) {
47994
- const date = new Date(d2.startedAt).toLocaleString();
47995
- const commit = d2.gitCommit ? d2.gitCommit.substring(0, 7) : "unknown";
47996
- const message = d2.gitMessage ? ` - ${d2.gitMessage.substring(0, 50)}` : "";
47997
- console.log(` [${d2.status}] ${date} (${commit})${message}`);
47998
- if (d2.errorMessage)
47999
- console.log(` Error: ${d2.errorMessage}`);
48000
- }
48001
- if (deployments.length > 10) {
48002
- console.log(` ... and ${deployments.length - 10} more`);
48003
- }
48004
- }
48005
- async function handleAppsCommand(action, positional, flags) {
48006
- const client = new FulcrumClient(flags.url, flags.port);
48007
- switch (action) {
48008
- case "list": {
48009
- let statusFilter;
48010
- if (flags.status) {
48011
- const status = flags.status.toLowerCase();
48012
- if (!VALID_STATUSES3.includes(status)) {
48013
- throw new CliError("INVALID_STATUS", `Invalid status: ${flags.status}. Valid: ${VALID_STATUSES3.join(", ")}`, ExitCodes.INVALID_ARGS);
48014
- }
48015
- statusFilter = status;
48016
- }
48017
- let apps = await client.listApps();
48018
- if (statusFilter) {
48019
- apps = apps.filter((a2) => a2.status === statusFilter);
48020
- }
48021
- if (isJsonOutput()) {
48022
- output(apps);
48023
- } else {
48024
- formatAppList(apps);
48025
- }
48026
- break;
48027
- }
48028
- case "get": {
48029
- const [id] = positional;
48030
- if (!id) {
48031
- throw new CliError("MISSING_ID", "App ID required", ExitCodes.INVALID_ARGS);
48032
- }
48033
- const app = await client.getApp(id);
48034
- if (isJsonOutput()) {
48035
- output(app);
48036
- } else {
48037
- formatApp(app);
48038
- }
48039
- break;
48040
- }
48041
- case "create": {
48042
- const name = flags.name;
48043
- const repositoryId = flags["repository-id"] || flags["repo-id"];
48044
- if (!name) {
48045
- throw new CliError("MISSING_NAME", "--name is required", ExitCodes.INVALID_ARGS);
48046
- }
48047
- if (!repositoryId) {
48048
- throw new CliError("MISSING_REPO_ID", "--repository-id is required", ExitCodes.INVALID_ARGS);
48049
- }
48050
- const app = await client.createApp({
48051
- name,
48052
- repositoryId,
48053
- branch: flags.branch,
48054
- composeFile: flags["compose-file"],
48055
- autoDeployEnabled: flags["auto-deploy"] === "true",
48056
- noCacheBuild: flags["no-cache"] === "true"
48057
- });
48058
- if (isJsonOutput()) {
48059
- output(app);
48060
- } else {
48061
- console.log(`Created app: ${app.name}`);
48062
- console.log(` ID: ${app.id}`);
48063
- console.log(` Status: ${app.status}`);
48064
- }
48065
- break;
48066
- }
48067
- case "update": {
48068
- const [id] = positional;
48069
- if (!id) {
48070
- throw new CliError("MISSING_ID", "App ID required", ExitCodes.INVALID_ARGS);
48071
- }
48072
- const updates = {};
48073
- if (flags.name !== undefined)
48074
- updates.name = flags.name;
48075
- if (flags.branch !== undefined)
48076
- updates.branch = flags.branch;
48077
- if (flags["auto-deploy"] !== undefined) {
48078
- updates.autoDeployEnabled = flags["auto-deploy"] === "true";
48079
- }
48080
- if (flags["no-cache"] !== undefined) {
48081
- updates.noCacheBuild = flags["no-cache"] === "true";
48082
- }
48083
- if (flags.notifications !== undefined) {
48084
- updates.notificationsEnabled = flags.notifications === "true";
48085
- }
48086
- if (Object.keys(updates).length === 0) {
48087
- throw new CliError("NO_UPDATES", "No updates provided. Use --name, --branch, --auto-deploy, --no-cache, or --notifications", ExitCodes.INVALID_ARGS);
48088
- }
48089
- const app = await client.updateApp(id, updates);
48090
- if (isJsonOutput()) {
48091
- output(app);
48092
- } else {
48093
- console.log(`Updated app: ${app.name}`);
48094
- }
48095
- break;
48096
- }
48097
- case "delete": {
48098
- const [id] = positional;
48099
- if (!id) {
48100
- throw new CliError("MISSING_ID", "App ID required", ExitCodes.INVALID_ARGS);
48101
- }
48102
- const keepContainers = flags["keep-containers"] === "true" || flags["keep-containers"] === "";
48103
- await client.deleteApp(id, !keepContainers);
48104
- if (isJsonOutput()) {
48105
- output({ deleted: id });
48106
- } else {
48107
- console.log(`Deleted app: ${id}`);
48108
- }
48109
- break;
48110
- }
48111
- case "deploy": {
48112
- const [id] = positional;
48113
- if (!id) {
48114
- throw new CliError("MISSING_ID", "App ID required", ExitCodes.INVALID_ARGS);
48115
- }
48116
- const result = await client.deployApp(id);
48117
- if (isJsonOutput()) {
48118
- output(result);
48119
- } else {
48120
- if (result.success) {
48121
- console.log(`Deployment started for app: ${id}`);
48122
- if (result.deployment) {
48123
- console.log(` Deployment ID: ${result.deployment.id}`);
48124
- }
48125
- } else {
48126
- console.log(`Deployment failed: ${result.error}`);
48127
- }
48128
- }
48129
- break;
48130
- }
48131
- case "stop": {
48132
- const [id] = positional;
48133
- if (!id) {
48134
- throw new CliError("MISSING_ID", "App ID required", ExitCodes.INVALID_ARGS);
48135
- }
48136
- const result = await client.stopApp(id);
48137
- if (isJsonOutput()) {
48138
- output(result);
48139
- } else {
48140
- if (result.success) {
48141
- console.log(`Stopped app: ${id}`);
48142
- } else {
48143
- console.log(`Failed to stop app: ${result.error}`);
48144
- }
48145
- }
48146
- break;
48147
- }
48148
- case "logs": {
48149
- const [id] = positional;
48150
- if (!id) {
48151
- throw new CliError("MISSING_ID", "App ID required", ExitCodes.INVALID_ARGS);
48152
- }
48153
- const tail = flags.tail ? parseInt(flags.tail, 10) : undefined;
48154
- const result = await client.getAppLogs(id, {
48155
- service: flags.service,
48156
- tail
48157
- });
48158
- if (isJsonOutput()) {
48159
- output(result);
48160
- } else {
48161
- console.log(result.logs);
48162
- }
48163
- break;
48164
- }
48165
- case "status": {
48166
- const [id] = positional;
48167
- if (!id) {
48168
- throw new CliError("MISSING_ID", "App ID required", ExitCodes.INVALID_ARGS);
48169
- }
48170
- const status = await client.getAppStatus(id);
48171
- if (isJsonOutput()) {
48172
- output(status);
48173
- } else {
48174
- if (status.containers.length === 0) {
48175
- console.log("No containers running");
48176
- } else {
48177
- console.log("Containers:");
48178
- for (const c3 of status.containers) {
48179
- const ports = c3.ports.length > 0 ? ` (${c3.ports.join(", ")})` : "";
48180
- console.log(` ${c3.service}: ${c3.status} [${c3.replicas}]${ports}`);
48181
- }
48182
- }
48183
- }
48184
- break;
48185
- }
48186
- case "deployments": {
48187
- const [id] = positional;
48188
- if (!id) {
48189
- throw new CliError("MISSING_ID", "App ID required", ExitCodes.INVALID_ARGS);
48190
- }
48191
- const deployments = await client.listDeployments(id);
48192
- if (isJsonOutput()) {
48193
- output(deployments);
48194
- } else {
48195
- formatDeploymentList(deployments);
48196
- }
48197
- break;
48198
- }
48199
- default:
48200
- throw new CliError("UNKNOWN_ACTION", `Unknown action: ${action}. Valid: list, get, create, update, delete, deploy, stop, logs, status, deployments`, ExitCodes.INVALID_ARGS);
48201
- }
48202
- }
48203
- var appsListCommand = defineCommand({
48204
- meta: { name: "list", description: "List apps" },
48205
- args: {
48206
- ...globalArgs,
48207
- status: { type: "string", description: "Filter by status" }
48208
- },
48209
- async run({ args }) {
48210
- setupJsonOutput(args);
48211
- await handleAppsCommand("list", [], toFlags(args));
48212
- }
48213
- });
48214
- var appsGetCommand = defineCommand({
48215
- meta: { name: "get", description: "Get app details" },
48216
- args: {
48217
- ...globalArgs,
48218
- id: { type: "positional", description: "App ID", required: true }
48219
- },
48220
- async run({ args }) {
48221
- setupJsonOutput(args);
48222
- await handleAppsCommand("get", [args.id], toFlags(args));
48223
- }
48224
- });
48225
- var appsCreateCommand = defineCommand({
48226
- meta: { name: "create", description: "Create an app" },
48227
- args: {
48228
- ...globalArgs,
48229
- name: { type: "string", description: "App name", required: true },
48230
- "repository-id": { type: "string", description: "Repository ID", required: true },
48231
- branch: { type: "string", description: "Branch to deploy" },
48232
- "compose-file": { type: "string", description: "Docker Compose file path" },
48233
- "auto-deploy": { type: "boolean", description: "Enable auto-deploy" },
48234
- "no-cache": { type: "boolean", description: "Build without cache" }
48235
- },
48236
- async run({ args }) {
48237
- setupJsonOutput(args);
48238
- await handleAppsCommand("create", [], toFlags(args));
48239
- }
48240
- });
48241
- var appsDeleteCommand = defineCommand({
48242
- meta: { name: "delete", description: "Delete an app" },
48243
- args: {
48244
- ...globalArgs,
48245
- id: { type: "positional", description: "App ID", required: true },
48246
- "keep-containers": { type: "boolean", description: "Keep running containers" }
48247
- },
48248
- async run({ args }) {
48249
- setupJsonOutput(args);
48250
- await handleAppsCommand("delete", [args.id], toFlags(args));
48251
- }
48252
- });
48253
- var appsDeployCommand = defineCommand({
48254
- meta: { name: "deploy", description: "Deploy an app" },
48255
- args: {
48256
- ...globalArgs,
48257
- id: { type: "positional", description: "App ID", required: true }
48258
- },
48259
- async run({ args }) {
48260
- setupJsonOutput(args);
48261
- await handleAppsCommand("deploy", [args.id], toFlags(args));
48262
- }
48263
- });
48264
- var appsStopCommand = defineCommand({
48265
- meta: { name: "stop", description: "Stop an app" },
48266
- args: {
48267
- ...globalArgs,
48268
- id: { type: "positional", description: "App ID", required: true }
48269
- },
48270
- async run({ args }) {
48271
- setupJsonOutput(args);
48272
- await handleAppsCommand("stop", [args.id], toFlags(args));
48273
- }
48274
- });
48275
- var appsLogsCommand = defineCommand({
48276
- meta: { name: "logs", description: "Get app logs" },
48277
- args: {
48278
- ...globalArgs,
48279
- id: { type: "positional", description: "App ID", required: true },
48280
- service: { type: "string", description: "Specific service" },
48281
- tail: { type: "string", description: "Number of lines" }
48282
- },
48283
- async run({ args }) {
48284
- setupJsonOutput(args);
48285
- await handleAppsCommand("logs", [args.id], toFlags(args));
48286
- }
48287
- });
48288
- var appsStatusCommand = defineCommand({
48289
- meta: { name: "status", description: "Get app container status" },
48290
- args: {
48291
- ...globalArgs,
48292
- id: { type: "positional", description: "App ID", required: true }
48293
- },
48294
- async run({ args }) {
48295
- setupJsonOutput(args);
48296
- await handleAppsCommand("status", [args.id], toFlags(args));
48297
- }
48298
- });
48299
- var appsDeploymentsCommand = defineCommand({
48300
- meta: { name: "deployments", description: "List deployment history" },
48301
- args: {
48302
- ...globalArgs,
48303
- id: { type: "positional", description: "App ID", required: true }
48304
- },
48305
- async run({ args }) {
48306
- setupJsonOutput(args);
48307
- await handleAppsCommand("deployments", [args.id], toFlags(args));
48308
- }
48309
- });
48310
- var appsCommand = defineCommand({
48311
- meta: { name: "apps", description: "Manage apps" },
48312
- subCommands: {
48313
- list: appsListCommand,
48314
- get: appsGetCommand,
48315
- create: appsCreateCommand,
48316
- delete: appsDeleteCommand,
48317
- deploy: appsDeployCommand,
48318
- stop: appsStopCommand,
48319
- logs: appsLogsCommand,
48320
- status: appsStatusCommand,
48321
- deployments: appsDeploymentsCommand
48322
- }
48323
- });
48324
-
48325
- // cli/src/commands/fs.ts
48326
- init_client();
48327
- init_errors();
48328
- function formatDirectoryEntry(entry) {
48329
- const icon = entry.type === "directory" ? entry.isGitRepo ? "\uD83D\uDCC1" : "\uD83D\uDCC2" : "\uD83D\uDCC4";
48330
- const gitIndicator = entry.isGitRepo ? " [git]" : "";
48331
- return `${icon} ${entry.name}${gitIndicator}`;
48332
- }
48333
- function formatTree(entries, indent = "") {
48334
- for (let i2 = 0;i2 < entries.length; i2++) {
48335
- const entry = entries[i2];
48336
- const isLast = i2 === entries.length - 1;
48337
- const prefix = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
48338
- const childIndent = indent + (isLast ? " " : "\u2502 ");
48339
- const icon = entry.type === "directory" ? "\uD83D\uDCC1" : "\uD83D\uDCC4";
48340
- console.log(`${indent}${prefix}${icon} ${entry.name}`);
48341
- if (entry.children && entry.children.length > 0) {
48342
- formatTree(entry.children, childIndent);
48343
- }
48344
- }
48345
- }
48346
- async function handleFsCommand(action, positional, flags) {
48347
- const client = new FulcrumClient(flags.url, flags.port);
48348
- switch (action) {
48349
- case "list": {
48350
- const path = flags.path || positional[0];
48351
- const result = await client.listDirectory(path);
48352
- if (isJsonOutput()) {
48353
- output(result);
48354
- } else {
48355
- console.log(`Directory: ${result.path}`);
48356
- console.log(`Parent: ${result.parent}`);
48357
- console.log("");
48358
- if (result.entries.length === 0) {
48359
- console.log("(empty directory)");
48360
- } else {
48361
- for (const entry of result.entries) {
48362
- console.log(formatDirectoryEntry(entry));
48363
- }
48364
- }
48365
- }
48366
- break;
48367
- }
48368
- case "tree": {
48369
- const root = flags.root || positional[0];
48370
- if (!root) {
48371
- throw new CliError("MISSING_ROOT", "--root is required", ExitCodes.INVALID_ARGS);
48372
- }
48373
- const result = await client.getFileTree(root);
48374
- if (isJsonOutput()) {
48375
- output(result);
48376
- } else {
48377
- console.log(`\uD83D\uDCC1 ${result.root}`);
48378
- formatTree(result.entries);
48379
- }
48380
- break;
48381
- }
48382
- case "read": {
48383
- const path = flags.path || positional[0];
48384
- const root = flags.root;
48385
- if (!path) {
48386
- throw new CliError("MISSING_PATH", "--path is required", ExitCodes.INVALID_ARGS);
48387
- }
48388
- if (!root) {
48389
- throw new CliError("MISSING_ROOT", "--root is required", ExitCodes.INVALID_ARGS);
48390
- }
48391
- const maxLines = flags["max-lines"] ? parseInt(flags["max-lines"], 10) : undefined;
48392
- const result = await client.readFile(path, root, maxLines);
48393
- if (isJsonOutput()) {
48394
- output(result);
48395
- } else {
48396
- if (result.truncated) {
48397
- console.log(`[Showing ${flags["max-lines"] || 5000} of ${result.lineCount} lines]
48398
- `);
48399
- }
48400
- console.log(result.content);
48401
- console.log("");
48402
- console.log(`--- ${result.mimeType} \xB7 ${result.size} bytes \xB7 ${result.lineCount} lines ---`);
48403
- }
48404
- break;
48405
- }
48406
- case "write": {
48407
- const path = flags.path || positional[0];
48408
- const root = flags.root;
48409
- const content = flags.content;
48410
- if (!path) {
48411
- throw new CliError("MISSING_PATH", "--path is required", ExitCodes.INVALID_ARGS);
48412
- }
48413
- if (!root) {
48414
- throw new CliError("MISSING_ROOT", "--root is required", ExitCodes.INVALID_ARGS);
48415
- }
48416
- if (content === undefined) {
48417
- throw new CliError("MISSING_CONTENT", "--content is required", ExitCodes.INVALID_ARGS);
48418
- }
48419
- const result = await client.writeFile({ path, root, content });
48420
- if (isJsonOutput()) {
48421
- output(result);
48422
- } else {
48423
- console.log(`Written: ${path}`);
48424
- console.log(` Size: ${result.size} bytes`);
48425
- console.log(` Modified: ${result.mtime}`);
48426
- }
48427
- break;
48428
- }
48429
- case "stat": {
48430
- const path = flags.path || positional[0];
48431
- if (!path) {
48432
- throw new CliError("MISSING_PATH", "--path is required", ExitCodes.INVALID_ARGS);
48433
- }
48434
- const result = await client.getPathStat(path);
48435
- if (isJsonOutput()) {
48436
- output(result);
48437
- } else {
48438
- console.log(`Path: ${result.path}`);
48439
- console.log(`Exists: ${result.exists}`);
48440
- console.log(`Type: ${result.type || "N/A"}`);
48441
- }
48442
- break;
48443
- }
48444
- case "is-git-repo": {
48445
- const path = flags.path || positional[0];
48446
- if (!path) {
48447
- throw new CliError("MISSING_PATH", "--path is required", ExitCodes.INVALID_ARGS);
48448
- }
48449
- const result = await client.isGitRepo(path);
48450
- if (isJsonOutput()) {
48451
- output(result);
48452
- } else {
48453
- console.log(`Path: ${result.path}`);
48454
- console.log(`Git repo: ${result.isGitRepo ? "yes" : "no"}`);
48455
- }
48456
- break;
48457
- }
48458
- case "edit": {
48459
- const path = flags.path || positional[0];
48460
- const root = flags.root;
48461
- const old_string = flags["old-string"];
48462
- const new_string = flags["new-string"];
48463
- if (!path) {
48464
- throw new CliError("MISSING_PATH", "--path is required", ExitCodes.INVALID_ARGS);
48465
- }
48466
- if (!root) {
48467
- throw new CliError("MISSING_ROOT", "--root is required", ExitCodes.INVALID_ARGS);
48468
- }
48469
- if (old_string === undefined) {
48470
- throw new CliError("MISSING_OLD_STRING", "--old-string is required", ExitCodes.INVALID_ARGS);
48471
- }
48472
- if (new_string === undefined) {
48473
- throw new CliError("MISSING_NEW_STRING", "--new-string is required", ExitCodes.INVALID_ARGS);
48474
- }
48475
- const result = await client.editFile({ path, root, old_string, new_string });
48476
- if (isJsonOutput()) {
48477
- output(result);
48478
- } else {
48479
- console.log(`Edited: ${path}`);
48480
- console.log(` Size: ${result.size} bytes`);
48481
- console.log(` Modified: ${result.mtime}`);
48482
- }
48483
- break;
48484
- }
48485
- default:
48486
- throw new CliError("UNKNOWN_ACTION", `Unknown action: ${action}. Valid: list, tree, read, write, edit, stat, is-git-repo`, ExitCodes.INVALID_ARGS);
48487
- }
48488
- }
48489
- var fsListCommand = defineCommand({
48490
- meta: { name: "list", description: "List directory contents" },
48491
- args: {
48492
- ...globalArgs,
48493
- path: { type: "positional", description: "Directory path" }
48494
- },
48495
- async run({ args }) {
48496
- setupJsonOutput(args);
48497
- await handleFsCommand("list", args.path ? [args.path] : [], toFlags(args));
48498
- }
48499
- });
48500
- var fsTreeCommand = defineCommand({
48501
- meta: { name: "tree", description: "Get file tree" },
48502
- args: {
48503
- ...globalArgs,
48504
- root: { type: "positional", description: "Root directory path", required: true }
48505
- },
48506
- async run({ args }) {
48507
- setupJsonOutput(args);
48508
- await handleFsCommand("tree", args.root ? [args.root] : [], toFlags(args));
48509
- }
48510
- });
48511
- var fsReadCommand = defineCommand({
48512
- meta: { name: "read", description: "Read file contents" },
48513
- args: {
48514
- ...globalArgs,
48515
- path: { type: "string", description: "File path", required: true },
48516
- root: { type: "string", description: "Root directory", required: true },
48517
- "max-lines": { type: "string", description: "Maximum lines to read" }
48518
- },
48519
- async run({ args }) {
48520
- setupJsonOutput(args);
48521
- await handleFsCommand("read", [], toFlags(args));
48522
- }
48523
- });
48524
- var fsWriteCommand = defineCommand({
48525
- meta: { name: "write", description: "Write file contents" },
48526
- args: {
48527
- ...globalArgs,
48528
- path: { type: "string", description: "File path", required: true },
48529
- root: { type: "string", description: "Root directory", required: true },
48530
- content: { type: "string", description: "Content to write", required: true }
48531
- },
48532
- async run({ args }) {
48533
- setupJsonOutput(args);
48534
- await handleFsCommand("write", [], toFlags(args));
48535
- }
48536
- });
48537
- var fsEditCommand = defineCommand({
48538
- meta: { name: "edit", description: "Edit file by string replacement" },
48539
- args: {
48540
- ...globalArgs,
48541
- path: { type: "string", description: "File path", required: true },
48542
- root: { type: "string", description: "Root directory", required: true },
48543
- "old-string": { type: "string", description: "String to replace", required: true },
48544
- "new-string": { type: "string", description: "Replacement string", required: true }
48545
- },
48546
- async run({ args }) {
48547
- setupJsonOutput(args);
48548
- await handleFsCommand("edit", [], toFlags(args));
48549
- }
48550
- });
48551
- var fsStatCommand = defineCommand({
48552
- meta: { name: "stat", description: "Get file/directory metadata" },
48553
- args: {
48554
- ...globalArgs,
48555
- path: { type: "positional", description: "Path to check", required: true }
48556
- },
48557
- async run({ args }) {
48558
- setupJsonOutput(args);
48559
- await handleFsCommand("stat", args.path ? [args.path] : [], toFlags(args));
48560
- }
48561
- });
48562
- var fsIsGitRepoCommand = defineCommand({
48563
- meta: { name: "is-git-repo", description: "Check if path is a git repo" },
48564
- args: {
48565
- ...globalArgs,
48566
- path: { type: "positional", description: "Path to check", required: true }
48567
- },
48568
- async run({ args }) {
48569
- setupJsonOutput(args);
48570
- await handleFsCommand("is-git-repo", args.path ? [args.path] : [], toFlags(args));
48571
- }
48572
- });
48573
- var fsCommand = defineCommand({
48574
- meta: { name: "fs", description: "Filesystem operations" },
48575
- subCommands: {
48576
- list: fsListCommand,
48577
- tree: fsTreeCommand,
48578
- read: fsReadCommand,
48579
- write: fsWriteCommand,
48580
- edit: fsEditCommand,
48581
- stat: fsStatCommand,
48582
- "is-git-repo": fsIsGitRepoCommand
48583
- }
48584
- });
48585
-
48586
- // cli/src/commands/git.ts
48587
- init_client();
48588
- init_errors();
48589
- async function handleGitCommand(action, flags) {
48590
- const client = new FulcrumClient(flags.url, flags.port);
48591
- switch (action) {
48592
- case "status": {
48593
- const path = flags.path || process.cwd();
48594
- const status = await client.getStatus(path);
48595
- if (isJsonOutput()) {
48596
- output(status);
48597
- } else {
48598
- console.log(`Branch: ${status.branch}`);
48599
- if (status.ahead)
48600
- console.log(` Ahead: ${status.ahead}`);
48601
- if (status.behind)
48602
- console.log(` Behind: ${status.behind}`);
48603
- if (status.staged?.length)
48604
- console.log(` Staged: ${status.staged.length} files`);
48605
- if (status.modified?.length)
48606
- console.log(` Modified: ${status.modified.length} files`);
48607
- if (status.untracked?.length)
48608
- console.log(` Untracked: ${status.untracked.length} files`);
48609
- if (!status.staged?.length && !status.modified?.length && !status.untracked?.length) {
48610
- console.log(" Working tree clean");
48611
- }
48612
- }
48613
- break;
48614
- }
48615
- case "diff": {
48616
- const path = flags.path || process.cwd();
48617
- const diff = await client.getDiff(path, {
48618
- staged: flags.staged === "true",
48619
- ignoreWhitespace: flags["ignore-whitespace"] === "true",
48620
- includeUntracked: flags["include-untracked"] === "true"
48621
- });
48622
- if (isJsonOutput()) {
48623
- output(diff);
48624
- } else {
48625
- console.log(diff.diff || "No changes");
48626
- }
48627
- break;
48628
- }
48629
- case "branches": {
48630
- const repo = flags.repo;
48631
- if (!repo) {
48632
- throw new CliError("MISSING_REPO", "--repo is required", ExitCodes.INVALID_ARGS);
48633
- }
48634
- const branches = await client.getBranches(repo);
48635
- if (isJsonOutput()) {
48636
- output(branches);
48637
- } else {
48638
- for (const branch of branches) {
48639
- const current = branch.current ? "* " : " ";
48640
- console.log(`${current}${branch.name}`);
48641
- }
48642
- }
48643
- break;
48644
- }
48645
- default:
48646
- throw new CliError("UNKNOWN_ACTION", `Unknown action: ${action}. Valid: status, diff, branches`, ExitCodes.INVALID_ARGS);
48647
- }
48648
- }
48649
- var gitStatusCommand = defineCommand({
48650
- meta: { name: "status", description: "Get git status for a worktree" },
48651
- args: {
48652
- ...globalArgs,
48653
- path: { type: "string", description: "Repository path (default: current directory)" }
48654
- },
48655
- async run({ args }) {
48656
- setupJsonOutput(args);
48657
- await handleGitCommand("status", toFlags(args));
48658
- }
48659
- });
48660
- var gitDiffCommand = defineCommand({
48661
- meta: { name: "diff", description: "Get git diff for a worktree" },
48662
- args: {
48663
- ...globalArgs,
48664
- path: { type: "string", description: "Repository path (default: current directory)" },
48665
- staged: { type: "boolean", description: "Show staged changes only" },
48666
- "ignore-whitespace": { type: "boolean", description: "Ignore whitespace changes" },
48667
- "include-untracked": { type: "boolean", description: "Include untracked files" }
48668
- },
48669
- async run({ args }) {
48670
- setupJsonOutput(args);
48671
- await handleGitCommand("diff", toFlags(args));
48672
- }
48673
- });
48674
- var gitBranchesCommand = defineCommand({
48675
- meta: { name: "branches", description: "List branches in a repository" },
48676
- args: {
48677
- ...globalArgs,
48678
- repo: { type: "string", description: "Repository path", required: true }
48679
- },
48680
- async run({ args }) {
48681
- setupJsonOutput(args);
48682
- await handleGitCommand("branches", toFlags(args));
48683
- }
48684
- });
48685
- var gitCommand = defineCommand({
48686
- meta: { name: "git", description: "Git operations" },
48687
- subCommands: {
48688
- status: gitStatusCommand,
48689
- diff: gitDiffCommand,
48690
- branches: gitBranchesCommand
48691
- }
48692
- });
48693
-
48694
- // cli/src/commands/worktrees.ts
48695
- init_client();
48696
- init_errors();
48697
- async function handleWorktreesCommand(action, flags) {
48698
- const client = new FulcrumClient(flags.url, flags.port);
48699
- switch (action) {
48700
- case "list": {
48701
- const worktrees = await client.listWorktrees();
48702
- if (isJsonOutput()) {
48703
- output(worktrees);
48704
- } else {
48705
- if (worktrees.length === 0) {
48706
- console.log("No worktrees found");
48707
- } else {
48708
- for (const wt of worktrees) {
48709
- console.log(`${wt.path}`);
48710
- console.log(` Branch: ${wt.branch}`);
48711
- if (wt.taskId)
48712
- console.log(` Task: ${wt.taskId}`);
48713
- }
48714
- }
48715
- }
48716
- break;
48717
- }
48718
- case "delete": {
48719
- const worktreePath = flags.path;
48720
- if (!worktreePath) {
48721
- throw new CliError("MISSING_PATH", "--path is required", ExitCodes.INVALID_ARGS);
48722
- }
48723
- const deleteLinkedTask = flags["delete-task"] === "true" || flags["delete-task"] === "";
48724
- const result = await client.deleteWorktree(worktreePath, flags.repo, deleteLinkedTask);
48725
- if (isJsonOutput()) {
48726
- output(result);
48727
- } else {
48728
- console.log(`Deleted worktree: ${worktreePath}`);
48729
- }
48730
- break;
48731
- }
48732
- default:
48733
- throw new CliError("UNKNOWN_ACTION", `Unknown action: ${action}. Valid: list, delete`, ExitCodes.INVALID_ARGS);
48734
- }
48735
- }
48736
- var worktreesListCommand = defineCommand({
48737
- meta: { name: "list", description: "List all worktrees" },
48738
- args: {
48739
- ...globalArgs,
48740
- repo: { type: "string", description: "Repository path" }
48741
- },
48742
- async run({ args }) {
48743
- setupJsonOutput(args);
48744
- await handleWorktreesCommand("list", toFlags(args));
48745
- }
48746
- });
48747
- var worktreesDeleteCommand = defineCommand({
48748
- meta: { name: "delete", description: "Delete a worktree" },
48749
- args: {
48750
- ...globalArgs,
48751
- path: { type: "string", description: "Worktree path", required: true },
48752
- force: { type: "boolean", description: "Force deletion" }
48753
- },
48754
- async run({ args }) {
48755
- setupJsonOutput(args);
48756
- await handleWorktreesCommand("delete", toFlags(args));
48757
- }
48758
- });
48759
- var worktreesCommand = defineCommand({
48760
- meta: { name: "worktrees", description: "Manage git worktrees" },
48761
- subCommands: {
48762
- list: worktreesListCommand,
48763
- delete: worktreesDeleteCommand
48764
- }
48765
- });
48766
-
48767
- // cli/src/commands/config.ts
48768
- init_client();
48769
- init_errors();
48770
- async function handleConfigCommand(action, positional, flags) {
48771
- const client = new FulcrumClient(flags.url, flags.port);
48772
- switch (action) {
48773
- case "list": {
48774
- const config = await client.getAllConfig();
48775
- if (isJsonOutput()) {
48776
- output(config);
48777
- } else {
48778
- console.log("Configuration:");
48779
- for (const [key, value] of Object.entries(config)) {
48780
- const displayValue = value === null ? "(not set)" : value;
48781
- console.log(` ${key}: ${displayValue}`);
48782
- }
48783
- }
48784
- break;
48785
- }
48786
- case "get": {
48787
- const [key] = positional;
48788
- if (!key) {
48789
- throw new CliError("MISSING_KEY", "Config key is required", ExitCodes.INVALID_ARGS);
48790
- }
48791
- const config = await client.getConfig(key);
48792
- if (isJsonOutput()) {
48793
- output(config);
48794
- } else {
48795
- const value = config.value === null ? "(not set)" : config.value;
48796
- console.log(`${key}: ${value}`);
48797
- }
48798
- break;
48799
- }
48800
- case "set": {
48801
- const [key, value] = positional;
48802
- if (!key) {
48803
- throw new CliError("MISSING_KEY", "Config key is required", ExitCodes.INVALID_ARGS);
48804
- }
48805
- if (value === undefined) {
48806
- throw new CliError("MISSING_VALUE", "Config value is required", ExitCodes.INVALID_ARGS);
48807
- }
48808
- const parsedValue = /^\d+$/.test(value) ? parseInt(value, 10) : value;
48809
- const config = await client.setConfig(key, parsedValue);
48810
- if (isJsonOutput()) {
48811
- output(config);
48812
- } else {
48813
- console.log(`Set ${key} = ${config.value}`);
48814
- }
48815
- break;
48816
- }
48817
- case "reset": {
48818
- const [key] = positional;
48819
- if (!key) {
48820
- throw new CliError("MISSING_KEY", "Config key is required", ExitCodes.INVALID_ARGS);
48821
- }
48822
- const config = await client.resetConfig(key);
48823
- if (isJsonOutput()) {
48824
- output(config);
48825
- } else {
48826
- console.log(`Reset ${key} to default: ${config.value}`);
48827
- }
48828
- break;
48829
- }
48830
- default:
48831
- throw new CliError("UNKNOWN_ACTION", `Unknown action: ${action}. Valid: list, get, set, reset`, ExitCodes.INVALID_ARGS);
48832
- }
48833
- }
48834
- var configListCommand = defineCommand({
48835
- meta: { name: "list", description: "List all config values" },
48836
- args: globalArgs,
48837
- async run({ args }) {
48838
- setupJsonOutput(args);
48839
- await handleConfigCommand("list", [], toFlags(args));
48840
- }
48841
- });
48842
- var configGetCommand = defineCommand({
48843
- meta: { name: "get", description: "Get a config value" },
48844
- args: {
48845
- ...globalArgs,
48846
- key: { type: "positional", description: "Config key", required: true }
48847
- },
48848
- async run({ args }) {
48849
- setupJsonOutput(args);
48850
- await handleConfigCommand("get", [args.key], toFlags(args));
48851
- }
48852
- });
48853
- var configSetCommand = defineCommand({
48854
- meta: { name: "set", description: "Set a config value" },
48855
- args: {
48856
- ...globalArgs,
48857
- key: { type: "positional", description: "Config key", required: true },
48858
- value: { type: "positional", description: "Config value", required: true }
48859
- },
48860
- async run({ args }) {
48861
- setupJsonOutput(args);
48862
- await handleConfigCommand("set", [args.key, args.value], toFlags(args));
46573
+ await handleConfigCommand("set", [args.key, args.value], toFlags(args));
48863
46574
  }
48864
46575
  });
48865
46576
  var configResetCommand = defineCommand({
@@ -49371,7 +47082,7 @@ var marketplace_default = `{
49371
47082
  "name": "fulcrum",
49372
47083
  "source": "./",
49373
47084
  "description": "Task orchestration for Claude Code",
49374
- "version": "1.6.1",
47085
+ "version": "1.7.1",
49375
47086
  "skills": [
49376
47087
  "./skills/fulcrum"
49377
47088
  ],
@@ -49481,9 +47192,13 @@ Use the Fulcrum CLI when:
49481
47192
  - **Linking PRs** - Associate a GitHub PR with the current task
49482
47193
  - **Linking URLs** - Attach any relevant URLs (design docs, specs, external resources) to the task
49483
47194
  - **Sending notifications** - Alert the user when work is complete or needs attention
49484
- - **Managing projects and repositories** - Create, update, and organize projects
47195
+ - **Server management** - Start, stop, and check server status
49485
47196
 
49486
47197
  Use the Fulcrum MCP tools when:
47198
+ - **Task CRUD operations** - Create, list, update, delete tasks
47199
+ - **Project/repository management** - Create, organize, and manage projects and repositories
47200
+ - **App deployment** - Deploy, stop, and monitor Docker Compose applications
47201
+ - **Filesystem operations** - Read, write, and edit files on the Fulcrum server
49487
47202
  - **Executing commands remotely** - Run shell commands on the Fulcrum server from Claude Desktop
49488
47203
  - **Stateful workflows** - Use persistent sessions to maintain environment variables and working directory across commands
49489
47204
 
@@ -49516,57 +47231,6 @@ fulcrum current-task link # List all links
49516
47231
  fulcrum current-task link --remove <url-or-id> # Remove a link
49517
47232
  \`\`\`
49518
47233
 
49519
- ### tasks
49520
-
49521
- Manage tasks across the system:
49522
-
49523
- \`\`\`bash
49524
- # List all tasks
49525
- fulcrum tasks list
49526
- fulcrum tasks list --status=IN_PROGRESS # Filter by status
49527
- fulcrum tasks list --search="ocai" # Search by title, labels
49528
- fulcrum tasks list --label="bug" # Filter by label
49529
-
49530
- # List all labels in use
49531
- fulcrum tasks labels # Show all labels with counts
49532
- fulcrum tasks labels --search="comm" # Find labels matching substring
49533
-
49534
- # Get a specific task (includes dependencies and attachments)
49535
- fulcrum tasks get <task-id>
49536
-
49537
- # Create a new task
49538
- fulcrum tasks create --title="My Task" --repo=/path/to/repo
49539
-
49540
- # Update task metadata
49541
- fulcrum tasks update <task-id> --title="New Title"
49542
-
49543
- # Move task to different status
49544
- fulcrum tasks move <task-id> --status=IN_REVIEW
49545
-
49546
- # Delete a task
49547
- fulcrum tasks delete <task-id>
49548
- fulcrum tasks delete <task-id> --delete-worktree # Also delete worktree
49549
-
49550
- # Labels
49551
- fulcrum tasks add-label <task-id> <label>
49552
- fulcrum tasks remove-label <task-id> <label>
49553
-
49554
- # Due dates
49555
- fulcrum tasks set-due-date <task-id> 2026-01-25 # Set due date
49556
- fulcrum tasks set-due-date <task-id> none # Clear due date
49557
-
49558
- # Dependencies
49559
- fulcrum tasks add-dependency <task-id> <depends-on-task-id>
49560
- fulcrum tasks remove-dependency <task-id> <dependency-id>
49561
- fulcrum tasks list-dependencies <task-id>
49562
-
49563
- # Attachments
49564
- fulcrum tasks attachments list <task-id>
49565
- fulcrum tasks attachments upload <task-id> <file-path>
49566
- fulcrum tasks attachments delete <task-id> <attachment-id>
49567
- fulcrum tasks attachments path <task-id> <attachment-id> # Get local file path
49568
- \`\`\`
49569
-
49570
47234
  ### notifications
49571
47235
 
49572
47236
  Send notifications to the user:
@@ -49622,162 +47286,6 @@ fulcrum opencode install # Install Fulcrum plugin for OpenCode
49622
47286
  fulcrum opencode uninstall # Uninstall plugin
49623
47287
  \`\`\`
49624
47288
 
49625
- ### Git Operations
49626
-
49627
- \`\`\`bash
49628
- fulcrum git status # Git status for current worktree
49629
- fulcrum git diff # Git diff for current worktree
49630
- fulcrum git diff --staged # Show staged changes only
49631
- fulcrum git branches --repo=/path/to/repo # List branches
49632
- \`\`\`
49633
-
49634
- ### Worktrees
49635
-
49636
- \`\`\`bash
49637
- fulcrum worktrees list # List all worktrees
49638
- fulcrum worktrees delete --path=/path/to/worktree # Delete a worktree
49639
- \`\`\`
49640
-
49641
- ### projects
49642
-
49643
- Manage projects (repositories with metadata):
49644
-
49645
- \`\`\`bash
49646
- # List all projects
49647
- fulcrum projects list
49648
- fulcrum projects list --status=active # Filter by status (active, archived)
49649
-
49650
- # Get project details
49651
- fulcrum projects get <project-id>
49652
-
49653
- # Create a new project
49654
- fulcrum projects create --name="My Project" --path=/path/to/repo # From local path
49655
- fulcrum projects create --name="My Project" --url=https://github.com/... # Clone from URL
49656
- fulcrum projects create --name="My Project" --repository-id=<repo-id> # Link existing repo
49657
-
49658
- # Update project
49659
- fulcrum projects update <project-id> --name="New Name"
49660
- fulcrum projects update <project-id> --status=archived
49661
-
49662
- # Delete project
49663
- fulcrum projects delete <project-id>
49664
- fulcrum projects delete <project-id> --delete-directory # Also delete directory
49665
- fulcrum projects delete <project-id> --delete-app # Also delete linked app
49666
-
49667
- # Scan for git repositories
49668
- fulcrum projects scan # Scan default directory
49669
- fulcrum projects scan --directory=/path # Scan specific directory
49670
-
49671
- # Manage project links (URLs)
49672
- fulcrum projects links list <project-id>
49673
- fulcrum projects links add <project-id> <url> --label="Custom Label"
49674
- fulcrum projects links remove <project-id> <link-id>
49675
- \`\`\`
49676
-
49677
- ### repositories
49678
-
49679
- Manage repositories (code sources that can be linked to projects):
49680
-
49681
- \`\`\`bash
49682
- # List repositories
49683
- fulcrum repositories list
49684
- fulcrum repositories list --orphans # Unlinked repos only
49685
- fulcrum repositories list --project-id=<id> # Filter by project
49686
-
49687
- # Get repository details
49688
- fulcrum repositories get <repo-id>
49689
-
49690
- # Add a new repository from local path
49691
- fulcrum repositories add --path=/path/to/repo
49692
- fulcrum repositories add --path=/path/to/repo --display-name="My Repo"
49693
-
49694
- # Update repository
49695
- fulcrum repositories update <repo-id> --display-name="New Name"
49696
- fulcrum repositories update <repo-id> --default-agent=claude
49697
- fulcrum repositories update <repo-id> --startup-script="mise run dev"
49698
- fulcrum repositories update <repo-id> --copy-files=".env,.env.local"
49699
-
49700
- # Delete orphaned repository (fails if linked to a project)
49701
- fulcrum repositories delete <repo-id>
49702
-
49703
- # Link repository to project (repos can only be linked to one project)
49704
- fulcrum repositories link <repo-id> <project-id>
49705
- fulcrum repositories link <repo-id> <project-id> --as-primary
49706
- fulcrum repositories link <repo-id> <project-id> --force # Move from existing project
49707
-
49708
- # Unlink repository from project
49709
- fulcrum repositories unlink <repo-id> <project-id>
49710
- \`\`\`
49711
-
49712
- ### apps
49713
-
49714
- Manage Docker Compose app deployments:
49715
-
49716
- \`\`\`bash
49717
- # List all apps
49718
- fulcrum apps list
49719
- fulcrum apps list --status=running # Filter by status (stopped, building, running, failed)
49720
-
49721
- # Get app details
49722
- fulcrum apps get <app-id>
49723
-
49724
- # Create a new app
49725
- fulcrum apps create --name="My App" --repository-id=<repo-id>
49726
- fulcrum apps create --name="My App" --repository-id=<repo-id> --branch=develop --auto-deploy
49727
-
49728
- # Update app
49729
- fulcrum apps update <app-id> --name="New Name"
49730
- fulcrum apps update <app-id> --auto-deploy # Enable auto-deploy
49731
- fulcrum apps update <app-id> --no-cache # Enable no-cache builds
49732
-
49733
- # Deploy an app
49734
- fulcrum apps deploy <app-id>
49735
-
49736
- # Stop an app
49737
- fulcrum apps stop <app-id>
49738
-
49739
- # Get logs
49740
- fulcrum apps logs <app-id> # All services
49741
- fulcrum apps logs <app-id> --service=web # Specific service
49742
- fulcrum apps logs <app-id> --tail=200 # Last 200 lines
49743
-
49744
- # Get container status
49745
- fulcrum apps status <app-id>
49746
-
49747
- # Get deployment history
49748
- fulcrum apps deployments <app-id>
49749
-
49750
- # Delete an app
49751
- fulcrum apps delete <app-id>
49752
- fulcrum apps delete <app-id> --keep-containers # Keep containers running
49753
- \`\`\`
49754
-
49755
- ### fs (Filesystem)
49756
-
49757
- Remote filesystem operations for reading/writing files on the Fulcrum server. These tools are designed for working with a **remote Fulcrum instance** - they allow AI agents to read/write files on the server's filesystem through the API, which is useful when the agent runs on a different machine than the Fulcrum server.
49758
-
49759
- \`\`\`bash
49760
- # List directory contents
49761
- fulcrum fs list # Home directory
49762
- fulcrum fs list --path=/path/to/dir
49763
-
49764
- # Get file tree
49765
- fulcrum fs tree --root=/path/to/worktree
49766
-
49767
- # Read a file (with path traversal protection)
49768
- fulcrum fs read --path=src/index.ts --root=/path/to/worktree
49769
- fulcrum fs read --path=src/index.ts --root=/path/to/worktree --max-lines=100
49770
-
49771
- # Write to a file (replaces entire content)
49772
- fulcrum fs write --path=src/index.ts --root=/path/to/worktree --content="..."
49773
-
49774
- # Edit a file (replace a unique string)
49775
- fulcrum fs edit --path=src/index.ts --root=/path/to/worktree --old-string="foo" --new-string="bar"
49776
-
49777
- # Get file/directory info
49778
- fulcrum fs stat --path=/path/to/check
49779
- \`\`\`
49780
-
49781
47289
  ## Agent Workflow Patterns
49782
47290
 
49783
47291
  ### Typical Task Lifecycle
@@ -50687,7 +48195,7 @@ function installUv() {
50687
48195
  var package_default = {
50688
48196
  name: "@knowsuchagency/fulcrum",
50689
48197
  private: true,
50690
- version: "1.6.1",
48198
+ version: "1.7.1",
50691
48199
  description: "Harness Attention. Orchestrate Agents. Ship.",
50692
48200
  license: "PolyForm-Perimeter-1.0.0",
50693
48201
  type: "module",
@@ -51309,13 +48817,6 @@ var main = defineCommand({
51309
48817
  },
51310
48818
  subCommands: {
51311
48819
  "current-task": currentTaskCommand,
51312
- tasks: tasksCommand,
51313
- projects: projectsCommand,
51314
- repositories: repositoriesCommand,
51315
- apps: appsCommand,
51316
- fs: fsCommand,
51317
- git: gitCommand,
51318
- worktrees: worktreesCommand,
51319
48820
  config: configCommand,
51320
48821
  opencode: opencodeCommand,
51321
48822
  claude: claudeCommand,