Todo-CLI 交互式待办工具 v2.0版本

TodoCLI v2.0.1 完整可运行源码

说明:v2.0.1 基于 v2.0 修复并增强,包含 JSON 存储 + CLI 交互,支持空格/等号两种参数格式,完善注释与异常处理。

目录结构

复制代码
├── .env.example          # 环境变量模板
├── package.json          # 项目依赖
├── tsconfig.json         # TypeScript 配置
├── data/                 # 运行时自动生成 tasks.json
├── docs/
│   └── v2.0.md           # 使用文档
└── src/
    ├── main.ts           # 程序入口
    ├── config/
    │   ├── env.ts        # 环境配置加载
    │   └── bootstrap.ts  # 存储实例工厂
    ├── core/
    │   ├── types.ts      # 核心类型定义
    │   ├── errors.ts     # 自定义异常
    │   └── storage/
    │       ├── base.ts       # 存储抽象接口
    │       ├── json.store.ts # JSON 文件存储实现
    │       └── factory.ts    # 存储工厂(预留)
    ├── commands/
    │   ├── index.ts      # 命令注册表
    │   ├── add.ts        # 添加任务
    │   ├── list.ts       # 查看列表
    │   ├── done.ts       # 标记完成
    │   ├── update.ts     # 修改任务
    │   ├── delete.ts     # 删除任务
    │   └── clear.ts      # 清空已完成
    ├── cli/
    │   ├── parser.ts     # 输入解析器
    │   └── run.ts        # 交互式终端
    └── utils/
        ├── logger.ts     # 彩色日志
        └── response.ts   # API 返回封装(预留)

1. .env.example

复制代码
# 运行模式 cli / api (v2.4才支持api)
RUN_MODE=cli
# 存储模式 json/sqlite/drizzle-sqlite/drizzle-mysql
STORAGE_MODE=json
# 调试堆栈输出 true/false
DEBUG=false
​
# SQLite 配置 v2.1使用
SQLITE_FILE=data/todo-sqlite.db
​
# Drizzle SQLite v2.2使用
DRIZZLE_SQLITE_FILE=data/drizzle-sqlite.db
​
# MySQL v2.3使用
MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=
MYSQL_DB=todo

2. package.json

复制代码
{
  "name": "todo-cli",
  "version": "2.0.1",
  "type": "module",
  "scripts": {
    "cli": "bun src/main.ts"
  },
  "dependencies": {
    "dotenv": "^16.4.5"
  },
  "devDependencies": {
    "@types/bun": "latest"
  }
}

3. tsconfig.json

复制代码
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"]
}

4. src/main.ts

复制代码
/**
 * TodoCLI v2.0 程序入口
 *
 * 启动流程:
 * 1. 从 .env 加载全局配置
 * 2. 根据 storageMode 创建对应的存储实例
 * 3. 根据 runMode 启动对应的服务(v2.0 仅支持 cli)
 */
​
import { loadConfig } from "./config/env";
import { createStorage } from "./config/bootstrap";
import { runCli } from "./cli/run";
​
async function main() {
  const cfg = loadConfig();
  const storage = await createStorage(cfg.storageMode);
​
  if (cfg.runMode === "cli") {
    await runCli(storage);
  } else {
    console.error("v2.0 仅支持 RUN_MODE=cli");
    process.exit(1);
  }
}
​
main().catch((err) => {
  console.error("程序启动失败:", err);
  process.exit(1);
});

5. src/config/env.ts

复制代码
/**
 * 环境配置模块
 *
 * 职责:
 * - 读取 .env 文件并注入 process.env
 * - 提供类型安全的配置对象
 * - 所有配置项均有默认值,.env 仅需覆盖要修改的项
 *
 * 配置优先级:临时环境变量 > .env > 代码默认值
 */
​
import dotenv from "dotenv";
import path from "path";
​
dotenv.config({ path: path.resolve(".env") });
​
/** 全局配置结构定义,覆盖所有版本的配置项 */
export type Config = {
  /** 运行模式:cli(终端交互)| api(HTTP 服务,v2.4 启用) */
  runMode: "cli" | "api";
  /** 存储模式:json | sqlite | drizzle-sqlite | drizzle-mysql */
  storageMode: "json" | "sqlite" | "drizzle-sqlite" | "drizzle-mysql";
  /** 是否输出完整错误堆栈 */
  debug: boolean;
  /** SQLite 数据库文件路径(v2.1 使用) */
  sqliteFile: string;
  /** Drizzle SQLite 数据库文件路径(v2.2 使用) */
  drizzleSqliteFile: string;
  /** MySQL 连接配置(v2.3 使用) */
  mysql: {
    host: string;
    port: number;
    user: string;
    password: string;
    db: string;
  };
};
​
/**
 * 加载并返回完整的配置对象
 * 从 process.env 读取,缺失项使用默认值
 */
export function loadConfig(): Config {
  return {
    runMode: (process.env.RUN_MODE as Config["runMode"]) || "cli",
    storageMode: (process.env.STORAGE_MODE as Config["storageMode"]) || "json",
    debug: process.env.DEBUG === "true",
    sqliteFile: process.env.SQLITE_FILE || "data/todo-sqlite.db",
    drizzleSqliteFile: process.env.DRIZZLE_SQLITE_FILE || "data/drizzle-sqlite.db",
    mysql: {
      host: process.env.MYSQL_HOST || "127.0.0.1",
      port: Number(process.env.MYSQL_PORT || 3306),
      user: process.env.MYSQL_USER || "root",
      password: process.env.MYSQL_PASSWORD || "",
      db: process.env.MYSQL_DB || "todo",
    },
  };
}

6. src/config/bootstrap.ts

复制代码
/**
 * 存储实例工厂
 *
 * 根据配置的存储模式创建对应的 TaskStorage 实现
 * v2.0 仅支持 json,其他模式预留分支供后续版本扩展
 *
 * 设计原则:单进程互斥,一次只启用一种存储
 */
​
import type { TaskStorage } from "../core/storage/base";
import { JsonStorage } from "../core/storage/json.store";
import { StorageError } from "../core/errors";
​
/**
 * 创建存储实例
 * @param mode 存储模式,对应 STORAGE_MODE 配置项
 * @returns TaskStorage 实例
 * @throws StorageError 不支持的存储模式
 */
export async function createStorage(mode: string): Promise<TaskStorage> {
  switch (mode) {
    case "json":
      return new JsonStorage();
    // v2.1: case "sqlite": return new SqliteStorage(config);
    // v2.2: case "drizzle-sqlite": return new DrizzleSqliteStorage(config);
    // v2.3: case "drizzle-mysql": return new DrizzleMysqlStorage(config);
    case "sqlite":
    case "drizzle-sqlite":
    case "drizzle-mysql":
      throw new StorageError(`v2.0 不支持存储模式 ${mode},请切换为 json`);
    default:
      throw new StorageError(`非法存储模式: ${mode}`);
  }
}

7. src/core/types.ts

复制代码
/**
 * 核心类型定义
 *
 * 定义领域实体和数据传输结构,所有层共享这些类型
 */
​
/** 待办任务领域实体 */
export type Task = {
  /** 自增主键 */
  id: number;
  /** 任务标题 */
  title: string;
  /** 优先级:low / medium / high */
  priority: "low" | "medium" | "high";
  /** 状态:active(待办)/ done(已完成) */
  status: "active" | "done";
  /** 创建时间,ISO 8601 格式 */
  createAt: string;
};
​
/**
 * JSON 存储的原子容器
 * 整个文件作为一个整体读写,保证数据一致性
 */
export type TaskStore = {
  /** 任务列表 */
  list: Task[];
  /** 下一个可用的自增 ID */
  nextId: number;
};
​
/** CLI 命令统一参数结构,所有可选字段由各命令自行校验 */
export type CliParams = {
  /** 任务标题(add / update 使用) */
  title?: string;
  /** 任务 ID(done / update / delete 使用) */
  id?: number;
  /** 优先级(add / update 使用) */
  priority?: Task["priority"];
};

8. src/core/errors.ts

复制代码
/**
 * 自定义异常体系
 *
 * 按错误来源分三层,CLI 层根据异常类型输出不同级别的日志:
 * - ParamError / BizError → warn(用户可修正)
 * - StorageError → error(系统级故障)
 */

/** 参数校验错误:缺少必填参数、参数值不合法等 */
export class ParamError extends Error {
  constructor(msg: string) {
    super(msg);
    this.name = "ParamError";
  }
}

/** 业务逻辑错误:操作对象不存在、状态冲突等 */
export class BizError extends Error {
  constructor(msg: string) {
    super(msg);
    this.name = "BizError";
  }
}

/** 存储层错误:文件读写失败、数据库连接异常等 */
export class StorageError extends Error {
  constructor(msg: string) {
    super(msg);
    this.name = "StorageError";
  }
}

9. src/core/storage/base.ts

复制代码
/**
 * 存储抽象接口
 *
 * 所有存储实现(JsonStorage / SqliteStorage / DrizzleStorage 等)
 * 必须实现此接口,确保上层代码与存储解耦
 *
 * 方法分两组:
 * - fullLoad / fullSave:整体读写,用于 JSON 原子操作
 * - findAll ~ clearCompleted:细粒度 CRUD,各实现可优化为数据库原生操作
 */

import type { Task, TaskStore } from "../types";

export interface TaskStorage {
  /** 整体加载:读取完整的 TaskStore(JSON 模式下为全文件读取) */
  fullLoad: () => Promise<TaskStore>;
  /** 整体保存:写入完整的 TaskStore(JSON 模式下为全文件写入) */
  fullSave: (store: TaskStore) => Promise<void>;

  /** 查询任务列表,可按状态筛选 */
  findAll: (status?: Task["status"]) => Promise<Task[]>;
  /** 根据 ID 查询单个任务,不存在返回 null */
  findOne: (id: number) => Promise<Task | null>;
  /** 创建任务,返回包含自增 ID 的完整任务对象 */
  create: (task: Omit<Task, "id">) => Promise<Task>;
  /** 更新任务,返回是否成功(false 表示 ID 不存在) */
  update: (id: number, patch: Partial<Omit<Task, "id">>) => Promise<boolean>;
  /** 删除任务,返回是否成功(false 表示 ID 不存在) */
  remove: (id: number) => Promise<boolean>;
  /** 清空所有已完成任务,返回删除数量 */
  clearCompleted: () => Promise<number>;
}

10. src/core/storage/json.store.ts

复制代码
/**
 * JSON 文件存储实现
 *
 * 将任务数据持久化为 data/tasks.json 文件
 * 采用"整体读写"策略:每次操作都读取完整文件 → 修改 → 写回
 * 适合小数据量场景,无需数据库依赖
 *
 * 关键设计:
 * - initFile() 确保文件存在且内容合法,不存在或损坏时自动重建
 * - ready Promise 保证初始化完成后才允许读写,避免竞态条件
 */
​
import fs from "fs/promises";
import path from "path";
import { TaskStorage } from "./base";
import { Task, TaskStore } from "../types";
import { StorageError } from "../errors";
​
/** 数据文件固定路径 */
const FILE_PATH = path.resolve("data/tasks.json");
​
/**
 * 初始化存储文件
 * 尝试读取并解析 JSON,失败(文件不存在/为空/格式损坏)则重建空存储
 */
async function initFile() {
  try {
    const raw = await fs.readFile(FILE_PATH, "utf8");
    JSON.parse(raw); // 验证 JSON 格式合法性
  } catch {
    // 文件不存在、为空、或 JSON 格式损坏,重建
    await fs.mkdir(path.dirname(FILE_PATH), { recursive: true });
    const empty: TaskStore = { list: [], nextId: 1 };
    await fs.writeFile(FILE_PATH, JSON.stringify(empty, null, 2), "utf8");
  }
}
​
export class JsonStorage implements TaskStorage {
  /** 初始化 Promise,所有读写操作必须等待此 Promise 完成 */
  private ready: Promise<void>;
​
  constructor() {
    this.ready = initFile();
  }
​
  /**
   * 整体加载:读取 tasks.json 并解析为 TaskStore
   * @throws StorageError 文件读取或解析失败
   */
  async fullLoad(): Promise<TaskStore> {
    await this.ready;
    try {
      const raw = await fs.readFile(FILE_PATH, "utf8");
      return JSON.parse(raw) as TaskStore;
    } catch (e) {
      throw new StorageError("读取tasks.json失败: " + (e as Error).message);
    }
  }
​
  /**
   * 整体保存:将 TaskStore 序列化后写入 tasks.json
   * @throws StorageError 文件写入失败
   */
  async fullSave(store: TaskStore): Promise<void> {
    await this.ready;
    try {
      await fs.writeFile(FILE_PATH, JSON.stringify(store, null, 2), "utf8");
    } catch (e) {
      throw new StorageError("写入tasks.json失败: " + (e as Error).message);
    }
  }
​
  /**
   * 查询任务列表
   * @param status 可选,按状态筛选(active/done),不传则返回全部
   */
  async findAll(status?: Task["status"]): Promise<Task[]> {
    const store = await this.fullLoad();
    if (!status) return store.list;
    return store.list.filter((t) => t.status === status);
  }
​
  /**
   * 根据 ID 查询单个任务
   * @returns 匹配的任务对象,不存在返回 null
   */
  async findOne(id: number): Promise<Task | null> {
    const store = await this.fullLoad();
    return store.list.find((t) => t.id === id) || null;
  }
​
  /**
   * 创建新任务
   * 自动分配自增 ID,写入后返回完整任务对象
   */
  async create(task: Omit<Task, "id">): Promise<Task> {
    const store = await this.fullLoad();
    const newTask: Task = { ...task, id: store.nextId };
    store.list.push(newTask);
    store.nextId += 1;
    await this.fullSave(store);
    return newTask;
  }
​
  /**
   * 更新任务的指定字段
   * @param id 目标任务 ID
   * @param patch 要更新的字段(不含 id)
   * @returns 是否更新成功(false 表示 ID 不存在)
   */
  async update(id: number, patch: Partial<Omit<Task, "id">>): Promise<boolean> {
    const store = await this.fullLoad();
    const target = store.list.find((t) => t.id === id);
    if (!target) return false;
    Object.assign(target, patch);
    await this.fullSave(store);
    return true;
  }
​
  /**
   * 删除指定任务
   * @returns 是否删除成功(false 表示 ID 不存在)
   */
  async remove(id: number): Promise<boolean> {
    const store = await this.fullLoad();
    const oldLen = store.list.length;
    store.list = store.list.filter((t) => t.id !== id);
    if (store.list.length === oldLen) return false;
    await this.fullSave(store);
    return true;
  }
​
  /**
   * 清空所有已完成(status === "done")的任务
   * @returns 实际删除的任务数量
   */
  async clearCompleted(): Promise<number> {
    const store = await this.fullLoad();
    const before = store.list.length;
    store.list = store.list.filter((t) => t.status !== "done");
    const clearCount = before - store.list.length;
    await this.fullSave(store);
    return clearCount;
  }
}

11. src/core/storage/factory.ts

复制代码
/**
 * 存储工厂(预留)
 *
 * v2.0 的存储实例化逻辑在 bootstrap.ts 中实现
 * 此文件预留为 v2.1~v2.3 扩展时的工厂模式入口
 */

12. src/commands/index.ts

复制代码
/**
 * 命令注册表
 *
 * 将所有 CLI 命令函数注册到统一的 cmdMap 中
 * cli/run.ts 根据用户输入的命令名从 cmdMap 查找并执行对应函数
 *
 * 扩展新命令只需:
 * 1. 在 src/commands/ 下新建命令文件
 * 2. 在此文件中 import 并注册到 cmdMap
 */

import type { TaskStorage } from "../core/storage/base";
import { addCmd } from "./add";
import { listCmd } from "./list";
import { doneCmd } from "./done";
import { updateCmd } from "./update";
import { deleteCmd } from "./delete";
import { clearCmd } from "./clear";

/** 命令函数签名:接收存储实例和解析后的参数 */
export type CommandFn = (storage: TaskStorage, params: Record<string, any>) => Promise<void>;

/** 命令名 → 命令函数的映射表 */
export const cmdMap: Record<string, CommandFn> = {
  add: addCmd,
  list: listCmd,
  done: doneCmd,
  update: updateCmd,
  delete: deleteCmd,
  clear: clearCmd,
};

13. src/commands/add.ts

复制代码
/**
 * 添加任务命令
 *
 * 用法:add --title 任务标题 --priority low/medium/high
 * - --title 必填,任务标题
 * - --priority 可选,默认 medium
 */

import type { TaskStorage } from "../core/storage/base";
import { ParamError } from "../core/errors";
import { logger } from "../utils/logger";
import type { CliParams } from "../core/types";

export async function addCmd(storage: TaskStorage, params: CliParams) {
  if (!params.title) throw new ParamError("必须传入 --title 任务标题");

  const priority = params.priority || "medium";
  if (!["low", "medium", "high"].includes(priority)) {
    throw new ParamError("优先级仅支持 low / medium / high");
  }

  const task = await storage.create({
    title: params.title,
    priority,
    status: "active",
    createAt: new Date().toISOString(),
  });
  logger.success(`创建任务成功,ID: ${task.id}`);
}

14. src/commands/list.ts

复制代码
/**
 * 查看任务列表命令
 *
 * 用法:list
 * 显示所有任务,无任务时提示"暂无待办任务"
 */

import type { TaskStorage } from "../core/storage/base";
import { logger } from "../utils/logger";

export async function listCmd(storage: TaskStorage) {
  const list = await storage.findAll();
  if (list.length === 0) {
    logger.info("暂无待办任务");
    return;
  }
  logger.info("===== 全部任务列表 =====");
  for (const t of list) {
    logger.info(`[${t.id}] ${t.title} | 优先级:${t.priority} | 状态:${t.status}`);
  }
}

15. src/commands/done.ts

复制代码
/**
 * 标记任务完成命令
 *
 * 用法:done --id 任务ID
 * 将指定任务的 status 更新为 "done"
 */

import type { TaskStorage } from "../core/storage/base";
import { ParamError, BizError } from "../core/errors";
import { logger } from "../utils/logger";
import type { CliParams } from "../core/types";

export async function doneCmd(storage: TaskStorage, params: CliParams) {
  const id = params.id;
  if (!id) throw new ParamError("必须传入 --id");

  const exist = await storage.findOne(id);
  if (!exist) throw new BizError(`ID ${id} 任务不存在`);

  await storage.update(id, { status: "done" });
  logger.success(`任务 ${id} 已标记完成`);
}

16. src/commands/update.ts

复制代码
/**
 * 修改任务命令
 *
 * 用法:update --id 任务ID --title 新标题 --priority 新优先级
 * - --id 必填
 * - --title / --priority 至少传入一项
 */

import type { TaskStorage } from "../core/storage/base";
import { ParamError, BizError } from "../core/errors";
import { logger } from "../utils/logger";
import type { CliParams } from "../core/types";

export async function updateCmd(storage: TaskStorage, params: CliParams) {
  const id = params.id;
  if (!id) throw new ParamError("必须传入 --id");

  const exist = await storage.findOne(id);
  if (!exist) throw new BizError(`ID ${id} 任务不存在`);

  const patch: Record<string, any> = {};
  if (params.title) patch.title = params.title;
  if (params.priority) {
    if (!["low", "medium", "high"].includes(params.priority)) {
      throw new ParamError("优先级仅支持 low / medium / high");
    }
    patch.priority = params.priority;
  }
  if (Object.keys(patch).length === 0) {
    throw new ParamError("至少传入 title / priority 其中一项");
  }

  await storage.update(id, patch);
  logger.success(`任务 ${id} 修改成功`);
}

17. src/commands/delete.ts

复制代码
/**
 * 删除任务命令
 *
 * 用法:delete --id 任务ID
 * 永久删除指定任务,不可恢复
 */

import type { TaskStorage } from "../core/storage/base";
import { ParamError, BizError } from "../core/errors";
import { logger } from "../utils/logger";
import type { CliParams } from "../core/types";

export async function deleteCmd(storage: TaskStorage, params: CliParams) {
  const id = params.id;
  if (!id) throw new ParamError("必须传入 --id");

  const ok = await storage.remove(id);
  if (!ok) throw new BizError(`ID ${id} 任务不存在`);
  logger.success(`任务 ${id} 已删除`);
}

18. src/commands/clear.ts

复制代码
/**
 * 清空已完成任务命令
 *
 * 用法:clear
 * 删除所有 status 为 "done" 的任务,返回删除数量
 */

import type { TaskStorage } from "../core/storage/base";
import { logger } from "../utils/logger";

export async function clearCmd(storage: TaskStorage) {
  const count = await storage.clearCompleted();
  logger.success(`清空已完成任务 ${count} 条`);
}

19. src/cli/parser.ts

复制代码
/**
 * CLI 输入解析器
 *
 * 将用户输入的字符串解析为命令名 + 参数对象
 * 支持两种参数格式:
 *   1. --key=value(等号分隔)
 *   2. --key value(空格分隔)
 *
 * 解析规则:
 * - 第一个词作为命令名
 * - 以 -- 开头的词作为参数 key
 * - 紧跟其后的非 -- 开头词作为 value(空格分隔模式)
 * - 或按 = 分割为 key/value(等号分隔模式)
 * - 数字类型的 value 自动转为 number
 *
 * 示例:
 *   "add --title 学习TS --priority=high"
 *   → { cmd: "add", params: { title: "学习TS", priority: "high" } }
 */

export function parseCliInput(input: string): { cmd: string; params: Record<string, string | number | boolean> } {
  const parts = input.trim().split(/\s+/).filter(Boolean);
  if (parts.length === 0) return { cmd: "", params: {} };

  const cmd = parts[0];
  const params: Record<string, string | number | boolean> = {};
  let i = 1;

  while (i < parts.length) {
    const part = parts[i];

    if (part.startsWith("--")) {
      // 等号分隔:--key=value
      if (part.includes("=")) {
        const [k, v] = part.slice(2).split("=");
        let val: string | number = v;
        if (!isNaN(Number(v))) val = Number(v);
        params[k] = val;
        i++;
      }
      // 空格分隔:--key value
      else {
        const key = part.slice(2);
        const next = parts[i + 1];
        if (next && !next.startsWith("--")) {
          let val: string | number = next;
          if (!isNaN(Number(next))) val = Number(next);
          params[key] = val;
          i += 2;
        } else {
          // 没有 value,当作布尔标志
          params[key] = true;
          i++;
        }
      }
    } else {
      i++;
    }
  }

  return { cmd, params };
}

20. src/cli/run.ts

复制代码
/**
 * CLI 交互式终端
 *
 * 使用 Node.js readline 创建交互式命令行界面
 * 循环流程:显示提示符 → 读取输入 → 解析 → 执行命令 → 处理异常 → 重新显示提示符
 *
 * 内置命令:help(显示帮助)、exit(退出程序)
 * 业务命令:从 cmdMap 中查找并执行
 *
 * 错误处理策略:
 * - ParamError / BizError → 黄色警告(用户可修正)
 * - StorageError → 红色错误(系统级问题)
 * - 其他异常 → 红色错误 + 可选堆栈(由 DEBUG 配置控制)
 */
​
import readline from "readline";
import type { TaskStorage } from "../core/storage/base";
import { parseCliInput } from "./parser";
import { cmdMap } from "../commands";
import { ParamError, BizError, StorageError } from "../core/errors";
import { logger } from "../utils/logger";
import { loadConfig } from "../config/env";
​
/**
 * 启动交互式 CLI
 * @param storage 存储实例,由 bootstrap 创建并注入
 */
export async function runCli(storage: TaskStorage) {
  const cfg = loadConfig();
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    prompt: "todo > ",
  });
​
  logger.info("TodoCLI v2.0 启动完成,输入 help 查看指令,exit 退出程序");
  rl.prompt();
​
  rl.on("line", async (line) => {
    const { cmd, params } = parseCliInput(line);
​
    try {
      // 空输入或 exit → 关闭终端
      if (cmd === "" || cmd === "exit") {
        rl.close();
        return;
      }
​
      // 内置 help 命令
      if (cmd === "help") {
        logger.info("可用指令:");
        logger.info("add --title xxx --priority low/medium/high");
        logger.info("list");
        logger.info("done --id 数字");
        logger.info("update --id 数字 --title xxx");
        logger.info("delete --id 数字");
        logger.info("clear");
        logger.info("exit 退出");
      }
      // 业务命令:从注册表查找
      else if (cmdMap[cmd]) {
        await cmdMap[cmd](storage, params);
      }
      // 未知命令
      else {
        logger.warn(`未知命令 ${cmd},输入 help 查看支持指令`);
      }
    } catch (err) {
      // 按异常类型分级输出
      if (err instanceof ParamError || err instanceof BizError) {
        logger.warn(err.message);
      } else if (err instanceof StorageError) {
        logger.error(err.message);
      } else {
        logger.error("未知异常:" + (err as Error).message);
        if (cfg.debug) console.error((err as Error).stack);
      }
    }
​
    rl.prompt();
  });
​
  rl.on("close", () => {
    logger.info("程序退出");
    process.exit(0);
  });
}

21. src/utils/logger.ts

复制代码
/**
 * 彩色分级日志工具
 *
 * 使用 ANSI 转义码为终端输出着色,区分不同级别的日志:
 * - info(青色):普通信息
 * - success(绿色):操作成功
 * - warn(黄色):警告(参数错误、业务异常)
 * - error(红色):错误(存储故障、未知异常)
 */
​
const color = {
  info: "\x1b[36m",
  success: "\x1b[32m",
  warn: "\x1b[33m",
  error: "\x1b[31m",
  reset: "\x1b[0m",
};
​
export const logger = {
  info: (msg: string) => console.log(`${color.info}${msg}${color.reset}`),
  success: (msg: string) => console.log(`${color.success}${msg}${color.reset}`),
  warn: (msg: string) => console.log(`${color.warn}${msg}${color.reset}`),
  error: (msg: string) => console.log(`${color.error}${msg}${color.reset}`),
};

22. src/utils/response.ts

复制代码
/**
 * API 标准返回封装(预留)
 *
 * v2.4 启用 Hono HTTP API 时,统一所有接口的返回格式:
 * { code: number, message: string, data: any }
 *
 * v2.0 仅 CLI 模式,此文件暂不实现
 */

快速启动

复制代码
# 1. 安装依赖
bun install
​
# 2. 复制环境配置
cp .env.example .env
​
# 3. 启动程序
bun cli

CLI 指令示例

复制代码
# 添加任务
add --title 学习TypeScript --priority high
​
# 查看全部任务
list
​
# 标记完成
done --id 1
​
# 修改任务
update --id 1 --title 学习TypeScript进阶
​
# 删除任务
delete --id 1
​
# 清空已完成任务
clear
​
# 查看帮助
help
​
# 退出程序
exit
相关推荐
芝麻别开门2 个月前
TS Demux 插件知识文档
ts
JaydenAI2 个月前
[Deep Agents:LangChain的Agent Harness-04]TodoListMiddleware的任务拆解与状态流转
langchain·todolist·middleware·deep agents
Json____2 个月前
vue3-商城管理系统-前端静态网站
前端·vue3·ts·商城纯静态
Huanzhi_Lin3 个月前
关于V8/MajorGC/MinorGC——性能优化
javascript·性能优化·ts·js·v8·新生代·老生代
belldeep4 个月前
nodejs:Vite + Svelte + ts 入门示例
typescript·node.js·ts·vite·svelte
却道天凉_好个秋4 个月前
音视频学习(九十二):ts封装
音视频·ts
zh_xuan5 个月前
React Native Demo
android·javascript·react native·ts
我讲个笑话你可别哭啊5 个月前
鸿蒙ArkTS快速入门
前端·ts·arkts·鸿蒙·方舟开发框架