@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.
- package/bin/fulcrum.js +56 -2555
- package/package.json +1 -1
- 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
|
|
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 ?
|
|
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/
|
|
46479
|
-
import { basename as basename2 } from "path";
|
|
46478
|
+
// cli/src/commands/config.ts
|
|
46480
46479
|
init_client();
|
|
46481
46480
|
init_errors();
|
|
46482
|
-
|
|
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
|
-
|
|
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(
|
|
46487
|
+
output(config);
|
|
46775
46488
|
} else {
|
|
46776
|
-
|
|
46777
|
-
|
|
46778
|
-
|
|
46779
|
-
console.log(
|
|
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 "
|
|
46785
|
-
const [
|
|
46786
|
-
if (!
|
|
46787
|
-
throw new CliError("
|
|
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
|
|
46502
|
+
const config = await client.getConfig(key);
|
|
46793
46503
|
if (isJsonOutput()) {
|
|
46794
|
-
output(
|
|
46504
|
+
output(config);
|
|
46795
46505
|
} else {
|
|
46796
|
-
|
|
46506
|
+
const value = config.value === null ? "(not set)" : config.value;
|
|
46507
|
+
console.log(`${key}: ${value}`);
|
|
46797
46508
|
}
|
|
46798
46509
|
break;
|
|
46799
46510
|
}
|
|
46800
|
-
case "
|
|
46801
|
-
const [
|
|
46802
|
-
if (!
|
|
46803
|
-
throw new CliError("
|
|
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
|
-
|
|
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
|
|
46519
|
+
const parsedValue = /^\d+$/.test(value) ? parseInt(value, 10) : value;
|
|
46520
|
+
const config = await client.setConfig(key, parsedValue);
|
|
46822
46521
|
if (isJsonOutput()) {
|
|
46823
|
-
output(
|
|
46522
|
+
output(config);
|
|
46824
46523
|
} else {
|
|
46825
|
-
console.log(`
|
|
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 "
|
|
46849
|
-
const
|
|
46850
|
-
|
|
46851
|
-
|
|
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
|
-
|
|
46533
|
+
const config = await client.resetConfig(key);
|
|
46864
46534
|
if (isJsonOutput()) {
|
|
46865
|
-
output(
|
|
46535
|
+
output(config);
|
|
46866
46536
|
} else {
|
|
46867
|
-
|
|
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,
|
|
46542
|
+
throw new CliError("UNKNOWN_ACTION", `Unknown action: ${action}. Valid: list, get, set, reset`, ExitCodes.INVALID_ARGS);
|
|
46971
46543
|
}
|
|
46972
46544
|
}
|
|
46973
|
-
var
|
|
46974
|
-
meta: { name: "list", description: "List
|
|
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
|
|
46550
|
+
await handleConfigCommand("list", [], toFlags(args));
|
|
46987
46551
|
}
|
|
46988
46552
|
});
|
|
46989
|
-
var
|
|
46990
|
-
meta: { name: "get", description: "Get
|
|
46553
|
+
var configGetCommand = defineCommand({
|
|
46554
|
+
meta: { name: "get", description: "Get a config value" },
|
|
46991
46555
|
args: {
|
|
46992
46556
|
...globalArgs,
|
|
46993
|
-
|
|
46557
|
+
key: { type: "positional", description: "Config key", required: true }
|
|
46994
46558
|
},
|
|
46995
46559
|
async run({ args }) {
|
|
46996
46560
|
setupJsonOutput(args);
|
|
46997
|
-
await
|
|
46561
|
+
await handleConfigCommand("get", [args.key], toFlags(args));
|
|
46998
46562
|
}
|
|
46999
46563
|
});
|
|
47000
|
-
var
|
|
47001
|
-
meta: { name: "
|
|
46564
|
+
var configSetCommand = defineCommand({
|
|
46565
|
+
meta: { name: "set", description: "Set a config value" },
|
|
47002
46566
|
args: {
|
|
47003
46567
|
...globalArgs,
|
|
47004
|
-
|
|
47005
|
-
|
|
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
|
|
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.
|
|
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
|
-
- **
|
|
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.
|
|
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,
|