@hasna/todos 0.11.22 → 0.11.24

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 CHANGED
@@ -3623,7 +3623,9 @@ function getTodosGlobalDir() {
3623
3623
  const home = process.env["HOME"] || HOME;
3624
3624
  const newDir = join3(home, ".hasna", "todos");
3625
3625
  const legacyDir = join3(home, ".todos");
3626
- if (!existsSync3(newDir) && existsSync3(legacyDir))
3626
+ const newConfig = join3(newDir, "config.json");
3627
+ const legacyConfig = join3(legacyDir, "config.json");
3628
+ if (!existsSync3(newConfig) && existsSync3(legacyConfig))
3627
3629
  return legacyDir;
3628
3630
  return newDir;
3629
3631
  }
@@ -4426,15 +4428,16 @@ function taskFromTemplate(templateId, overrides = {}, db) {
4426
4428
  const t = getTemplate(templateId, db);
4427
4429
  if (!t)
4428
4430
  throw new Error(`Template not found: ${templateId}`);
4431
+ const cleanOverrides = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== undefined));
4429
4432
  return {
4430
- title: overrides.title || t.title_pattern,
4431
- description: overrides.description ?? t.description ?? undefined,
4432
- priority: overrides.priority ?? t.priority,
4433
- tags: overrides.tags ?? t.tags,
4434
- project_id: overrides.project_id ?? t.project_id ?? undefined,
4435
- plan_id: overrides.plan_id ?? t.plan_id ?? undefined,
4436
- metadata: overrides.metadata ?? t.metadata,
4437
- ...overrides
4433
+ title: cleanOverrides.title || t.title_pattern,
4434
+ description: cleanOverrides.description ?? t.description ?? undefined,
4435
+ priority: cleanOverrides.priority ?? t.priority,
4436
+ tags: cleanOverrides.tags ?? t.tags,
4437
+ project_id: cleanOverrides.project_id ?? t.project_id ?? undefined,
4438
+ plan_id: cleanOverrides.plan_id ?? t.plan_id ?? undefined,
4439
+ metadata: cleanOverrides.metadata ?? t.metadata,
4440
+ ...cleanOverrides
4438
4441
  };
4439
4442
  }
4440
4443
  function addTemplateTasks(templateId, tasks, db) {
@@ -33484,7 +33487,7 @@ program2.command("plans").description("List and manage plans").option("--add <na
33484
33487
  console.log(`${chalk3.dim(p.id.slice(0, 8))} ${chalk3.bold(p.name)} ${chalk3.cyan(`[${p.status}]`)}${desc}`);
33485
33488
  }
33486
33489
  });
33487
- program2.command("templates").description("List and manage task templates").option("--add <name>", "Create a template").option("--title <pattern>", "Title pattern (with --add)").option("-d, --description <text>", "Default description").option("-p, --priority <level>", "Default priority").option("-t, --tags <tags>", "Default tags (comma-separated)").option("--delete <id>", "Delete a template").option("--update <id>", "Update a template").option("--use <id>", "Create a task from a template").action((opts) => {
33490
+ program2.command("templates").description("List and manage task templates").option("--add <name>", "Create a template").option("--title <pattern>", "Title pattern (with --add)").option("-d, --description <text>", "Default description").option("-p, --priority <level>", "Default priority").option("-t, --tags <tags>", "Default tags (comma-separated)").option("--delete <id>", "Delete a template").option("--update <id>", "Update a template").option("--use <id>", "Create a task from a template").option("--var <vars...>", "Variable substitutions: key=value (e.g. --var feature=login)").action((opts) => {
33488
33491
  const globalOpts = program2.opts();
33489
33492
  const { createTemplate: createTemplate2, listTemplates: listTemplates2, deleteTemplate: deleteTemplate2, updateTemplate: updateTemplate2, taskFromTemplate: taskFromTemplate2 } = (init_templates(), __toCommonJS(exports_templates));
33490
33493
  if (opts.add) {
@@ -33546,12 +33549,30 @@ program2.command("templates").description("List and manage task templates").opti
33546
33549
  }
33547
33550
  if (opts.use) {
33548
33551
  try {
33552
+ const variables = {};
33553
+ if (opts.var) {
33554
+ for (const v of opts.var) {
33555
+ const eq = v.indexOf("=");
33556
+ if (eq === -1) {
33557
+ console.error(chalk3.red(`Invalid variable format: ${v} (expected key=value)`));
33558
+ process.exit(1);
33559
+ }
33560
+ variables[v.slice(0, eq)] = v.slice(eq + 1);
33561
+ }
33562
+ }
33549
33563
  const input = taskFromTemplate2(opts.use, {
33550
33564
  title: opts.title,
33551
33565
  description: opts.description,
33552
33566
  priority: opts.priority
33553
33567
  });
33554
- const task = createTask({ ...input, project_id: input.project_id || autoProject(globalOpts) });
33568
+ if (input.title) {
33569
+ let title = input.title;
33570
+ for (const [k, v] of Object.entries(variables)) {
33571
+ title = title.replace(new RegExp(`\\{${k}\\}`, "g"), v);
33572
+ }
33573
+ input.title = title;
33574
+ }
33575
+ const task = createTask({ ...input, agent_id: globalOpts.agent, project_id: input.project_id || autoProject(globalOpts) });
33555
33576
  if (globalOpts.json) {
33556
33577
  output(task, true);
33557
33578
  } else {
@@ -33640,16 +33661,17 @@ program2.command("template-export <id>").alias("templates-export").description("
33640
33661
  handleError(e);
33641
33662
  }
33642
33663
  });
33643
- program2.command("template-import").alias("templates-import").description("Import a template from a JSON file").option("--file <path>", "Path to template JSON file").action((opts) => {
33664
+ program2.command("template-import [file]").alias("templates-import").description("Import a template from a JSON file").option("--file <path>", "Path to template JSON file (alternative to positional arg)").action((file, opts) => {
33644
33665
  const globalOpts = program2.opts();
33645
33666
  const { importTemplate: importTemplate2 } = (init_templates(), __toCommonJS(exports_templates));
33646
33667
  const { readFileSync: readFileSync8 } = __require("fs");
33647
33668
  try {
33648
- if (!opts.file) {
33649
- console.error(chalk3.red("--file is required"));
33669
+ const filePath = file || opts.file;
33670
+ if (!filePath) {
33671
+ console.error(chalk3.red("Provide a file path: todos template-import <file> or --file <path>"));
33650
33672
  process.exit(1);
33651
33673
  }
33652
- const content = readFileSync8(opts.file, "utf-8");
33674
+ const content = readFileSync8(filePath, "utf-8");
33653
33675
  const json2 = JSON.parse(content);
33654
33676
  const template = importTemplate2(json2);
33655
33677
  if (globalOpts.json) {
@@ -34471,8 +34493,11 @@ program2.command("focus [project]").description("Focus on a project (or clear fo
34471
34493
  }
34472
34494
  const db = getDatabase();
34473
34495
  if (project) {
34474
- const { getProjectByPath: getProjectByPath2, getProjectByName } = (init_projects(), __toCommonJS(exports_projects));
34475
- const p = getProjectByPath2(process.cwd(), db) || getProjectByName(project, db);
34496
+ const { getProjectByPath: getProjectByPath2 } = (init_projects(), __toCommonJS(exports_projects));
34497
+ const p = getProjectByPath2(project, db) || (() => {
34498
+ const id = resolvePartialId(db, "projects", project);
34499
+ return id ? db.query("SELECT * FROM projects WHERE id = ?").get(id) : null;
34500
+ })() || db.query("SELECT * FROM projects WHERE name = ? OR task_list_id = ?").get(project, project);
34476
34501
  const projectId = p?.id || project;
34477
34502
  db.run("UPDATE agents SET active_project_id = ? WHERE id = ? OR name = ?", [projectId, agentId, agentId]);
34478
34503
  console.log(chalk3.green(`Focused on: ${p?.name || projectId}`));
@@ -34732,7 +34757,10 @@ Updated to ${latestVersion}!`));
34732
34757
  });
34733
34758
  program2.command("config").description("View or update configuration").option("--get <key>", "Get a config value").option("--set <key=value>", "Set a config value (e.g. completion_guard.enabled=true)").action((opts) => {
34734
34759
  const globalOpts = program2.opts();
34735
- const configPath = join12(process.env["HOME"] || "~", ".todos", "config.json");
34760
+ const home = process.env["HOME"] || "~";
34761
+ const newPath = join12(home, ".hasna", "todos", "config.json");
34762
+ const legacyPath = join12(home, ".todos", "config.json");
34763
+ const configPath = !existsSync10(newPath) && existsSync10(legacyPath) ? legacyPath : newPath;
34736
34764
  if (opts.get) {
34737
34765
  const config2 = loadConfig();
34738
34766
  const keys = opts.get.split(".");
@@ -34961,10 +34989,15 @@ program2.command("dashboard").description("Live-updating dashboard showing proje
34961
34989
  render2(React.createElement(Dashboard2, { projectId, refreshMs: parseInt(opts.refresh, 10) }));
34962
34990
  });
34963
34991
  program2.command("next").description("Show the best pending task to work on next").option("--agent <id>", "Prefer tasks assigned to this agent").option("--project <id>", "Filter to project").option("--json", "Output as JSON").action(async (opts) => {
34992
+ const globalOpts = program2.opts();
34964
34993
  const db = getDatabase();
34965
34994
  const filters = {};
34966
- if (opts.project)
34967
- filters.project_id = opts.project;
34995
+ const projectInput = opts.project || globalOpts.project;
34996
+ if (projectInput) {
34997
+ const pid = autoProject({ project: projectInput }) || resolvePartialId(db, "projects", projectInput) || db.query("SELECT id FROM projects WHERE path = ? OR name = ? OR task_list_id = ?").get(projectInput, projectInput, projectInput)?.id;
34998
+ if (pid)
34999
+ filters.project_id = pid;
35000
+ }
34968
35001
  const task = getNextTask(opts.agent, Object.keys(filters).length ? filters : undefined, db);
34969
35002
  if (!task) {
34970
35003
  console.log(chalk3.dim("No tasks available."));
@@ -35530,7 +35563,7 @@ program2.command("report").description("Analytics report: task activity, complet
35530
35563
  `);
35531
35564
  lines.push(`| Metric | Value |`);
35532
35565
  lines.push(`|--------|-------|`);
35533
- lines.push(`| Active tasks | ${all.length} total (${stats.pending} pending, ${stats.in_progress} active) |`);
35566
+ lines.push(`| Active tasks | ${all.length} total (${stats.by_status?.pending ?? 0} pending, ${stats.by_status?.in_progress ?? 0} active) |`);
35534
35567
  lines.push(`| Changed (${days}d) | ${changed.length} tasks |`);
35535
35568
  lines.push(`| Completed (${days}d) | ${completed.length} (${completionRate}% rate) |`);
35536
35569
  lines.push(`| Failed (${days}d) | ${failed.length} |`);
@@ -35539,7 +35572,7 @@ program2.command("report").description("Analytics report: task activity, complet
35539
35572
  } else {
35540
35573
  lines.push(chalk3.bold(`todos report \u2014 last ${days} day${days !== 1 ? "s" : ""}`));
35541
35574
  lines.push("");
35542
- lines.push(` Total: ${chalk3.bold(String(all.length))} tasks (${chalk3.yellow(String(stats.pending))} pending, ${chalk3.blue(String(stats.in_progress))} active)`);
35575
+ lines.push(` Total: ${chalk3.bold(String(all.length))} tasks (${chalk3.yellow(String(stats.by_status?.pending ?? 0))} pending, ${chalk3.blue(String(stats.by_status?.in_progress ?? 0))} active)`);
35543
35576
  lines.push(` Changed: ${chalk3.bold(String(changed.length))} in period`);
35544
35577
  lines.push(` Completed: ${chalk3.green(String(completed.length))} (${completionRate}% rate)`);
35545
35578
  if (failed.length > 0)
@@ -35549,7 +35582,7 @@ program2.command("report").description("Analytics report: task activity, complet
35549
35582
  if (Object.keys(byAgent).length > 0) {
35550
35583
  lines.push(` By agent: ${Object.entries(byAgent).map(([a, n]) => `${a}=${n}`).join(" ")}`);
35551
35584
  }
35552
- if (stats.in_progress > 0)
35585
+ if ((stats.by_status?.in_progress ?? 0) > 0)
35553
35586
  lines.push(` Stale risk: check \`todos stale\` for stuck tasks`);
35554
35587
  }
35555
35588
  console.log(lines.join(`
package/dist/index.js CHANGED
@@ -1850,7 +1850,9 @@ function getTodosGlobalDir() {
1850
1850
  const home = process.env["HOME"] || HOME;
1851
1851
  const newDir = join3(home, ".hasna", "todos");
1852
1852
  const legacyDir = join3(home, ".todos");
1853
- if (!existsSync3(newDir) && existsSync3(legacyDir))
1853
+ const newConfig = join3(newDir, "config.json");
1854
+ const legacyConfig = join3(legacyDir, "config.json");
1855
+ if (!existsSync3(newConfig) && existsSync3(legacyConfig))
1854
1856
  return legacyDir;
1855
1857
  return newDir;
1856
1858
  }
@@ -2553,15 +2555,16 @@ function taskFromTemplate(templateId, overrides = {}, db) {
2553
2555
  const t = getTemplate(templateId, db);
2554
2556
  if (!t)
2555
2557
  throw new Error(`Template not found: ${templateId}`);
2558
+ const cleanOverrides = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== undefined));
2556
2559
  return {
2557
- title: overrides.title || t.title_pattern,
2558
- description: overrides.description ?? t.description ?? undefined,
2559
- priority: overrides.priority ?? t.priority,
2560
- tags: overrides.tags ?? t.tags,
2561
- project_id: overrides.project_id ?? t.project_id ?? undefined,
2562
- plan_id: overrides.plan_id ?? t.plan_id ?? undefined,
2563
- metadata: overrides.metadata ?? t.metadata,
2564
- ...overrides
2560
+ title: cleanOverrides.title || t.title_pattern,
2561
+ description: cleanOverrides.description ?? t.description ?? undefined,
2562
+ priority: cleanOverrides.priority ?? t.priority,
2563
+ tags: cleanOverrides.tags ?? t.tags,
2564
+ project_id: cleanOverrides.project_id ?? t.project_id ?? undefined,
2565
+ plan_id: cleanOverrides.plan_id ?? t.plan_id ?? undefined,
2566
+ metadata: cleanOverrides.metadata ?? t.metadata,
2567
+ ...cleanOverrides
2565
2568
  };
2566
2569
  }
2567
2570
  function addTemplateTasks(templateId, tasks, db) {
package/dist/mcp/index.js CHANGED
@@ -12602,7 +12602,9 @@ function getTodosGlobalDir() {
12602
12602
  const home = process.env["HOME"] || HOME;
12603
12603
  const newDir = join9(home, ".hasna", "todos");
12604
12604
  const legacyDir = join9(home, ".todos");
12605
- if (!existsSync9(newDir) && existsSync9(legacyDir))
12605
+ const newConfig = join9(newDir, "config.json");
12606
+ const legacyConfig = join9(legacyDir, "config.json");
12607
+ if (!existsSync9(newConfig) && existsSync9(legacyConfig))
12606
12608
  return legacyDir;
12607
12609
  return newDir;
12608
12610
  }
@@ -13283,15 +13285,16 @@ function taskFromTemplate(templateId, overrides = {}, db) {
13283
13285
  const t = getTemplate(templateId, db);
13284
13286
  if (!t)
13285
13287
  throw new Error(`Template not found: ${templateId}`);
13288
+ const cleanOverrides = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== undefined));
13286
13289
  return {
13287
- title: overrides.title || t.title_pattern,
13288
- description: overrides.description ?? t.description ?? undefined,
13289
- priority: overrides.priority ?? t.priority,
13290
- tags: overrides.tags ?? t.tags,
13291
- project_id: overrides.project_id ?? t.project_id ?? undefined,
13292
- plan_id: overrides.plan_id ?? t.plan_id ?? undefined,
13293
- metadata: overrides.metadata ?? t.metadata,
13294
- ...overrides
13290
+ title: cleanOverrides.title || t.title_pattern,
13291
+ description: cleanOverrides.description ?? t.description ?? undefined,
13292
+ priority: cleanOverrides.priority ?? t.priority,
13293
+ tags: cleanOverrides.tags ?? t.tags,
13294
+ project_id: cleanOverrides.project_id ?? t.project_id ?? undefined,
13295
+ plan_id: cleanOverrides.plan_id ?? t.plan_id ?? undefined,
13296
+ metadata: cleanOverrides.metadata ?? t.metadata,
13297
+ ...cleanOverrides
13295
13298
  };
13296
13299
  }
13297
13300
  function addTemplateTasks(templateId, tasks, db) {
@@ -1403,7 +1403,9 @@ function getTodosGlobalDir() {
1403
1403
  const home = process.env["HOME"] || HOME;
1404
1404
  const newDir = join2(home, ".hasna", "todos");
1405
1405
  const legacyDir = join2(home, ".todos");
1406
- if (!existsSync3(newDir) && existsSync3(legacyDir))
1406
+ const newConfig = join2(newDir, "config.json");
1407
+ const legacyConfig = join2(legacyDir, "config.json");
1408
+ if (!existsSync3(newConfig) && existsSync3(legacyConfig))
1407
1409
  return legacyDir;
1408
1410
  return newDir;
1409
1411
  }
@@ -2151,15 +2153,16 @@ function taskFromTemplate(templateId, overrides = {}, db) {
2151
2153
  const t = getTemplate(templateId, db);
2152
2154
  if (!t)
2153
2155
  throw new Error(`Template not found: ${templateId}`);
2156
+ const cleanOverrides = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== undefined));
2154
2157
  return {
2155
- title: overrides.title || t.title_pattern,
2156
- description: overrides.description ?? t.description ?? undefined,
2157
- priority: overrides.priority ?? t.priority,
2158
- tags: overrides.tags ?? t.tags,
2159
- project_id: overrides.project_id ?? t.project_id ?? undefined,
2160
- plan_id: overrides.plan_id ?? t.plan_id ?? undefined,
2161
- metadata: overrides.metadata ?? t.metadata,
2162
- ...overrides
2158
+ title: cleanOverrides.title || t.title_pattern,
2159
+ description: cleanOverrides.description ?? t.description ?? undefined,
2160
+ priority: cleanOverrides.priority ?? t.priority,
2161
+ tags: cleanOverrides.tags ?? t.tags,
2162
+ project_id: cleanOverrides.project_id ?? t.project_id ?? undefined,
2163
+ plan_id: cleanOverrides.plan_id ?? t.plan_id ?? undefined,
2164
+ metadata: cleanOverrides.metadata ?? t.metadata,
2165
+ ...cleanOverrides
2163
2166
  };
2164
2167
  }
2165
2168
  function addTemplateTasks(templateId, tasks, db) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/todos",
3
- "version": "0.11.22",
3
+ "version": "0.11.24",
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",