@hasna/todos 0.9.55 → 0.9.56

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 +80 -2
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -13262,7 +13262,7 @@ program2.command("add <title>").description("Create a new task").option("-d, --d
13262
13262
  console.log(formatTaskLine(task));
13263
13263
  }
13264
13264
  });
13265
- program2.command("list").description("List tasks").option("-s, --status <status>", "Filter by status").option("-p, --priority <priority>", "Filter by priority").option("--assigned <agent>", "Filter by assigned agent").option("--tags <tags>", "Filter by tags (comma-separated)").option("--tag <tags>", "Filter by tags (alias for --tags)").option("-a, --all", "Show all tasks (including completed/cancelled)").option("--list <id>", "Filter by task list ID").option("--task-list <id>", "Filter by task list ID (alias for --list)").option("--project-name <name>", "Filter by project name").option("--agent-name <name>", "Filter by agent name/assigned").option("--sort <field>", "Sort by: updated, created, priority, status").option("--format <fmt>", "Output format: table (default), compact, csv, json").action((opts) => {
13265
+ program2.command("list").description("List tasks").option("-s, --status <status>", "Filter by status").option("-p, --priority <priority>", "Filter by priority").option("--assigned <agent>", "Filter by assigned agent").option("--tags <tags>", "Filter by tags (comma-separated)").option("--tag <tags>", "Filter by tags (alias for --tags)").option("-a, --all", "Show all tasks (including completed/cancelled)").option("--list <id>", "Filter by task list ID").option("--task-list <id>", "Filter by task list ID (alias for --list)").option("--project-name <name>", "Filter by project name").option("--agent-name <name>", "Filter by agent name/assigned").option("--sort <field>", "Sort by: updated, created, priority, status").option("--format <fmt>", "Output format: table (default), compact, csv, json").option("--due-today", "Only tasks due today or earlier").option("--overdue", "Only overdue tasks (past due_at)").option("--recurring", "Only recurring tasks").option("--limit <n>", "Max tasks to return").action((opts) => {
13266
13266
  const globalOpts = program2.opts();
13267
13267
  opts.tags = opts.tags || opts.tag;
13268
13268
  opts.list = opts.list || opts.taskList;
@@ -13303,7 +13303,20 @@ program2.command("list").description("List tasks").option("-s, --status <status>
13303
13303
  if (opts.agentName) {
13304
13304
  filter["assigned_to"] = opts.agentName;
13305
13305
  }
13306
- const tasks = listTasks(filter);
13306
+ if (opts.recurring)
13307
+ filter["has_recurrence"] = true;
13308
+ if (opts.limit)
13309
+ filter["limit"] = parseInt(opts.limit, 10);
13310
+ let tasks = listTasks(filter);
13311
+ if (opts.dueToday) {
13312
+ const todayEnd = new Date;
13313
+ todayEnd.setHours(23, 59, 59, 999);
13314
+ tasks = tasks.filter((t) => t.due_at && t.due_at <= todayEnd.toISOString());
13315
+ }
13316
+ if (opts.overdue) {
13317
+ const now2 = new Date().toISOString();
13318
+ tasks = tasks.filter((t) => t.due_at && t.due_at < now2 && t.status !== "completed");
13319
+ }
13307
13320
  if (opts.sort) {
13308
13321
  const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
13309
13322
  tasks.sort((a, b) => {
@@ -14908,6 +14921,71 @@ program2.command("summary").description("Generate a markdown summary of recent t
14908
14921
  console.log(lines.join(`
14909
14922
  `));
14910
14923
  });
14924
+ program2.command("report").description("Analytics report: task activity, completion rates, agent breakdown").option("--days <n>", "Days to include in report", "7").option("--project <id>", "Filter to project").option("--markdown", "Output as markdown").option("--json", "Output as JSON").action(async (opts) => {
14925
+ const globalOpts = program2.opts();
14926
+ const db = getDatabase();
14927
+ const days = parseInt(opts.days, 10);
14928
+ const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
14929
+ const projectId = opts.project || autoProject(globalOpts);
14930
+ const filter = {};
14931
+ if (projectId)
14932
+ filter.project_id = projectId;
14933
+ const { getTasksChangedSince: getTasksChangedSince2, getTaskStats: getTaskStats2 } = (init_tasks(), __toCommonJS(exports_tasks));
14934
+ const changed = getTasksChangedSince2(since, Object.keys(filter).length ? filter : undefined, db);
14935
+ const completed = changed.filter((t) => t.status === "completed");
14936
+ const failed = changed.filter((t) => t.status === "failed");
14937
+ const all = listTasks(filter);
14938
+ const stats = getTaskStats2(Object.keys(filter).length ? filter : undefined, db);
14939
+ const byDay = {};
14940
+ for (const t of changed) {
14941
+ const day = t.updated_at.slice(0, 10);
14942
+ byDay[day] = (byDay[day] || 0) + 1;
14943
+ }
14944
+ const dayValues = Object.values(byDay);
14945
+ const maxDay = Math.max(...dayValues, 1);
14946
+ const sparkline = dayValues.map((v) => "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588"[Math.min(7, Math.floor(v / maxDay * 7))] || "\u2581").join("");
14947
+ const byAgent = {};
14948
+ for (const t of completed) {
14949
+ const agent = t.assigned_to || "unassigned";
14950
+ byAgent[agent] = (byAgent[agent] || 0) + 1;
14951
+ }
14952
+ const completionRate = changed.length > 0 ? Math.round(completed.length / changed.length * 100) : 0;
14953
+ if (opts.json || globalOpts.json) {
14954
+ console.log(JSON.stringify({ days, period_since: since, changed: changed.length, completed: completed.length, failed: failed.length, completion_rate: completionRate, total: all.length, stats, by_agent: byAgent, by_day: byDay }, null, 2));
14955
+ return;
14956
+ }
14957
+ const lines = [];
14958
+ if (opts.markdown) {
14959
+ lines.push(`## Todos Report \u2014 last ${days} days`);
14960
+ lines.push(`*${new Date().toLocaleDateString()}*
14961
+ `);
14962
+ lines.push(`| Metric | Value |`);
14963
+ lines.push(`|--------|-------|`);
14964
+ lines.push(`| Active tasks | ${all.length} total (${stats.pending} pending, ${stats.in_progress} active) |`);
14965
+ lines.push(`| Changed (${days}d) | ${changed.length} tasks |`);
14966
+ lines.push(`| Completed (${days}d) | ${completed.length} (${completionRate}% rate) |`);
14967
+ lines.push(`| Failed (${days}d) | ${failed.length} |`);
14968
+ if (sparkline)
14969
+ lines.push(`| Activity | \`${sparkline}\` |`);
14970
+ } else {
14971
+ lines.push(chalk.bold(`todos report \u2014 last ${days} day${days !== 1 ? "s" : ""}`));
14972
+ lines.push("");
14973
+ lines.push(` Total: ${chalk.bold(String(all.length))} tasks (${chalk.yellow(String(stats.pending))} pending, ${chalk.blue(String(stats.in_progress))} active)`);
14974
+ lines.push(` Changed: ${chalk.bold(String(changed.length))} in period`);
14975
+ lines.push(` Completed: ${chalk.green(String(completed.length))} (${completionRate}% rate)`);
14976
+ if (failed.length > 0)
14977
+ lines.push(` Failed: ${chalk.red(String(failed.length))}`);
14978
+ if (sparkline)
14979
+ lines.push(` Activity: ${chalk.dim(sparkline)}`);
14980
+ if (Object.keys(byAgent).length > 0) {
14981
+ lines.push(` By agent: ${Object.entries(byAgent).map(([a, n]) => `${a}=${n}`).join(" ")}`);
14982
+ }
14983
+ if (stats.in_progress > 0)
14984
+ lines.push(` Stale risk: check \`todos stale\` for stuck tasks`);
14985
+ }
14986
+ console.log(lines.join(`
14987
+ `));
14988
+ });
14911
14989
  program2.action(async () => {
14912
14990
  if (process.stdout.isTTY) {
14913
14991
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/todos",
3
- "version": "0.9.55",
3
+ "version": "0.9.56",
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",