@hasna/todos 0.3.4 → 0.3.6
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/dist/cli/index.js +207 -171
- package/dist/mcp/index.js +201 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2638,6 +2638,178 @@ var init_search = __esm(() => {
|
|
|
2638
2638
|
init_database();
|
|
2639
2639
|
});
|
|
2640
2640
|
|
|
2641
|
+
// src/lib/claude-tasks.ts
|
|
2642
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, readdirSync, writeFileSync } from "fs";
|
|
2643
|
+
import { join as join2 } from "path";
|
|
2644
|
+
function getTaskListDir(taskListId) {
|
|
2645
|
+
return join2(HOME, ".claude", "tasks", taskListId);
|
|
2646
|
+
}
|
|
2647
|
+
function readHighWaterMark(dir) {
|
|
2648
|
+
const path = join2(dir, ".highwatermark");
|
|
2649
|
+
if (!existsSync2(path))
|
|
2650
|
+
return 1;
|
|
2651
|
+
const val = parseInt(readFileSync(path, "utf-8").trim(), 10);
|
|
2652
|
+
return isNaN(val) ? 1 : val;
|
|
2653
|
+
}
|
|
2654
|
+
function writeHighWaterMark(dir, value) {
|
|
2655
|
+
writeFileSync(join2(dir, ".highwatermark"), String(value));
|
|
2656
|
+
}
|
|
2657
|
+
function readClaudeTask(dir, filename) {
|
|
2658
|
+
try {
|
|
2659
|
+
const content = readFileSync(join2(dir, filename), "utf-8");
|
|
2660
|
+
return JSON.parse(content);
|
|
2661
|
+
} catch {
|
|
2662
|
+
return null;
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
function writeClaudeTask(dir, task) {
|
|
2666
|
+
writeFileSync(join2(dir, `${task.id}.json`), JSON.stringify(task, null, 2) + `
|
|
2667
|
+
`);
|
|
2668
|
+
}
|
|
2669
|
+
function toClaudeStatus(status) {
|
|
2670
|
+
if (status === "pending" || status === "in_progress" || status === "completed") {
|
|
2671
|
+
return status;
|
|
2672
|
+
}
|
|
2673
|
+
return "completed";
|
|
2674
|
+
}
|
|
2675
|
+
function toSqliteStatus(status) {
|
|
2676
|
+
return status;
|
|
2677
|
+
}
|
|
2678
|
+
function taskToClaudeTask(task, claudeTaskId) {
|
|
2679
|
+
return {
|
|
2680
|
+
id: claudeTaskId,
|
|
2681
|
+
subject: task.title,
|
|
2682
|
+
description: task.description || "",
|
|
2683
|
+
activeForm: "",
|
|
2684
|
+
status: toClaudeStatus(task.status),
|
|
2685
|
+
owner: task.assigned_to || task.agent_id || "",
|
|
2686
|
+
blocks: [],
|
|
2687
|
+
blockedBy: [],
|
|
2688
|
+
metadata: {
|
|
2689
|
+
todos_id: task.id,
|
|
2690
|
+
priority: task.priority
|
|
2691
|
+
}
|
|
2692
|
+
};
|
|
2693
|
+
}
|
|
2694
|
+
function pushToClaudeTaskList(taskListId, projectId) {
|
|
2695
|
+
const dir = getTaskListDir(taskListId);
|
|
2696
|
+
if (!existsSync2(dir))
|
|
2697
|
+
mkdirSync2(dir, { recursive: true });
|
|
2698
|
+
const filter = {};
|
|
2699
|
+
if (projectId)
|
|
2700
|
+
filter["project_id"] = projectId;
|
|
2701
|
+
const tasks = listTasks(filter);
|
|
2702
|
+
const existingByTodosId = new Map;
|
|
2703
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
2704
|
+
for (const f of files) {
|
|
2705
|
+
const ct = readClaudeTask(dir, f);
|
|
2706
|
+
if (ct?.metadata?.["todos_id"]) {
|
|
2707
|
+
existingByTodosId.set(ct.metadata["todos_id"], ct);
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
let hwm = readHighWaterMark(dir);
|
|
2711
|
+
let pushed = 0;
|
|
2712
|
+
const errors = [];
|
|
2713
|
+
for (const task of tasks) {
|
|
2714
|
+
try {
|
|
2715
|
+
const existing = existingByTodosId.get(task.id);
|
|
2716
|
+
if (existing) {
|
|
2717
|
+
const updated = taskToClaudeTask(task, existing.id);
|
|
2718
|
+
updated.blocks = existing.blocks;
|
|
2719
|
+
updated.blockedBy = existing.blockedBy;
|
|
2720
|
+
updated.activeForm = existing.activeForm;
|
|
2721
|
+
writeClaudeTask(dir, updated);
|
|
2722
|
+
} else {
|
|
2723
|
+
const claudeId = String(hwm);
|
|
2724
|
+
hwm++;
|
|
2725
|
+
const ct = taskToClaudeTask(task, claudeId);
|
|
2726
|
+
writeClaudeTask(dir, ct);
|
|
2727
|
+
const current = getTask(task.id);
|
|
2728
|
+
if (current) {
|
|
2729
|
+
const newMeta = { ...current.metadata, claude_task_id: claudeId };
|
|
2730
|
+
updateTask(task.id, { version: current.version, metadata: newMeta });
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
pushed++;
|
|
2734
|
+
} catch (e) {
|
|
2735
|
+
errors.push(`push ${task.id}: ${e instanceof Error ? e.message : String(e)}`);
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
writeHighWaterMark(dir, hwm);
|
|
2739
|
+
return { pushed, pulled: 0, errors };
|
|
2740
|
+
}
|
|
2741
|
+
function pullFromClaudeTaskList(taskListId, projectId) {
|
|
2742
|
+
const dir = getTaskListDir(taskListId);
|
|
2743
|
+
if (!existsSync2(dir)) {
|
|
2744
|
+
return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
|
|
2745
|
+
}
|
|
2746
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
2747
|
+
let pulled = 0;
|
|
2748
|
+
const errors = [];
|
|
2749
|
+
const allTasks = listTasks({});
|
|
2750
|
+
const byClaudeId = new Map;
|
|
2751
|
+
for (const t of allTasks) {
|
|
2752
|
+
const cid = t.metadata["claude_task_id"];
|
|
2753
|
+
if (cid)
|
|
2754
|
+
byClaudeId.set(String(cid), t);
|
|
2755
|
+
}
|
|
2756
|
+
const byTodosId = new Map;
|
|
2757
|
+
for (const t of allTasks) {
|
|
2758
|
+
byTodosId.set(t.id, t);
|
|
2759
|
+
}
|
|
2760
|
+
for (const f of files) {
|
|
2761
|
+
try {
|
|
2762
|
+
const ct = readClaudeTask(dir, f);
|
|
2763
|
+
if (!ct)
|
|
2764
|
+
continue;
|
|
2765
|
+
if (ct.metadata?.["_internal"])
|
|
2766
|
+
continue;
|
|
2767
|
+
const todosId = ct.metadata?.["todos_id"];
|
|
2768
|
+
const existingByMapping = byClaudeId.get(ct.id);
|
|
2769
|
+
const existingByTodos = todosId ? byTodosId.get(todosId) : undefined;
|
|
2770
|
+
const existing = existingByMapping || existingByTodos;
|
|
2771
|
+
if (existing) {
|
|
2772
|
+
updateTask(existing.id, {
|
|
2773
|
+
version: existing.version,
|
|
2774
|
+
title: ct.subject,
|
|
2775
|
+
description: ct.description || undefined,
|
|
2776
|
+
status: toSqliteStatus(ct.status),
|
|
2777
|
+
assigned_to: ct.owner || undefined,
|
|
2778
|
+
metadata: { ...existing.metadata, claude_task_id: ct.id }
|
|
2779
|
+
});
|
|
2780
|
+
} else {
|
|
2781
|
+
createTask({
|
|
2782
|
+
title: ct.subject,
|
|
2783
|
+
description: ct.description || undefined,
|
|
2784
|
+
status: toSqliteStatus(ct.status),
|
|
2785
|
+
assigned_to: ct.owner || undefined,
|
|
2786
|
+
project_id: projectId,
|
|
2787
|
+
metadata: { claude_task_id: ct.id },
|
|
2788
|
+
priority: ct.metadata?.["priority"] || "medium"
|
|
2789
|
+
});
|
|
2790
|
+
}
|
|
2791
|
+
pulled++;
|
|
2792
|
+
} catch (e) {
|
|
2793
|
+
errors.push(`pull ${f}: ${e instanceof Error ? e.message : String(e)}`);
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
return { pushed: 0, pulled, errors };
|
|
2797
|
+
}
|
|
2798
|
+
function syncClaudeTaskList(taskListId, projectId) {
|
|
2799
|
+
const pullResult = pullFromClaudeTaskList(taskListId, projectId);
|
|
2800
|
+
const pushResult = pushToClaudeTaskList(taskListId, projectId);
|
|
2801
|
+
return {
|
|
2802
|
+
pushed: pushResult.pushed,
|
|
2803
|
+
pulled: pullResult.pulled,
|
|
2804
|
+
errors: [...pullResult.errors, ...pushResult.errors]
|
|
2805
|
+
};
|
|
2806
|
+
}
|
|
2807
|
+
var HOME;
|
|
2808
|
+
var init_claude_tasks = __esm(() => {
|
|
2809
|
+
init_tasks();
|
|
2810
|
+
HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
2811
|
+
});
|
|
2812
|
+
|
|
2641
2813
|
// node_modules/zod/v3/helpers/util.js
|
|
2642
2814
|
var util, objectUtil, ZodParsedType, getParsedType = (data) => {
|
|
2643
2815
|
const t = typeof data;
|
|
@@ -6667,6 +6839,7 @@ var init_mcp = __esm(() => {
|
|
|
6667
6839
|
init_comments();
|
|
6668
6840
|
init_projects();
|
|
6669
6841
|
init_search();
|
|
6842
|
+
init_claude_tasks();
|
|
6670
6843
|
init_database();
|
|
6671
6844
|
init_types();
|
|
6672
6845
|
server = new McpServer({
|
|
@@ -6964,6 +7137,39 @@ ${text}` }] };
|
|
|
6964
7137
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
6965
7138
|
}
|
|
6966
7139
|
});
|
|
7140
|
+
server.tool("sync", "Sync tasks with a Claude Code task list. Writes SQLite tasks as JSON files to ~/.claude/tasks/<task_list_id>/ so they appear in Claude Code's native task UI. The task_list_id is your Claude Code session ID (visible in the conversation or via CLAUDE_CODE_SESSION_ID).", {
|
|
7141
|
+
task_list_id: exports_external.string().describe("Claude Code task list ID \u2014 use your session ID"),
|
|
7142
|
+
project_id: exports_external.string().optional().describe("Limit sync to a project"),
|
|
7143
|
+
direction: exports_external.enum(["push", "pull", "both"]).optional().describe("Sync direction: push (SQLite->Claude), pull (Claude->SQLite), or both (default)")
|
|
7144
|
+
}, async ({ task_list_id, project_id, direction }) => {
|
|
7145
|
+
try {
|
|
7146
|
+
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
7147
|
+
const taskListId = task_list_id;
|
|
7148
|
+
let result;
|
|
7149
|
+
if (direction === "push") {
|
|
7150
|
+
result = pushToClaudeTaskList(taskListId, resolvedProjectId);
|
|
7151
|
+
} else if (direction === "pull") {
|
|
7152
|
+
result = pullFromClaudeTaskList(taskListId, resolvedProjectId);
|
|
7153
|
+
} else {
|
|
7154
|
+
result = syncClaudeTaskList(taskListId, resolvedProjectId);
|
|
7155
|
+
}
|
|
7156
|
+
const parts = [];
|
|
7157
|
+
if (result.pulled > 0)
|
|
7158
|
+
parts.push(`Pulled ${result.pulled} task(s) from Claude task list.`);
|
|
7159
|
+
if (result.pushed > 0)
|
|
7160
|
+
parts.push(`Pushed ${result.pushed} task(s) to Claude task list.`);
|
|
7161
|
+
if (result.pulled === 0 && result.pushed === 0 && result.errors.length === 0) {
|
|
7162
|
+
parts.push("Nothing to sync.");
|
|
7163
|
+
}
|
|
7164
|
+
for (const err of result.errors) {
|
|
7165
|
+
parts.push(`Error: ${err}`);
|
|
7166
|
+
}
|
|
7167
|
+
return { content: [{ type: "text", text: parts.join(`
|
|
7168
|
+
`) }] };
|
|
7169
|
+
} catch (e) {
|
|
7170
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
7171
|
+
}
|
|
7172
|
+
});
|
|
6967
7173
|
server.resource("tasks", "todos://tasks", { description: "All active tasks", mimeType: "application/json" }, async () => {
|
|
6968
7174
|
const tasks = listTasks({ status: ["pending", "in_progress"] });
|
|
6969
7175
|
return { contents: [{ uri: "todos://tasks", text: JSON.stringify(tasks, null, 2), mimeType: "application/json" }] };
|
|
@@ -8440,182 +8646,12 @@ init_tasks();
|
|
|
8440
8646
|
init_projects();
|
|
8441
8647
|
init_comments();
|
|
8442
8648
|
init_search();
|
|
8649
|
+
init_claude_tasks();
|
|
8443
8650
|
import chalk from "chalk";
|
|
8444
8651
|
import { execSync as execSync2 } from "child_process";
|
|
8445
8652
|
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
8446
8653
|
import { basename, dirname as dirname3, join as join4, resolve as resolve2 } from "path";
|
|
8447
8654
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8448
|
-
|
|
8449
|
-
// src/lib/claude-tasks.ts
|
|
8450
|
-
init_tasks();
|
|
8451
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, readdirSync, writeFileSync } from "fs";
|
|
8452
|
-
import { join as join2 } from "path";
|
|
8453
|
-
var HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
8454
|
-
function getTaskListDir(taskListId) {
|
|
8455
|
-
return join2(HOME, ".claude", "tasks", taskListId);
|
|
8456
|
-
}
|
|
8457
|
-
function readHighWaterMark(dir) {
|
|
8458
|
-
const path = join2(dir, ".highwatermark");
|
|
8459
|
-
if (!existsSync2(path))
|
|
8460
|
-
return 1;
|
|
8461
|
-
const val = parseInt(readFileSync(path, "utf-8").trim(), 10);
|
|
8462
|
-
return isNaN(val) ? 1 : val;
|
|
8463
|
-
}
|
|
8464
|
-
function writeHighWaterMark(dir, value) {
|
|
8465
|
-
writeFileSync(join2(dir, ".highwatermark"), String(value));
|
|
8466
|
-
}
|
|
8467
|
-
function readClaudeTask(dir, filename) {
|
|
8468
|
-
try {
|
|
8469
|
-
const content = readFileSync(join2(dir, filename), "utf-8");
|
|
8470
|
-
return JSON.parse(content);
|
|
8471
|
-
} catch {
|
|
8472
|
-
return null;
|
|
8473
|
-
}
|
|
8474
|
-
}
|
|
8475
|
-
function writeClaudeTask(dir, task) {
|
|
8476
|
-
writeFileSync(join2(dir, `${task.id}.json`), JSON.stringify(task, null, 2) + `
|
|
8477
|
-
`);
|
|
8478
|
-
}
|
|
8479
|
-
function toClaudeStatus(status) {
|
|
8480
|
-
if (status === "pending" || status === "in_progress" || status === "completed") {
|
|
8481
|
-
return status;
|
|
8482
|
-
}
|
|
8483
|
-
return "completed";
|
|
8484
|
-
}
|
|
8485
|
-
function toSqliteStatus(status) {
|
|
8486
|
-
return status;
|
|
8487
|
-
}
|
|
8488
|
-
function taskToClaudeTask(task, claudeTaskId) {
|
|
8489
|
-
return {
|
|
8490
|
-
id: claudeTaskId,
|
|
8491
|
-
subject: task.title,
|
|
8492
|
-
description: task.description || "",
|
|
8493
|
-
activeForm: "",
|
|
8494
|
-
status: toClaudeStatus(task.status),
|
|
8495
|
-
owner: task.assigned_to || task.agent_id || "",
|
|
8496
|
-
blocks: [],
|
|
8497
|
-
blockedBy: [],
|
|
8498
|
-
metadata: {
|
|
8499
|
-
todos_id: task.id,
|
|
8500
|
-
priority: task.priority
|
|
8501
|
-
}
|
|
8502
|
-
};
|
|
8503
|
-
}
|
|
8504
|
-
function pushToClaudeTaskList(taskListId, projectId) {
|
|
8505
|
-
const dir = getTaskListDir(taskListId);
|
|
8506
|
-
if (!existsSync2(dir))
|
|
8507
|
-
mkdirSync2(dir, { recursive: true });
|
|
8508
|
-
const filter = {};
|
|
8509
|
-
if (projectId)
|
|
8510
|
-
filter["project_id"] = projectId;
|
|
8511
|
-
const tasks = listTasks(filter);
|
|
8512
|
-
const existingByTodosId = new Map;
|
|
8513
|
-
const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
8514
|
-
for (const f of files) {
|
|
8515
|
-
const ct = readClaudeTask(dir, f);
|
|
8516
|
-
if (ct?.metadata?.["todos_id"]) {
|
|
8517
|
-
existingByTodosId.set(ct.metadata["todos_id"], ct);
|
|
8518
|
-
}
|
|
8519
|
-
}
|
|
8520
|
-
let hwm = readHighWaterMark(dir);
|
|
8521
|
-
let pushed = 0;
|
|
8522
|
-
const errors = [];
|
|
8523
|
-
for (const task of tasks) {
|
|
8524
|
-
try {
|
|
8525
|
-
const existing = existingByTodosId.get(task.id);
|
|
8526
|
-
if (existing) {
|
|
8527
|
-
const updated = taskToClaudeTask(task, existing.id);
|
|
8528
|
-
updated.blocks = existing.blocks;
|
|
8529
|
-
updated.blockedBy = existing.blockedBy;
|
|
8530
|
-
updated.activeForm = existing.activeForm;
|
|
8531
|
-
writeClaudeTask(dir, updated);
|
|
8532
|
-
} else {
|
|
8533
|
-
const claudeId = String(hwm);
|
|
8534
|
-
hwm++;
|
|
8535
|
-
const ct = taskToClaudeTask(task, claudeId);
|
|
8536
|
-
writeClaudeTask(dir, ct);
|
|
8537
|
-
const current = getTask(task.id);
|
|
8538
|
-
if (current) {
|
|
8539
|
-
const newMeta = { ...current.metadata, claude_task_id: claudeId };
|
|
8540
|
-
updateTask(task.id, { version: current.version, metadata: newMeta });
|
|
8541
|
-
}
|
|
8542
|
-
}
|
|
8543
|
-
pushed++;
|
|
8544
|
-
} catch (e) {
|
|
8545
|
-
errors.push(`push ${task.id}: ${e instanceof Error ? e.message : String(e)}`);
|
|
8546
|
-
}
|
|
8547
|
-
}
|
|
8548
|
-
writeHighWaterMark(dir, hwm);
|
|
8549
|
-
return { pushed, pulled: 0, errors };
|
|
8550
|
-
}
|
|
8551
|
-
function pullFromClaudeTaskList(taskListId, projectId) {
|
|
8552
|
-
const dir = getTaskListDir(taskListId);
|
|
8553
|
-
if (!existsSync2(dir)) {
|
|
8554
|
-
return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
|
|
8555
|
-
}
|
|
8556
|
-
const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
8557
|
-
let pulled = 0;
|
|
8558
|
-
const errors = [];
|
|
8559
|
-
const allTasks = listTasks({});
|
|
8560
|
-
const byClaudeId = new Map;
|
|
8561
|
-
for (const t of allTasks) {
|
|
8562
|
-
const cid = t.metadata["claude_task_id"];
|
|
8563
|
-
if (cid)
|
|
8564
|
-
byClaudeId.set(String(cid), t);
|
|
8565
|
-
}
|
|
8566
|
-
const byTodosId = new Map;
|
|
8567
|
-
for (const t of allTasks) {
|
|
8568
|
-
byTodosId.set(t.id, t);
|
|
8569
|
-
}
|
|
8570
|
-
for (const f of files) {
|
|
8571
|
-
try {
|
|
8572
|
-
const ct = readClaudeTask(dir, f);
|
|
8573
|
-
if (!ct)
|
|
8574
|
-
continue;
|
|
8575
|
-
if (ct.metadata?.["_internal"])
|
|
8576
|
-
continue;
|
|
8577
|
-
const todosId = ct.metadata?.["todos_id"];
|
|
8578
|
-
const existingByMapping = byClaudeId.get(ct.id);
|
|
8579
|
-
const existingByTodos = todosId ? byTodosId.get(todosId) : undefined;
|
|
8580
|
-
const existing = existingByMapping || existingByTodos;
|
|
8581
|
-
if (existing) {
|
|
8582
|
-
updateTask(existing.id, {
|
|
8583
|
-
version: existing.version,
|
|
8584
|
-
title: ct.subject,
|
|
8585
|
-
description: ct.description || undefined,
|
|
8586
|
-
status: toSqliteStatus(ct.status),
|
|
8587
|
-
assigned_to: ct.owner || undefined,
|
|
8588
|
-
metadata: { ...existing.metadata, claude_task_id: ct.id }
|
|
8589
|
-
});
|
|
8590
|
-
} else {
|
|
8591
|
-
createTask({
|
|
8592
|
-
title: ct.subject,
|
|
8593
|
-
description: ct.description || undefined,
|
|
8594
|
-
status: toSqliteStatus(ct.status),
|
|
8595
|
-
assigned_to: ct.owner || undefined,
|
|
8596
|
-
project_id: projectId,
|
|
8597
|
-
metadata: { claude_task_id: ct.id },
|
|
8598
|
-
priority: ct.metadata?.["priority"] || "medium"
|
|
8599
|
-
});
|
|
8600
|
-
}
|
|
8601
|
-
pulled++;
|
|
8602
|
-
} catch (e) {
|
|
8603
|
-
errors.push(`pull ${f}: ${e instanceof Error ? e.message : String(e)}`);
|
|
8604
|
-
}
|
|
8605
|
-
}
|
|
8606
|
-
return { pushed: 0, pulled, errors };
|
|
8607
|
-
}
|
|
8608
|
-
function syncClaudeTaskList(taskListId, projectId) {
|
|
8609
|
-
const pullResult = pullFromClaudeTaskList(taskListId, projectId);
|
|
8610
|
-
const pushResult = pushToClaudeTaskList(taskListId, projectId);
|
|
8611
|
-
return {
|
|
8612
|
-
pushed: pushResult.pushed,
|
|
8613
|
-
pulled: pullResult.pulled,
|
|
8614
|
-
errors: [...pullResult.errors, ...pushResult.errors]
|
|
8615
|
-
};
|
|
8616
|
-
}
|
|
8617
|
-
|
|
8618
|
-
// src/cli/index.tsx
|
|
8619
8655
|
function getPackageVersion2() {
|
|
8620
8656
|
try {
|
|
8621
8657
|
const pkgPath = join4(dirname3(fileURLToPath2(import.meta.url)), "..", "..", "package.json");
|
package/dist/mcp/index.js
CHANGED
|
@@ -4529,6 +4529,174 @@ function searchTasks(query, projectId, db) {
|
|
|
4529
4529
|
return rows.map(rowToTask2);
|
|
4530
4530
|
}
|
|
4531
4531
|
|
|
4532
|
+
// src/lib/claude-tasks.ts
|
|
4533
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, readdirSync, writeFileSync } from "fs";
|
|
4534
|
+
import { join as join2 } from "path";
|
|
4535
|
+
var HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
4536
|
+
function getTaskListDir(taskListId) {
|
|
4537
|
+
return join2(HOME, ".claude", "tasks", taskListId);
|
|
4538
|
+
}
|
|
4539
|
+
function readHighWaterMark(dir) {
|
|
4540
|
+
const path = join2(dir, ".highwatermark");
|
|
4541
|
+
if (!existsSync2(path))
|
|
4542
|
+
return 1;
|
|
4543
|
+
const val = parseInt(readFileSync(path, "utf-8").trim(), 10);
|
|
4544
|
+
return isNaN(val) ? 1 : val;
|
|
4545
|
+
}
|
|
4546
|
+
function writeHighWaterMark(dir, value) {
|
|
4547
|
+
writeFileSync(join2(dir, ".highwatermark"), String(value));
|
|
4548
|
+
}
|
|
4549
|
+
function readClaudeTask(dir, filename) {
|
|
4550
|
+
try {
|
|
4551
|
+
const content = readFileSync(join2(dir, filename), "utf-8");
|
|
4552
|
+
return JSON.parse(content);
|
|
4553
|
+
} catch {
|
|
4554
|
+
return null;
|
|
4555
|
+
}
|
|
4556
|
+
}
|
|
4557
|
+
function writeClaudeTask(dir, task) {
|
|
4558
|
+
writeFileSync(join2(dir, `${task.id}.json`), JSON.stringify(task, null, 2) + `
|
|
4559
|
+
`);
|
|
4560
|
+
}
|
|
4561
|
+
function toClaudeStatus(status) {
|
|
4562
|
+
if (status === "pending" || status === "in_progress" || status === "completed") {
|
|
4563
|
+
return status;
|
|
4564
|
+
}
|
|
4565
|
+
return "completed";
|
|
4566
|
+
}
|
|
4567
|
+
function toSqliteStatus(status) {
|
|
4568
|
+
return status;
|
|
4569
|
+
}
|
|
4570
|
+
function taskToClaudeTask(task, claudeTaskId) {
|
|
4571
|
+
return {
|
|
4572
|
+
id: claudeTaskId,
|
|
4573
|
+
subject: task.title,
|
|
4574
|
+
description: task.description || "",
|
|
4575
|
+
activeForm: "",
|
|
4576
|
+
status: toClaudeStatus(task.status),
|
|
4577
|
+
owner: task.assigned_to || task.agent_id || "",
|
|
4578
|
+
blocks: [],
|
|
4579
|
+
blockedBy: [],
|
|
4580
|
+
metadata: {
|
|
4581
|
+
todos_id: task.id,
|
|
4582
|
+
priority: task.priority
|
|
4583
|
+
}
|
|
4584
|
+
};
|
|
4585
|
+
}
|
|
4586
|
+
function pushToClaudeTaskList(taskListId, projectId) {
|
|
4587
|
+
const dir = getTaskListDir(taskListId);
|
|
4588
|
+
if (!existsSync2(dir))
|
|
4589
|
+
mkdirSync2(dir, { recursive: true });
|
|
4590
|
+
const filter = {};
|
|
4591
|
+
if (projectId)
|
|
4592
|
+
filter["project_id"] = projectId;
|
|
4593
|
+
const tasks = listTasks(filter);
|
|
4594
|
+
const existingByTodosId = new Map;
|
|
4595
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
4596
|
+
for (const f of files) {
|
|
4597
|
+
const ct = readClaudeTask(dir, f);
|
|
4598
|
+
if (ct?.metadata?.["todos_id"]) {
|
|
4599
|
+
existingByTodosId.set(ct.metadata["todos_id"], ct);
|
|
4600
|
+
}
|
|
4601
|
+
}
|
|
4602
|
+
let hwm = readHighWaterMark(dir);
|
|
4603
|
+
let pushed = 0;
|
|
4604
|
+
const errors2 = [];
|
|
4605
|
+
for (const task of tasks) {
|
|
4606
|
+
try {
|
|
4607
|
+
const existing = existingByTodosId.get(task.id);
|
|
4608
|
+
if (existing) {
|
|
4609
|
+
const updated = taskToClaudeTask(task, existing.id);
|
|
4610
|
+
updated.blocks = existing.blocks;
|
|
4611
|
+
updated.blockedBy = existing.blockedBy;
|
|
4612
|
+
updated.activeForm = existing.activeForm;
|
|
4613
|
+
writeClaudeTask(dir, updated);
|
|
4614
|
+
} else {
|
|
4615
|
+
const claudeId = String(hwm);
|
|
4616
|
+
hwm++;
|
|
4617
|
+
const ct = taskToClaudeTask(task, claudeId);
|
|
4618
|
+
writeClaudeTask(dir, ct);
|
|
4619
|
+
const current = getTask(task.id);
|
|
4620
|
+
if (current) {
|
|
4621
|
+
const newMeta = { ...current.metadata, claude_task_id: claudeId };
|
|
4622
|
+
updateTask(task.id, { version: current.version, metadata: newMeta });
|
|
4623
|
+
}
|
|
4624
|
+
}
|
|
4625
|
+
pushed++;
|
|
4626
|
+
} catch (e) {
|
|
4627
|
+
errors2.push(`push ${task.id}: ${e instanceof Error ? e.message : String(e)}`);
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4630
|
+
writeHighWaterMark(dir, hwm);
|
|
4631
|
+
return { pushed, pulled: 0, errors: errors2 };
|
|
4632
|
+
}
|
|
4633
|
+
function pullFromClaudeTaskList(taskListId, projectId) {
|
|
4634
|
+
const dir = getTaskListDir(taskListId);
|
|
4635
|
+
if (!existsSync2(dir)) {
|
|
4636
|
+
return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
|
|
4637
|
+
}
|
|
4638
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
4639
|
+
let pulled = 0;
|
|
4640
|
+
const errors2 = [];
|
|
4641
|
+
const allTasks = listTasks({});
|
|
4642
|
+
const byClaudeId = new Map;
|
|
4643
|
+
for (const t of allTasks) {
|
|
4644
|
+
const cid = t.metadata["claude_task_id"];
|
|
4645
|
+
if (cid)
|
|
4646
|
+
byClaudeId.set(String(cid), t);
|
|
4647
|
+
}
|
|
4648
|
+
const byTodosId = new Map;
|
|
4649
|
+
for (const t of allTasks) {
|
|
4650
|
+
byTodosId.set(t.id, t);
|
|
4651
|
+
}
|
|
4652
|
+
for (const f of files) {
|
|
4653
|
+
try {
|
|
4654
|
+
const ct = readClaudeTask(dir, f);
|
|
4655
|
+
if (!ct)
|
|
4656
|
+
continue;
|
|
4657
|
+
if (ct.metadata?.["_internal"])
|
|
4658
|
+
continue;
|
|
4659
|
+
const todosId = ct.metadata?.["todos_id"];
|
|
4660
|
+
const existingByMapping = byClaudeId.get(ct.id);
|
|
4661
|
+
const existingByTodos = todosId ? byTodosId.get(todosId) : undefined;
|
|
4662
|
+
const existing = existingByMapping || existingByTodos;
|
|
4663
|
+
if (existing) {
|
|
4664
|
+
updateTask(existing.id, {
|
|
4665
|
+
version: existing.version,
|
|
4666
|
+
title: ct.subject,
|
|
4667
|
+
description: ct.description || undefined,
|
|
4668
|
+
status: toSqliteStatus(ct.status),
|
|
4669
|
+
assigned_to: ct.owner || undefined,
|
|
4670
|
+
metadata: { ...existing.metadata, claude_task_id: ct.id }
|
|
4671
|
+
});
|
|
4672
|
+
} else {
|
|
4673
|
+
createTask({
|
|
4674
|
+
title: ct.subject,
|
|
4675
|
+
description: ct.description || undefined,
|
|
4676
|
+
status: toSqliteStatus(ct.status),
|
|
4677
|
+
assigned_to: ct.owner || undefined,
|
|
4678
|
+
project_id: projectId,
|
|
4679
|
+
metadata: { claude_task_id: ct.id },
|
|
4680
|
+
priority: ct.metadata?.["priority"] || "medium"
|
|
4681
|
+
});
|
|
4682
|
+
}
|
|
4683
|
+
pulled++;
|
|
4684
|
+
} catch (e) {
|
|
4685
|
+
errors2.push(`pull ${f}: ${e instanceof Error ? e.message : String(e)}`);
|
|
4686
|
+
}
|
|
4687
|
+
}
|
|
4688
|
+
return { pushed: 0, pulled, errors: errors2 };
|
|
4689
|
+
}
|
|
4690
|
+
function syncClaudeTaskList(taskListId, projectId) {
|
|
4691
|
+
const pullResult = pullFromClaudeTaskList(taskListId, projectId);
|
|
4692
|
+
const pushResult = pushToClaudeTaskList(taskListId, projectId);
|
|
4693
|
+
return {
|
|
4694
|
+
pushed: pushResult.pushed,
|
|
4695
|
+
pulled: pullResult.pulled,
|
|
4696
|
+
errors: [...pullResult.errors, ...pushResult.errors]
|
|
4697
|
+
};
|
|
4698
|
+
}
|
|
4699
|
+
|
|
4532
4700
|
// src/mcp/index.ts
|
|
4533
4701
|
var server = new McpServer({
|
|
4534
4702
|
name: "todos",
|
|
@@ -4873,6 +5041,39 @@ ${text}` }] };
|
|
|
4873
5041
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
4874
5042
|
}
|
|
4875
5043
|
});
|
|
5044
|
+
server.tool("sync", "Sync tasks with a Claude Code task list. Writes SQLite tasks as JSON files to ~/.claude/tasks/<task_list_id>/ so they appear in Claude Code's native task UI. The task_list_id is your Claude Code session ID (visible in the conversation or via CLAUDE_CODE_SESSION_ID).", {
|
|
5045
|
+
task_list_id: exports_external.string().describe("Claude Code task list ID \u2014 use your session ID"),
|
|
5046
|
+
project_id: exports_external.string().optional().describe("Limit sync to a project"),
|
|
5047
|
+
direction: exports_external.enum(["push", "pull", "both"]).optional().describe("Sync direction: push (SQLite->Claude), pull (Claude->SQLite), or both (default)")
|
|
5048
|
+
}, async ({ task_list_id, project_id, direction }) => {
|
|
5049
|
+
try {
|
|
5050
|
+
const resolvedProjectId = project_id ? resolveId(project_id, "projects") : undefined;
|
|
5051
|
+
const taskListId = task_list_id;
|
|
5052
|
+
let result;
|
|
5053
|
+
if (direction === "push") {
|
|
5054
|
+
result = pushToClaudeTaskList(taskListId, resolvedProjectId);
|
|
5055
|
+
} else if (direction === "pull") {
|
|
5056
|
+
result = pullFromClaudeTaskList(taskListId, resolvedProjectId);
|
|
5057
|
+
} else {
|
|
5058
|
+
result = syncClaudeTaskList(taskListId, resolvedProjectId);
|
|
5059
|
+
}
|
|
5060
|
+
const parts = [];
|
|
5061
|
+
if (result.pulled > 0)
|
|
5062
|
+
parts.push(`Pulled ${result.pulled} task(s) from Claude task list.`);
|
|
5063
|
+
if (result.pushed > 0)
|
|
5064
|
+
parts.push(`Pushed ${result.pushed} task(s) to Claude task list.`);
|
|
5065
|
+
if (result.pulled === 0 && result.pushed === 0 && result.errors.length === 0) {
|
|
5066
|
+
parts.push("Nothing to sync.");
|
|
5067
|
+
}
|
|
5068
|
+
for (const err of result.errors) {
|
|
5069
|
+
parts.push(`Error: ${err}`);
|
|
5070
|
+
}
|
|
5071
|
+
return { content: [{ type: "text", text: parts.join(`
|
|
5072
|
+
`) }] };
|
|
5073
|
+
} catch (e) {
|
|
5074
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
5075
|
+
}
|
|
5076
|
+
});
|
|
4876
5077
|
server.resource("tasks", "todos://tasks", { description: "All active tasks", mimeType: "application/json" }, async () => {
|
|
4877
5078
|
const tasks = listTasks({ status: ["pending", "in_progress"] });
|
|
4878
5079
|
return { contents: [{ uri: "todos://tasks", text: JSON.stringify(tasks, null, 2), mimeType: "application/json" }] };
|