@hasna/todos 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli/index.js +313 -38
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -6975,27 +6975,27 @@ __export(exports_serve, {
6975
6975
  startServer: () => startServer
6976
6976
  });
6977
6977
  import { execSync } from "child_process";
6978
- import { existsSync as existsSync2, readFileSync } from "fs";
6979
- import { join as join2, dirname as dirname2, extname } from "path";
6978
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
6979
+ import { join as join3, dirname as dirname2, extname } from "path";
6980
6980
  import { fileURLToPath } from "url";
6981
6981
  function resolveDashboardDir() {
6982
6982
  const candidates = [];
6983
6983
  try {
6984
6984
  const scriptDir = dirname2(fileURLToPath(import.meta.url));
6985
- candidates.push(join2(scriptDir, "..", "dashboard", "dist"));
6986
- candidates.push(join2(scriptDir, "..", "..", "dashboard", "dist"));
6985
+ candidates.push(join3(scriptDir, "..", "dashboard", "dist"));
6986
+ candidates.push(join3(scriptDir, "..", "..", "dashboard", "dist"));
6987
6987
  } catch {}
6988
6988
  if (process.argv[1]) {
6989
6989
  const mainDir = dirname2(process.argv[1]);
6990
- candidates.push(join2(mainDir, "..", "dashboard", "dist"));
6991
- candidates.push(join2(mainDir, "..", "..", "dashboard", "dist"));
6990
+ candidates.push(join3(mainDir, "..", "dashboard", "dist"));
6991
+ candidates.push(join3(mainDir, "..", "..", "dashboard", "dist"));
6992
6992
  }
6993
- candidates.push(join2(process.cwd(), "dashboard", "dist"));
6993
+ candidates.push(join3(process.cwd(), "dashboard", "dist"));
6994
6994
  for (const candidate of candidates) {
6995
- if (existsSync2(candidate))
6995
+ if (existsSync3(candidate))
6996
6996
  return candidate;
6997
6997
  }
6998
- return join2(process.cwd(), "dashboard", "dist");
6998
+ return join3(process.cwd(), "dashboard", "dist");
6999
6999
  }
7000
7000
  function json(data, status = 200, port) {
7001
7001
  return new Response(JSON.stringify(data), {
@@ -7009,14 +7009,14 @@ function json(data, status = 200, port) {
7009
7009
  }
7010
7010
  function getPackageVersion() {
7011
7011
  try {
7012
- const pkgPath = join2(dirname2(fileURLToPath(import.meta.url)), "..", "..", "package.json");
7013
- return JSON.parse(readFileSync(pkgPath, "utf-8")).version || "0.0.0";
7012
+ const pkgPath = join3(dirname2(fileURLToPath(import.meta.url)), "..", "..", "package.json");
7013
+ return JSON.parse(readFileSync2(pkgPath, "utf-8")).version || "0.0.0";
7014
7014
  } catch {
7015
7015
  return "0.0.0";
7016
7016
  }
7017
7017
  }
7018
7018
  function serveStaticFile(filePath) {
7019
- if (!existsSync2(filePath))
7019
+ if (!existsSync3(filePath))
7020
7020
  return null;
7021
7021
  const ext = extname(filePath);
7022
7022
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
@@ -7027,7 +7027,7 @@ function serveStaticFile(filePath) {
7027
7027
  async function startServer(port, options) {
7028
7028
  const shouldOpen = options?.open ?? true;
7029
7029
  const dashboardDir = resolveDashboardDir();
7030
- const dashboardExists = existsSync2(dashboardDir);
7030
+ const dashboardExists = existsSync3(dashboardDir);
7031
7031
  if (!dashboardExists) {
7032
7032
  console.error(`
7033
7033
  Dashboard not found at: ${dashboardDir}`);
@@ -7297,12 +7297,12 @@ Dashboard not found at: ${dashboardDir}`);
7297
7297
  }
7298
7298
  if (dashboardExists && (method === "GET" || method === "HEAD")) {
7299
7299
  if (path !== "/") {
7300
- const filePath = join2(dashboardDir, path);
7300
+ const filePath = join3(dashboardDir, path);
7301
7301
  const res2 = serveStaticFile(filePath);
7302
7302
  if (res2)
7303
7303
  return res2;
7304
7304
  }
7305
- const indexPath = join2(dashboardDir, "index.html");
7305
+ const indexPath = join3(dashboardDir, "index.html");
7306
7306
  const res = serveStaticFile(indexPath);
7307
7307
  if (res)
7308
7308
  return res;
@@ -8433,13 +8433,187 @@ init_comments();
8433
8433
  init_search();
8434
8434
  import chalk from "chalk";
8435
8435
  import { execSync as execSync2 } from "child_process";
8436
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
8437
- import { basename, dirname as dirname3, join as join3, resolve as resolve2 } from "path";
8436
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
8437
+ import { basename, dirname as dirname3, join as join4, resolve as resolve2 } from "path";
8438
8438
  import { fileURLToPath as fileURLToPath2 } from "url";
8439
+
8440
+ // src/lib/claude-tasks.ts
8441
+ init_tasks();
8442
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, readdirSync, writeFileSync } from "fs";
8443
+ import { join as join2 } from "path";
8444
+ var HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
8445
+ function getTaskListDir(taskListId) {
8446
+ return join2(HOME, ".claude", "tasks", taskListId);
8447
+ }
8448
+ function readHighWaterMark(dir) {
8449
+ const path = join2(dir, ".highwatermark");
8450
+ if (!existsSync2(path))
8451
+ return 1;
8452
+ const val = parseInt(readFileSync(path, "utf-8").trim(), 10);
8453
+ return isNaN(val) ? 1 : val;
8454
+ }
8455
+ function writeHighWaterMark(dir, value) {
8456
+ writeFileSync(join2(dir, ".highwatermark"), String(value));
8457
+ }
8458
+ function readClaudeTask(dir, filename) {
8459
+ try {
8460
+ const content = readFileSync(join2(dir, filename), "utf-8");
8461
+ return JSON.parse(content);
8462
+ } catch {
8463
+ return null;
8464
+ }
8465
+ }
8466
+ function writeClaudeTask(dir, task) {
8467
+ writeFileSync(join2(dir, `${task.id}.json`), JSON.stringify(task, null, 2) + `
8468
+ `);
8469
+ }
8470
+ function toClaudeStatus(status) {
8471
+ if (status === "pending" || status === "in_progress" || status === "completed") {
8472
+ return status;
8473
+ }
8474
+ return "completed";
8475
+ }
8476
+ function toSqliteStatus(status) {
8477
+ return status;
8478
+ }
8479
+ function taskToClaudeTask(task, claudeTaskId) {
8480
+ return {
8481
+ id: claudeTaskId,
8482
+ subject: task.title,
8483
+ description: task.description || "",
8484
+ activeForm: "",
8485
+ status: toClaudeStatus(task.status),
8486
+ owner: task.assigned_to || task.agent_id || "",
8487
+ blocks: [],
8488
+ blockedBy: [],
8489
+ metadata: {
8490
+ todos_id: task.id,
8491
+ priority: task.priority
8492
+ }
8493
+ };
8494
+ }
8495
+ function pushToClaudeTaskList(taskListId, projectId) {
8496
+ const dir = getTaskListDir(taskListId);
8497
+ if (!existsSync2(dir))
8498
+ mkdirSync2(dir, { recursive: true });
8499
+ const filter = {};
8500
+ if (projectId)
8501
+ filter["project_id"] = projectId;
8502
+ const tasks = listTasks(filter);
8503
+ const existingByTodosId = new Map;
8504
+ const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
8505
+ for (const f of files) {
8506
+ const ct = readClaudeTask(dir, f);
8507
+ if (ct?.metadata?.["todos_id"]) {
8508
+ existingByTodosId.set(ct.metadata["todos_id"], ct);
8509
+ }
8510
+ }
8511
+ let hwm = readHighWaterMark(dir);
8512
+ let pushed = 0;
8513
+ const errors = [];
8514
+ for (const task of tasks) {
8515
+ try {
8516
+ const existing = existingByTodosId.get(task.id);
8517
+ if (existing) {
8518
+ const updated = taskToClaudeTask(task, existing.id);
8519
+ updated.blocks = existing.blocks;
8520
+ updated.blockedBy = existing.blockedBy;
8521
+ updated.activeForm = existing.activeForm;
8522
+ writeClaudeTask(dir, updated);
8523
+ } else {
8524
+ const claudeId = String(hwm);
8525
+ hwm++;
8526
+ const ct = taskToClaudeTask(task, claudeId);
8527
+ writeClaudeTask(dir, ct);
8528
+ const current = getTask(task.id);
8529
+ if (current) {
8530
+ const newMeta = { ...current.metadata, claude_task_id: claudeId };
8531
+ updateTask(task.id, { version: current.version, metadata: newMeta });
8532
+ }
8533
+ }
8534
+ pushed++;
8535
+ } catch (e) {
8536
+ errors.push(`push ${task.id}: ${e instanceof Error ? e.message : String(e)}`);
8537
+ }
8538
+ }
8539
+ writeHighWaterMark(dir, hwm);
8540
+ return { pushed, pulled: 0, errors };
8541
+ }
8542
+ function pullFromClaudeTaskList(taskListId, projectId) {
8543
+ const dir = getTaskListDir(taskListId);
8544
+ if (!existsSync2(dir)) {
8545
+ return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
8546
+ }
8547
+ const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
8548
+ let pulled = 0;
8549
+ const errors = [];
8550
+ const filter = {};
8551
+ if (projectId)
8552
+ filter["project_id"] = projectId;
8553
+ const existingTasks = listTasks(filter);
8554
+ const byClaudeId = new Map;
8555
+ for (const t of existingTasks) {
8556
+ const cid = t.metadata["claude_task_id"];
8557
+ if (cid)
8558
+ byClaudeId.set(String(cid), t);
8559
+ }
8560
+ const byTodosId = new Map;
8561
+ for (const t of existingTasks) {
8562
+ byTodosId.set(t.id, t);
8563
+ }
8564
+ for (const f of files) {
8565
+ try {
8566
+ const ct = readClaudeTask(dir, f);
8567
+ if (!ct)
8568
+ continue;
8569
+ if (ct.metadata?.["_internal"])
8570
+ continue;
8571
+ const todosId = ct.metadata?.["todos_id"];
8572
+ const existingByMapping = byClaudeId.get(ct.id);
8573
+ const existingByTodos = todosId ? byTodosId.get(todosId) : undefined;
8574
+ const existing = existingByMapping || existingByTodos;
8575
+ if (existing) {
8576
+ updateTask(existing.id, {
8577
+ version: existing.version,
8578
+ title: ct.subject,
8579
+ description: ct.description || undefined,
8580
+ status: toSqliteStatus(ct.status),
8581
+ assigned_to: ct.owner || undefined,
8582
+ metadata: { ...existing.metadata, claude_task_id: ct.id }
8583
+ });
8584
+ } else {
8585
+ createTask({
8586
+ title: ct.subject,
8587
+ description: ct.description || undefined,
8588
+ status: toSqliteStatus(ct.status),
8589
+ assigned_to: ct.owner || undefined,
8590
+ project_id: projectId,
8591
+ metadata: { claude_task_id: ct.id },
8592
+ priority: ct.metadata?.["priority"] || "medium"
8593
+ });
8594
+ }
8595
+ pulled++;
8596
+ } catch (e) {
8597
+ errors.push(`pull ${f}: ${e instanceof Error ? e.message : String(e)}`);
8598
+ }
8599
+ }
8600
+ return { pushed: 0, pulled, errors };
8601
+ }
8602
+ function syncClaudeTaskList(taskListId, projectId) {
8603
+ const pullResult = pullFromClaudeTaskList(taskListId, projectId);
8604
+ const pushResult = pushToClaudeTaskList(taskListId, projectId);
8605
+ return {
8606
+ pushed: pushResult.pushed,
8607
+ pulled: pullResult.pulled,
8608
+ errors: [...pullResult.errors, ...pushResult.errors]
8609
+ };
8610
+ }
8611
+
8612
+ // src/cli/index.tsx
8439
8613
  function getPackageVersion2() {
8440
8614
  try {
8441
- const pkgPath = join3(dirname3(fileURLToPath2(import.meta.url)), "..", "..", "package.json");
8442
- return JSON.parse(readFileSync2(pkgPath, "utf-8")).version || "0.0.0";
8615
+ const pkgPath = join4(dirname3(fileURLToPath2(import.meta.url)), "..", "..", "package.json");
8616
+ return JSON.parse(readFileSync3(pkgPath, "utf-8")).version || "0.0.0";
8443
8617
  } catch {
8444
8618
  return "0.0.0";
8445
8619
  }
@@ -8909,6 +9083,107 @@ program2.command("export").description("Export tasks").option("-f, --format <for
8909
9083
  console.log(JSON.stringify(tasks, null, 2));
8910
9084
  }
8911
9085
  });
9086
+ program2.command("sync").description("Sync tasks with a Claude Code task list").option("--task-list <id>", "Claude Code task list ID (or env TODOS_CLAUDE_TASK_LIST)").option("--push", "One-way: push SQLite tasks to Claude task list").option("--pull", "One-way: pull Claude task list into SQLite").action((opts) => {
9087
+ const globalOpts = program2.opts();
9088
+ const projectId = autoProject(globalOpts);
9089
+ const taskListId = opts.taskList || process.env["TODOS_CLAUDE_TASK_LIST"];
9090
+ if (!taskListId) {
9091
+ console.error(chalk.red("Task list ID required. Use --task-list <id> or set TODOS_CLAUDE_TASK_LIST."));
9092
+ process.exit(1);
9093
+ }
9094
+ let result;
9095
+ if (opts.push && !opts.pull) {
9096
+ result = pushToClaudeTaskList(taskListId, projectId);
9097
+ } else if (opts.pull && !opts.push) {
9098
+ result = pullFromClaudeTaskList(taskListId, projectId);
9099
+ } else {
9100
+ result = syncClaudeTaskList(taskListId, projectId);
9101
+ }
9102
+ if (globalOpts.json) {
9103
+ output(result, true);
9104
+ return;
9105
+ }
9106
+ if (result.pulled > 0)
9107
+ console.log(chalk.green(`Pulled ${result.pulled} task(s) from Claude task list.`));
9108
+ if (result.pushed > 0)
9109
+ console.log(chalk.green(`Pushed ${result.pushed} task(s) to Claude task list.`));
9110
+ if (result.pulled === 0 && result.pushed === 0 && result.errors.length === 0) {
9111
+ console.log(chalk.dim("Nothing to sync."));
9112
+ }
9113
+ for (const err of result.errors) {
9114
+ console.error(chalk.red(` Error: ${err}`));
9115
+ }
9116
+ });
9117
+ var hooks = program2.command("hooks").description("Manage Claude Code hook integration");
9118
+ hooks.command("install").description("Install Claude Code hooks for auto-sync").option("--task-list <id>", "Claude Code task list ID (or env TODOS_CLAUDE_TASK_LIST)").action((opts) => {
9119
+ const taskListId = opts.taskList || process.env["TODOS_CLAUDE_TASK_LIST"];
9120
+ if (!taskListId) {
9121
+ console.error(chalk.red("Task list ID required. Use --task-list <id> or set TODOS_CLAUDE_TASK_LIST."));
9122
+ process.exit(1);
9123
+ }
9124
+ let todosBin = "todos";
9125
+ try {
9126
+ const p = execSync2("which todos", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
9127
+ if (p)
9128
+ todosBin = p;
9129
+ } catch {}
9130
+ const hooksDir = join4(process.cwd(), ".claude", "hooks");
9131
+ if (!existsSync4(hooksDir))
9132
+ mkdirSync3(hooksDir, { recursive: true });
9133
+ const hookScript = `#!/usr/bin/env bash
9134
+ # Auto-generated by: todos hooks install
9135
+ # Syncs todos with Claude Code task list on tool use events.
9136
+ # Reads hook JSON from stdin; determines sync direction from tool_name.
9137
+
9138
+ set -e
9139
+
9140
+ TASK_LIST="\${TODOS_CLAUDE_TASK_LIST:-${taskListId}}"
9141
+ TOOL_NAME=$(cat /dev/stdin | grep -o '"tool_name":"[^"]*"' | head -1 | cut -d'"' -f4 2>/dev/null || true)
9142
+
9143
+ case "$TOOL_NAME" in
9144
+ TaskCreate|TaskUpdate)
9145
+ ${todosBin} sync --pull --task-list "$TASK_LIST" 2>/dev/null || true
9146
+ ;;
9147
+ mcp__todos__*)
9148
+ ${todosBin} sync --push --task-list "$TASK_LIST" 2>/dev/null || true
9149
+ ;;
9150
+ *)
9151
+ # Unknown tool, run bidirectional sync
9152
+ ${todosBin} sync --task-list "$TASK_LIST" 2>/dev/null || true
9153
+ ;;
9154
+ esac
9155
+
9156
+ exit 0
9157
+ `;
9158
+ const hookPath = join4(hooksDir, "todos-sync.sh");
9159
+ writeFileSync2(hookPath, hookScript);
9160
+ execSync2(`chmod +x "${hookPath}"`);
9161
+ console.log(chalk.green(`Hook script created: ${hookPath}`));
9162
+ const settingsPath = join4(process.cwd(), ".claude", "settings.json");
9163
+ const settings = readJsonFile(settingsPath);
9164
+ if (!settings["hooks"]) {
9165
+ settings["hooks"] = {};
9166
+ }
9167
+ const hooksConfig = settings["hooks"];
9168
+ if (!hooksConfig["PostToolUse"]) {
9169
+ hooksConfig["PostToolUse"] = [];
9170
+ }
9171
+ const postToolUse = hooksConfig["PostToolUse"];
9172
+ const filtered = postToolUse.filter((h) => !(h["command"] || "").includes("todos-sync.sh"));
9173
+ filtered.push({
9174
+ matcher: "TaskCreate|TaskUpdate",
9175
+ command: hookPath
9176
+ });
9177
+ filtered.push({
9178
+ matcher: "mcp__todos__create_task|mcp__todos__update_task|mcp__todos__complete_task|mcp__todos__start_task",
9179
+ command: hookPath
9180
+ });
9181
+ hooksConfig["PostToolUse"] = filtered;
9182
+ writeJsonFile(settingsPath, settings);
9183
+ console.log(chalk.green(`Claude Code hooks configured in: ${settingsPath}`));
9184
+ console.log(chalk.dim(`Task list ID: ${taskListId}`));
9185
+ console.log(chalk.dim("Set TODOS_CLAUDE_TASK_LIST env var to override at runtime."));
9186
+ });
8912
9187
  program2.command("mcp").description("Start MCP server (stdio)").option("--register <agent>", "Register MCP server with an agent (claude, codex, gemini, all)").option("--unregister <agent>", "Unregister MCP server from an agent (claude, codex, gemini, all)").option("-g, --global", "Register/unregister globally (user-level) instead of project-level").action(async (opts) => {
8913
9188
  if (opts.register) {
8914
9189
  registerMcp(opts.register, opts.global);
@@ -8920,47 +9195,47 @@ program2.command("mcp").description("Start MCP server (stdio)").option("--regist
8920
9195
  }
8921
9196
  await Promise.resolve().then(() => (init_mcp(), exports_mcp));
8922
9197
  });
8923
- var HOME = process.env["HOME"] || process.env["USERPROFILE"] || "~";
9198
+ var HOME2 = process.env["HOME"] || process.env["USERPROFILE"] || "~";
8924
9199
  function getMcpBinaryPath() {
8925
9200
  try {
8926
9201
  const p = execSync2("which todos-mcp", { encoding: "utf-8" }).trim();
8927
9202
  if (p)
8928
9203
  return p;
8929
9204
  } catch {}
8930
- const bunBin = join3(HOME, ".bun", "bin", "todos-mcp");
8931
- if (existsSync3(bunBin))
9205
+ const bunBin = join4(HOME2, ".bun", "bin", "todos-mcp");
9206
+ if (existsSync4(bunBin))
8932
9207
  return bunBin;
8933
9208
  return "todos-mcp";
8934
9209
  }
8935
9210
  function readJsonFile(path) {
8936
- if (!existsSync3(path))
9211
+ if (!existsSync4(path))
8937
9212
  return {};
8938
9213
  try {
8939
- return JSON.parse(readFileSync2(path, "utf-8"));
9214
+ return JSON.parse(readFileSync3(path, "utf-8"));
8940
9215
  } catch {
8941
9216
  return {};
8942
9217
  }
8943
9218
  }
8944
9219
  function writeJsonFile(path, data) {
8945
9220
  const dir = dirname3(path);
8946
- if (!existsSync3(dir))
8947
- mkdirSync2(dir, { recursive: true });
8948
- writeFileSync(path, JSON.stringify(data, null, 2) + `
9221
+ if (!existsSync4(dir))
9222
+ mkdirSync3(dir, { recursive: true });
9223
+ writeFileSync2(path, JSON.stringify(data, null, 2) + `
8949
9224
  `);
8950
9225
  }
8951
9226
  function readTomlFile(path) {
8952
- if (!existsSync3(path))
9227
+ if (!existsSync4(path))
8953
9228
  return "";
8954
- return readFileSync2(path, "utf-8");
9229
+ return readFileSync3(path, "utf-8");
8955
9230
  }
8956
9231
  function writeTomlFile(path, content) {
8957
9232
  const dir = dirname3(path);
8958
- if (!existsSync3(dir))
8959
- mkdirSync2(dir, { recursive: true });
8960
- writeFileSync(path, content);
9233
+ if (!existsSync4(dir))
9234
+ mkdirSync3(dir, { recursive: true });
9235
+ writeFileSync2(path, content);
8961
9236
  }
8962
9237
  function registerClaude(binPath, global) {
8963
- const configPath = global ? join3(HOME, ".claude", ".mcp.json") : join3(process.cwd(), ".mcp.json");
9238
+ const configPath = global ? join4(HOME2, ".claude", ".mcp.json") : join4(process.cwd(), ".mcp.json");
8964
9239
  const config = readJsonFile(configPath);
8965
9240
  if (!config["mcpServers"]) {
8966
9241
  config["mcpServers"] = {};
@@ -8975,7 +9250,7 @@ function registerClaude(binPath, global) {
8975
9250
  console.log(chalk.green(`Claude Code (${scope}): registered in ${configPath}`));
8976
9251
  }
8977
9252
  function unregisterClaude(global) {
8978
- const configPath = global ? join3(HOME, ".claude", ".mcp.json") : join3(process.cwd(), ".mcp.json");
9253
+ const configPath = global ? join4(HOME2, ".claude", ".mcp.json") : join4(process.cwd(), ".mcp.json");
8979
9254
  const config = readJsonFile(configPath);
8980
9255
  const servers = config["mcpServers"];
8981
9256
  if (!servers || !("todos" in servers)) {
@@ -8988,7 +9263,7 @@ function unregisterClaude(global) {
8988
9263
  console.log(chalk.green(`Claude Code (${scope}): unregistered from ${configPath}`));
8989
9264
  }
8990
9265
  function registerCodex(binPath) {
8991
- const configPath = join3(HOME, ".codex", "config.toml");
9266
+ const configPath = join4(HOME2, ".codex", "config.toml");
8992
9267
  let content = readTomlFile(configPath);
8993
9268
  content = removeTomlBlock(content, "mcp_servers.todos");
8994
9269
  const block = `
@@ -9002,7 +9277,7 @@ args = []
9002
9277
  console.log(chalk.green(`Codex CLI: registered in ${configPath}`));
9003
9278
  }
9004
9279
  function unregisterCodex() {
9005
- const configPath = join3(HOME, ".codex", "config.toml");
9280
+ const configPath = join4(HOME2, ".codex", "config.toml");
9006
9281
  let content = readTomlFile(configPath);
9007
9282
  if (!content.includes("[mcp_servers.todos]")) {
9008
9283
  console.log(chalk.dim(`Codex CLI: todos not found in ${configPath}`));
@@ -9035,7 +9310,7 @@ function removeTomlBlock(content, blockName) {
9035
9310
  `);
9036
9311
  }
9037
9312
  function registerGemini(binPath) {
9038
- const configPath = join3(HOME, ".gemini", "settings.json");
9313
+ const configPath = join4(HOME2, ".gemini", "settings.json");
9039
9314
  const config = readJsonFile(configPath);
9040
9315
  if (!config["mcpServers"]) {
9041
9316
  config["mcpServers"] = {};
@@ -9049,7 +9324,7 @@ function registerGemini(binPath) {
9049
9324
  console.log(chalk.green(`Gemini CLI: registered in ${configPath}`));
9050
9325
  }
9051
9326
  function unregisterGemini() {
9052
- const configPath = join3(HOME, ".gemini", "settings.json");
9327
+ const configPath = join4(HOME2, ".gemini", "settings.json");
9053
9328
  const config = readJsonFile(configPath);
9054
9329
  const servers = config["mcpServers"];
9055
9330
  if (!servers || !("todos" in servers)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/todos",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",