资讯动态

从零构建AI Agent CLI:TypeScript工程化实践与工具链配置

发布时间:2026/8/12 17:29:48 来源:尧图企业网站定制
1. 项目概述为什么从零构建一个Agent CLI如果你关注过近两年的技术趋势会发现“AI Agent”已经从一个前沿概念变成了开发者工具箱里越来越常见的组件。无论是自动化代码审查、智能文档生成还是复杂的业务流程编排背后往往都有一个或多个Agent在协同工作。然而当我们想亲手打造一个属于自己的Agent并将其封装成一个命令行工具CLI时常常会陷入一种困境网上充斥着各种“五分钟快速入门”的框架教程但当你真正想构建一个结构清晰、易于维护、测试完备的生产级项目时却发现无从下手。框架帮你解决了“从0到0.5”的问题但“从0.5到1”的工程化之路才是决定项目能否长期健康发展的关键。这正是我们启动这个系列的原因。我们不打算只教你用某个现成的框架“跑起来”一个Demo而是要深入工程实践的腹地从最基础的项目初始化和工程基建开始一步步搭建一个高标准的TypeScript CLI项目骨架。这个骨架将具备现代前端工程的所有优秀特质严格的类型安全、自动化的代码质量检查、单元测试覆盖、以及可复用的构建发布流程。你可以把它看作是为你的Agent大脑打造一个强健、可靠的“身体”。为什么选择CLI作为载体因为命令行是开发者与系统交互最直接、最强大的界面。一个设计良好的CLI工具能够无缝融入开发者的工作流无论是本地调试、CI/CD流水线还是服务器端的自动化脚本。将你的Agent能力通过CLI暴露出来意味着它具备了极强的可集成性和可扩展性。在第一篇中我们将聚焦于“基建”。这听起来可能不如直接实现AI功能那么激动人心但我以多年的项目经验告诉你前期在工程规范上投入的每一分钟都会在后续的开发、协作和迭代中十倍地回报你。我们将使用TypeScript作为开发语言搭配ESLint、Prettier、Vitest等工具链目标是建立一个即便项目复杂度和团队规模增长也能保持代码整洁和开发体验流畅的坚实基础。2. 核心需求与工具选型解析在动手写第一行代码之前我们必须想清楚我们要构建的究竟是一个什么样的CLI它需要满足哪些核心需求基于这些需求我们又该如何选择技术栈2.1 核心需求定义一个面向生产的Agent CLI其核心需求远不止“能运行”那么简单类型安全与开发体验Agent的逻辑可能涉及复杂的数据结构如LLM的请求/响应、工具调用规范。静态类型检查能在编码阶段就捕获大量潜在错误提供卓越的代码提示和重构能力这对提升开发效率和代码可靠性至关重要。代码质量与一致性当多人协作或项目长期演进时统一的代码风格和避免常见的错误模式是维持代码库健康的基础。我们需要自动化工具来强制执行这些规范。可测试性Agent的核心是逻辑与决策。我们必须能够方便地对这些逻辑进行单元测试、集成测试确保每次修改都不会破坏现有功能并为重构提供信心。良好的开发者体验DX包括清晰的错误提示、丰富的命令行帮助、易于理解的参数解析以及平滑的调试流程。可维护性与可扩展性项目结构应该清晰模块职责分明便于后续添加新的命令、集成新的AI模型或工具。打包与分发最终产物应该是一个可以全局安装、独立运行的二进制文件并且最好能支持跨平台。2.2 技术栈选型与理由基于以上需求我们做出如下选型并解释其背后的考量语言TypeScript理由它是满足“类型安全”需求的不二之选。对于CLI项目其强类型系统能完美约束命令行参数、配置对象以及Agent内部复杂的流程状态。相较于纯JavaScript它能极大减少运行时因类型错误导致的崩溃。社区生态和类型定义文件也极其完善。包管理与项目初始化npm init/pnpm init理由npm是Node.js生态的事实标准。近年来pnpm因其更快的速度和高效的磁盘空间利用通过硬链接和符号链接而备受青睐尤其适合Monorepo场景。本系列将使用pnpm但其命令与npm大多兼容。选择哪个取决于团队偏好pnpm在性能上确有优势。代码质量工具链ESLint PrettierESLint用于识别并报告JavaScript/TypeScript代码中的问题模式确保代码质量。我们可以配置适用于TypeScript的规则集如typescript-eslint并继承一些优秀的开源配置如Airbnb、Standard快速建立规范。Prettier一个“有主见”的代码格式化工具。它接管了所有关于代码风格的决策缩进、分号、引号等让开发者从无休止的风格争论中解放出来并与ESLint分工合作ESLint负责代码质量问题Prettier负责风格问题。测试框架Vitest理由这是一个基于Vite的下一代测试框架。它与Vite共享配置、转换器和解析器速度极快对TypeScript和ES模块的支持是原生级的。其API与Jest高度兼容学习成本低但拥有更优秀的开发体验如智能监听、模块级模拟。对于新项目Vitest是比Jest更现代、更快速的选择。构建与打包工具tsup或tsx理由我们需要将TypeScript源代码编译、打包成单一的、可在Node.js环境运行的JavaScript文件。tsup基于esbuild配置极其简单打包速度飞快非常适合CLI工具。它支持生成CommonJS和ESM格式并能将依赖打包进去通过--bundle减少用户环境依赖问题。tsx则更侧重于开发时的即时执行可以作为tsup的补充。命令行框架commander.js或cac理由解析命令行参数、生成帮助信息是CLI的基础功能。使用成熟的框架能避免重复造轮子处理复杂的子命令、选项、参数验证等。commander.js历史悠久、功能全面、文档完善cac更轻量、更现代。我们将根据项目复杂度进行选择。注意工具选型没有绝对的“最佳”只有“最适合”。这里的选择是基于当前2024年社区主流趋势、项目需求和个人偏好的平衡。例如如果你对Jest更熟悉完全可以用Jest替代Vitest核心的工程化思想是相通的。3. 项目初始化与基础配置实操理论说再多不如动手做。现在让我们打开终端开始搭建项目。3.1 创建项目并初始化首先为你的Agent CLI项目创建一个新目录并进入。mkdir my-agent-cli cd my-agent-cli接着初始化package.json。这里我使用pnpm如果你用npm将pnpm替换为npm即可。pnpm init你会被提示输入项目名称、版本、描述等信息。这里可以先快速按回车使用默认值我们稍后再来修改package.json。一个更高效的方式是使用-y参数快速生成默认配置pnpm init -y现在你的目录下应该有了一个基础的package.json文件。3.2 安装TypeScript及基础依赖安装TypeScript作为开发依赖同时安装Node.js的类型定义因为我们的CLI会运行在Node环境中。pnpm add -D typescript types/node-D参数表示将这些包安装在devDependencies中因为它们是开发工具最终用户不需要。接下来生成TypeScript配置文件tsconfig.jsonnpx tsc --init这个命令会创建一个包含大量默认选项大部分被注释掉的tsconfig.json文件。对于CLI项目我们需要一个针对性更强的配置。让我们直接替换其内容{ compilerOptions: { /* 基础选项 */ target: ES2022, // 编译目标ES版本现代Node.js支持ES2022 module: ESNext, // 使用ES模块便于tree-shaking和现代打包工具 lib: [ES2022], moduleResolution: bundler, // 配合现代打包工具的解析策略 resolveJsonModule: true, // 允许导入JSON文件 allowSyntheticDefaultImports: true, /* 类型检查与严格模式 */ strict: true, // 启用所有严格类型检查选项 skipLibCheck: true, // 跳过库文件的类型检查以提升速度 noUnusedLocals: true, // 报告未使用的局部变量 noUnusedParameters: true, // 报告未使用的函数参数 noFallthroughCasesInSwitch: true, // 防止switch语句贯穿 /* 输出控制 */ outDir: ./dist, // 编译输出目录 rootDir: ./src, // 源代码根目录 declaration: true, // 生成.d.ts类型声明文件 declarationMap: true, // 为声明文件生成sourcemap sourceMap: true, // 为JavaScript生成sourcemap便于调试 /* 其他 */ esModuleInterop: true, // 改善CommonJS和ES模块的互操作性 forceConsistentCasingInFileNames: true // 强制文件名大小写一致 }, include: [src/**/*], // 包含src目录下所有文件 exclude: [node_modules, dist, **/*.test.ts, **/*.spec.ts] // 排除不需要编译的文件 }关键配置解读“target”: “ES2022”Node.js 18 已良好支持 ES2022 特性选择较新的目标版本能生成更简洁的代码。“module”: “ESNext”和“moduleResolution”: “bundler”这是为使用tsup、Vite等现代打包工具做的准备它们能更好地处理 ES 模块。“outDir”和“rootDir”清晰地分离源代码src和编译产物dist这是保持项目结构清晰的好习惯。“strict”: true强烈建议开启。严格的类型检查初期可能会让你多写一些类型注解但它能避免无数潜在的运行时错误是TypeScript价值的核心体现。3.3 配置ESLint与Prettier代码质量守卫首先安装ESLint及其相关插件pnpm add -D eslint typescript-eslint/parser typescript-eslint/eslint-plugin eslint-config-prettiereslint: ESLint核心库。typescript-eslint/parser: 使ESLint能够解析TypeScript语法。typescript-eslint/eslint-plugin: 提供针对TypeScript的linting规则。eslint-config-prettier: 关闭所有与Prettier冲突的ESLint规则让两者和谐共处。接下来创建ESLint配置文件.eslintrc.cjs使用.cjs扩展名确保在Node环境下能被正确识别为CommonJS模块// .eslintrc.cjs module.exports { root: true, // 表明这是根配置文件ESLint将不再向上查找 env: { node: true, // 启用Node.js全局变量和语法 es2022: true, // 启用ES2022全局变量 }, parser: typescript-eslint/parser, // 指定TypeScript解析器 parserOptions: { ecmaVersion: latest, sourceType: module, // 使用ES模块 project: ./tsconfig.json, // 告诉ESLint你的tsconfig位置用于基于类型的规则 }, plugins: [typescript-eslint], // 加载TypeScript插件 extends: [ eslint:recommended, // ESLint推荐规则 plugin:typescript-eslint/recommended-type-checked, // TS推荐规则需要类型信息 plugin:typescript-eslint/stylistic-type-checked, // TS风格规则 prettier, // 必须放在最后用于禁用与Prettier冲突的规则 ], rules: { // 可以在这里覆盖或添加自定义规则 typescript-eslint/no-unused-vars: [ error, { argsIgnorePattern: ^_, varsIgnorePattern: ^_ }, ], // 允许以下划线开头的变量未使用常用于忽略参数 }, ignorePatterns: [dist, node_modules, *.cjs, *.js], // 忽略这些目录和文件 };一个常见陷阱如果你在配置中启用了需要类型信息的规则如recommended-type-checked但在VSCode或其他编辑器里ESLint仍然报错提示找不到某些模块或类型例如网络热词中提到的“amap is undefined”这通常是因为ESLint没有正确读取到tsconfig.json中的compilerOptions.paths别名配置或者项目依赖没有完全安装。确保parserOptions.project路径正确。运行pnpm install确保所有依赖已安装。在VSCode中可以尝试重启ESLint服务器或重新加载窗口。现在安装并配置Prettierpnpm add -D prettier创建Prettier配置文件.prettierrc.json{ semi: true, singleQuote: true, tabWidth: 2, trailingComma: es5, printWidth: 100, endOfLine: lf }这些是常见的Prettier配置定义了分号、单引号、缩进等格式规则。你可以根据团队习惯调整。最后我们需要一个机制让ESLint和Prettier在代码保存时自动运行。这通常由编辑器的插件如VSCode的ESLint和Prettier插件配合设置实现。但为了确保团队一致性我们还可以在package.json中定义脚本并配置lint-staged和husky在提交前自动检查。这是更高级的工程化步骤我们可以在后续篇章展开。3.4 配置Vitest测试堡垒安装Vitest及相关工具pnpm add -D vitest vitest/uivitest: 测试框架本身。vitest/ui: 提供一个漂亮的图形化测试界面便于调试和查看覆盖率。创建Vitest配置文件vitest.config.tsimport { defineConfig } from vitest/config; export default defineConfig({ test: { globals: true, // 类似Jest允许使用describe, it, expect等全局API environment: node, // 测试环境设为Node.js include: [src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}], // 测试文件匹配模式 coverage: { provider: v8, // 使用Node.js内置的V8覆盖率收集器 reporter: [text, json, html], // 生成多种格式的覆盖率报告 exclude: [**/*.test.ts, **/*.spec.ts, dist, node_modules], // 排除项 }, }, });现在更新package.json添加测试脚本{ scripts: { test: vitest, test:ui: vitest --ui, test:coverage: vitest run --coverage, type-check: tsc --noEmit // 只做类型检查不输出文件 } }让我们立刻验证一下。创建源代码目录和第一个测试文件mkdir src// src/math.test.ts import { describe, it, expect } from vitest; function add(a: number, b: number): number { return a b; } describe(math functions, () { it(should add two numbers correctly, () { expect(add(1, 2)).toBe(3); expect(add(-1, 5)).toBe(4); }); });运行测试pnpm test你应该能看到测试通过的输出。运行pnpm test:ui可以打开浏览器查看图形化界面。3.5 配置构建脚本与入口我们的CLI需要一个入口点。创建src/cli.ts作为主入口文件#!/usr/bin/env node // 上面的shebang行告诉系统这个文件应该用Node.js来执行 import { program } from commander; // 我们稍后会安装commander program .name(my-agent) // 你的CLI工具名 .description(一个强大的AI Agent命令行工具) .version(0.1.0); // 从package.json读取版本号更好这里先写死 program.parse(process.argv);现在我们需要一个构建脚本将TypeScript编译打包成可执行文件。安装tsuppnpm add -D tsup在package.json中添加构建脚本和bin字段bin字段用于指定当用户全局安装你的包时哪个命令对应哪个可执行文件。{ name: my-agent-cli, version: 0.1.0, description: A powerful AI Agent CLI tool, main: dist/cli.js, bin: { my-agent: ./dist/cli.js // 命令名: 入口文件路径 }, scripts: { build: tsup src/cli.ts --format cjs,esm --dts --clean --minify, dev: tsup src/cli.ts --format cjs --watch, start: node dist/cli.js, // ... 之前添加的test脚本 }, // ... 其他字段 }tsup配置解释src/cli.ts: 入口文件。--format cjs,esm: 同时生成CommonJS和ES Module格式。--dts: 生成类型声明文件.d.ts。--clean: 构建前清理dist目录。--minify: 压缩代码。--watch(在dev脚本中): 监听文件变化并重新构建。现在运行pnpm build你会在dist目录下看到生成的文件。为了在开发时方便地测试CLI我们可以使用pnpm link在本地创建一个全局软链接# 在项目根目录执行 pnpm link --global执行后理论上你就可以在终端任何地方运行my-agent命令了。它会执行dist/cli.js。试试看my-agent --help你应该能看到commander生成的帮助信息目前只有版本和描述。4. 工程化进阶Git Hooks与自动化工作流基础配置完成后我们可以引入一些“守卫”来自动化代码质量流程确保提交到仓库的代码是符合规范的。4.1 配置Husky与lint-stagedHusky允许我们方便地定义Git钩子如pre-commit,pre-push。lint-staged则允许我们对**暂存区staged**的文件运行特定的脚本避免每次提交都检查整个项目。安装依赖pnpm add -D husky lint-staged初始化Huskynpx husky init这个命令会创建.husky目录并在其中添加pre-commit钩子文件。同时它会在package.json中添加一个“prepare”: “husky install”脚本。现在配置lint-staged。在package.json中添加{ // ... 其他配置 lint-staged: { *.{js,ts,jsx,tsx}: [ eslint --fix --max-warnings0, // 对暂存区的JS/TS文件运行ESLint并自动修复 prettier --write // 运行Prettier格式化 ], *.{json,md,yml,yaml}: [ prettier --write // 格式化其他类型的文件 ] } }然后修改.husky/pre-commit文件将其内容替换为#!/usr/bin/env sh . $(dirname -- $0)/_/husky.sh npx lint-staged npx vitest run --changed # 可选对更改的文件运行测试这样每次执行git commit时lint-staged会自动对暂存区中符合条件的文件执行ESLint修复和Prettier格式化。如果ESLint有无法自动修复的错误或测试失败提交就会被阻止。4.2 完善package.json与项目结构让我们回头完善一下package.json添加一些元信息和更合理的脚本。{ name: my-agent-cli, version: 0.1.0, description: A powerful AI Agent CLI tool built with TypeScript, keywords: [cli, agent, ai, typescript, automation], license: MIT, author: Your Name your.emailexample.com, homepage: https://github.com/your-username/my-agent-cli#readme, repository: { type: git, url: githttps://github.com/your-username/my-agent-cli.git }, bugs: { url: https://github.com/your-username/my-agent-cli/issues }, type: module, // 声明包使用ES模块影响Node.js如何解析导入 main: ./dist/cli.js, module: ./dist/cli.mjs, // 为ESM打包工具提供入口 types: ./dist/cli.d.ts, // 类型声明文件入口 bin: { my-agent: ./dist/cli.js }, files: [dist], // 发布到npm时只包含dist目录 scripts: { dev: tsup src/cli.ts --format cjs --watch, build: tsup src/cli.ts --format cjs,esm --dts --clean --minify, start: node dist/cli.js, lint: eslint . --ext .ts,.js --fix --max-warnings0, format: prettier --write ., type-check: tsc --noEmit, test: vitest, test:ui: vitest --ui, test:run: vitest run, test:coverage: vitest run --coverage, prepublishOnly: npm run build, // 在npm publish前自动构建 prepare: husky install }, engines: { node: 18 }, publishConfig: { access: public }, // ... dependencies and devDependencies }项目结构梳理 至此你的项目目录结构应该大致如下my-agent-cli/ ├── .husky/ # Git钩子配置 │ └── pre-commit ├── .vscode/ # 可选编辑器配置 ├── dist/ # 构建输出目录由tsup生成应在.gitignore中 ├── src/ # 源代码目录 │ ├── cli.ts # CLI主入口 │ └── math.test.ts # 示例测试文件 ├── .eslintrc.cjs # ESLint配置 ├── .eslintignore # 可选ESLint忽略文件配置 ├── .prettierrc.json # Prettier配置 ├── .prettierignore # 可选Prettier忽略文件配置 ├── .gitignore # Git忽略文件配置 ├── vitest.config.ts # Vitest配置 ├── tsconfig.json # TypeScript配置 ├── package.json # 项目配置和依赖 └── pnpm-lock.yaml # pnpm锁文件如果是npm则是package-lock.json务必在.gitignore文件中添加dist/,node_modules/,coverage/等目录。5. 常见问题与排查技巧实录在搭建这套基建的过程中你几乎一定会遇到一些问题。以下是我在实际操作中踩过的坑和解决方案希望能帮你快速排雷。5.1 TypeScript配置与模块解析问题问题在src/cli.ts中导入其他模块时VS Code提示“找不到模块”或“没有默认导出”但运行tsc --noEmit类型检查却通过。排查检查tsconfig.json中的“moduleResolution”。如果你使用了“bundler”确保你的打包工具如tsup支持它。对于更传统的Node.js项目可以尝试改为“node16”或“nodenext”。检查导入路径是否正确。使用相对路径‘./utils’或配置了“paths”的别名。重启TypeScript语言服务器。在VS Code中按CmdShiftP(Mac) 或CtrlShiftP(Windows/Linux)输入并选择“TypeScript: Restart TS Server”。5.2 ESLint与Prettier冲突或报错问题保存文件时ESLint和Prettier互相“打架”或者ESLint报告一些奇怪的语法错误如网络热词中的object-curly-spacing错误。解决确保配置顺序正确在.eslintrc.cjs的extends数组中‘prettier’必须放在最后以确保它能覆盖其他配置中与Prettier冲突的规则。检查规则覆盖有时某个插件如typescript-eslint的规则会与Prettier冲突。eslint-config-prettier就是用来解决这个的。确保你已安装并正确扩展了它。规则具体配置像object-curly-spacing这类风格规则应该完全交给Prettier处理。如果ESLint还在报错可以在.eslintrc.cjs的rules中显式关闭它“object-curly-spacing”: “off”。但更推荐的做法是确保eslint-config-prettier生效。编辑器插件设置在VS Code中确保设置了“editor.formatOnSave”: true并且“editor.defaultFormatter”是你的Prettier插件。同时确保ESLint插件已启用并配置为在保存时运行。有时需要设置“eslint.validate”包含“typescript”。5.3 Vitest测试无法识别或运行缓慢问题pnpm test找不到测试文件或者测试运行异常缓慢。排查检查配置文件确认vitest.config.ts中的test.include模式能匹配到你的测试文件。测试文件通常以.test.ts或.spec.ts结尾。检查环境如果你的代码依赖浏览器API如document,window但测试环境配置为“node”就会出错。对于CLI项目环境应为“node”。如果部分代码需要浏览器环境可以考虑使用jsdom环境或使用vi.mock进行模拟。速度问题首次运行Vitest可能会稍慢因为它要收集和转换测试。后续运行在watch模式下会很快。如果一直很慢检查是否在测试中引入了巨大的模块或进行了真实的网络请求/文件IO。尽量使用模拟mock。5.4 构建产物无法运行或行为异常问题pnpm build成功但运行node dist/cli.js或全局链接后的命令报错如Cannot find module。排查检查shebang确保src/cli.ts第一行是#!/usr/bin/env node。检查package.json中的bin字段路径是否正确指向构建后的文件例如./dist/cli.js。检查依赖打包如果你的CLI依赖了某些第三方包默认情况下tsup不会将它们打包进输出文件。这意味着用户安装你的CLI时必须同时安装这些依赖。对于简单的CLI可以将依赖放在dependencies中。对于希望生成单一可执行文件的情况可以使用tsup的--bundle标志注意处理原生模块等边界情况。更复杂的打包可以考虑pkg或nexe。文件权限在Unix系统上构建后的JS文件需要有可执行权限。tsup通常不会设置这个权限。你可以在package.json的“scripts”中添加一个后置脚本例如在build后执行chmod x dist/cli.js。或者更规范的做法是在npm publish后由npm在全局安装时自动处理。5.5 Git Hooks不生效问题配置了Husky和lint-staged但提交时代码没有被检查或格式化。排查Husky是否安装成功确保项目根目录下有.husky文件夹并且里面的钩子文件如pre-commit有可执行权限在Unix系统上。你可以手动运行chmod x .husky/pre-commit。prepare脚本是否运行Husky的安装通常由npm install或pnpm install后自动运行的prepare脚本触发。如果你克隆了一个已有项目可能需要手动运行一次pnpm prepare或pnpm install。lint-staged配置检查package.json中的lint-staged配置路径和命令是否正确。可以手动运行npx lint-staged来测试。Git版本确保你的Git版本在2.9以上Husky对旧版本支持可能有问题。搭建一个坚实的工程基础就像为高楼打下地基。虽然前期花费了一些时间在配置上但当你开始真正编写Agent业务逻辑时你会感谢现在所做的一切代码提示精准、错误在编写时就被捕获、代码风格统一、测试运行迅速、提交前自动检查。这一切都将使你的开发体验变得顺畅并极大地提升项目的可维护性。在下一篇中我们将在这个坚实的基建之上开始设计并实现我们Agent CLI的核心命令解析架构并接入第一个AI模型让我们的工具真正“智能”起来。你会发现有了好的工程习惯添加新功能将变得有条不紊水到渠成。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价