文件预览

tasks.ts

查看 TickTick CLI 技能包中的文件内容。

文件内容

scripts/commands/tasks.ts

import { api, PRIORITY_REVERSE, type Task } from "../api";

interface TaskWithProject extends Task {
  projectName?: string;
}

interface TasksOptions {
  list?: string;
  status?: "pending" | "completed";
  json?: boolean;
}

function formatDueDate(dateStr: string): string {
  const date = new Date(dateStr);
  const now = new Date();
  const tomorrow = new Date(now);
  tomorrow.setDate(tomorrow.getDate() + 1);

  // Check if it's today or tomorrow
  if (date.toDateString() === now.toDateString()) {
    return "today";
  }
  if (date.toDateString() === tomorrow.toDateString()) {
    return "tomorrow";
  }

  // Format as "Jan 7" style
  return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
}

function formatTask(task: TaskWithProject, showProject = false): string {
  const status = task.status === 2 ? "✓" : "○";
  const priorityIndicator =
    task.priority === 5
      ? "!!!"
      : task.priority === 3
        ? "!!"
        : task.priority === 1
          ? "!"
          : "";
  const dueStr = task.dueDate ? ` - due ${formatDueDate(task.dueDate)}` : "";
  const projectStr = showProject && task.projectName ? ` (${task.projectName})` : "";
  const shortId = task.id.slice(0, 8);

  return `${status} [${shortId}] ${priorityIndicator}${task.title}${projectStr}${dueStr}`;
}

export async function tasksCommand(options: TasksOptions): Promise<void> {
  try {
    const projects = await api.listProjects();
    const projectMap = new Map(projects.map((p) => [p.id, p.name]));

    let searchProjects = projects;
    if (options.list) {
      const project = await api.findProjectByName(options.list);
      if (!project) {
        console.error(`Project not found: ${options.list}`);
        process.exit(1);
      }
      searchProjects = [project];
    }

    const tasksWithProjects: TaskWithProject[] = [];
    for (const project of searchProjects) {
      try {
        const data = await api.getProjectData(project.id);
        if (data.tasks) {
          for (const task of data.tasks) {
            tasksWithProjects.push({
              ...task,
              projectName: projectMap.get(task.projectId) || task.projectId,
            });
          }
        }
      } catch {
        continue;
      }
    }

    // Filter by status if specified
    let filteredTasks = tasksWithProjects;
    if (options.status === "pending") {
      filteredTasks = tasksWithProjects.filter((t) => t.status !== 2);
    } else if (options.status === "completed") {
      filteredTasks = tasksWithProjects.filter((t) => t.status === 2);
    }

    if (options.json) {
      console.log(JSON.stringify(filteredTasks, null, 2));
      return;
    }

    if (filteredTasks.length === 0) {
      console.log("No tasks found.");
      return;
    }

    console.log(`\nTasks (${filteredTasks.length}):\n`);
    for (const task of filteredTasks) {
      console.log(formatTask(task, !options.list));
    }
  } catch (error) {
    console.error(
      `Error: ${error instanceof Error ? error.message : String(error)}`
    );
    process.exit(1);
  }
}