# cli-lib **Repository Path**: tjqm/cli-lib ## Basic Information - **Project Name**: cli-lib - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-05-15 - **Last Updated**: 2026-06-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # cli-lib — Go 命令编排框架 **一次注册 handler,同时跑 CLI / HTTP / Cron / 程序化 / MCP / AI Agent。** License: [MIT](LICENSE) · 状态:v0.6.0 beta(v1.0 前 API 仍可能调整,详见 [CHANGELOG.md](CHANGELOG.md)) > **仓库定位**:本仓库是**公司通用框架**,只展示「cli-lib 能做什么」。它不含任何业务原型,也不含个人本机开发环境。 > - 业务项目(拿 cli-lib 做的东西)放各自独立仓库; > - 个人开发环境(webterm/Caddy/tmux 等)放个人私有仓库; > - 这里的 `examples/` 都是无业务、无机器绑定、可独立编译的教学示例。 ## Quick Start ```bash go get gitee.com/tjqm/cli-lib/sdk ``` `commands.yaml`: ```yaml commands: 1: { name: ping, title: 健康检查, auto_safe: true } scenarios: smoke: { command: "1" } ``` `main.go`: ```go package main import sdk "gitee.com/tjqm/cli-lib/sdk" func main() { app := sdk.New("myapp", "demo", sdk.WithConfigPath("commands.yaml")) app.Handle(1, func(ctx *sdk.Context) error { ctx.Output("status", "ok") return nil }) app.Run() } ``` ```bash go run . run 1 # CLI go run . run -s smoke # 跑场景 go run . --mcp # 当 MCP server 跑(Claude Desktop / Cursor 可直连) ``` 想看更完整的用法:[`examples/gate_expr`](examples/gate_expr/) 演示动态闸门,[`examples/web`](examples/web/) 是 HTTP + 鉴权全链路,[`examples/mcp`](examples/mcp/) 接入 Claude Desktop。下面是详细文档。 --- ## 多种入口,同一次注册 | 入口 | 代码 | 典型场景 | |---|---|---| | CLI | `app.Run()` 后用 `myapp run 41` | 运维终端操作 | | **Daemon** | `myapp serve -a :8080` | 长驻服务:HTTP + cron + 健康检查 | | Web | `app.Web()` → gin 路由 | 管理后台 / REST API / **SSE 流式** | | **MCP** | `app.MCP()` | Claude Desktop / Cursor / AI Agent 直接调 | | 程序化 | `app.Exec(ids, inputs)` | cron / gRPC / MQ 消费 | | **带取消** | `app.ExecCtx(ctx, ids, inputs)` | 需要超时控制的调用 | --- ## 安装 ```bash go get gitee.com/tjqm/cli-lib/sdk ``` 最小项目只引 `sdk`。内部 `core/` 包(expr / registry / recorder)可独立引用,但大多数场景不需要。 --- ## 快速开始 ### 1. CLI 工具(30 秒) ```go // main.go package main import sdk "gitee.com/tjqm/cli-lib/sdk" func main() { app := sdk.New("hello", "Hello CLI") app.Handle(1, func(ctx *sdk.Context) error { ctx.Output("msg", "hello world") return nil }) app.Run() } ``` ```bash go run main.go run 1 # → ● Step 1: hello world ... 完成 # outputs: { msg: "hello world" } ``` ### 2. Web API(1 分钟) ```go r := gin.Default() web, _ := app.Web() // 鉴权中间件(gin 原生支持) api := r.Group("/api/v1") api.Use(yourAuthMiddleware) // JWT / API Key / IP 白名单 web.RegisterRoutes(api) r.Run(":8080") ``` ```bash curl -X POST http://localhost:8080/api/v1/runs \ -d '{"expression":"1","inputs":{"date":"2026-05-13"}}' # 同步:直接返回结果 # {"run_id":"...", "status":"done"} # 异步:不阻塞长时间流程 curl -X POST http://localhost:8080/api/v1/runs \ -d '{"expression":"1-54", "async":true}' # {"run_id":"...", "status":"pending"} # 轮询: GET /api/v1/runs/:run_id ``` ### 3. Prometheus 接入(30 秒) ```go app := sdk.New("myapp", "My App", sdk.WithMetrics()) web, _ := app.Web() web.RegisterMetrics(r.Group("/api/v1")) // GET /api/v1/metrics 现在输出: // sdk_steps_total{id="1",name="ping",status="done"} 42 // sdk_steps_duration_seconds{id="1",name="ping"} ... // sdk_runs_active 1 ``` 也支持 `sdk.WithMetricsRegistry(customRegisterer)` 与 gin 生态的 prometheus 中间件共用。 ### 4. 程序化调用(定时任务 / gRPC) ```go // cron c.AddFunc("0 9 * * 1-5", func() { app.ExecScenario("daily", map[string]interface{}{"date": today()}) }) // gRPC(带超时 + 可取消) ctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() outputs, err := app.ExecCtx(ctx, ids, req.Inputs) ``` ### 5. 高频运维能力 ```bash # 历史耗时与成功率 myapp stats 500 --days 30 # 参数网格,自动展开为 4×2 次执行并汇总 myapp run 500 \ --grid 'batch_size=[10,20,30,50]' \ --grid 'mode=["fast","safe"]' # 手动 CLI 遇到同流程锁时排队;默认仍是立即失败 myapp run -s daily --queue # Ctrl+C 后,已完成步骤和当前步骤状态已自动落盘 myapp resume ``` 命令与场景可直接声明缓存、超时和废弃策略: ```yaml commands: 500: name: batch_job title: 批量数据处理 cache: 24h # 相同输入 24 小时内复用步骤输出 timeout: 30m timeout_mode: soft # hard=取消;soft=只告警并继续等待 outputs: [score, error_count] inputs: - name: batch_size type: int required: true validate: "value >= 5 && value <= 100" validate_message: "batch_size 需在 5-100 之间" 501: name: old_batch_job title: 旧批处理 deprecated: true deprecated_message: "已合并到 500" scenarios: daily: command: "500" timeout: 2h # 整个场景的总超时 ``` 执行长场景时 CLI 会根据最近 30 天的 p50 耗时显示“历史估剩”;handler 实际输出与 `outputs` 声明不一致时会记录非阻塞 Schema 告警。`list` 默认隐藏废弃命令,`list --all` 可查看全部。 --- ## 类型化注册 (HandleT) — 新写法 旧写法 `app.Handle` 需要同时维护 YAML 里的 `inputs`/`outputs` 和 Go 代码里的 `ctx.InputWithDefault`——两处定义易不同步。HandleT 让 **struct tag 成为 schema 的唯一来源**。 ### 四种注册 API ```go // 有入参有出参 — HandleT sdk.HandleT(2, "拉取数据", func(ctx *sdk.Context, in FetchIn) (FetchOut, error) { return FetchOut{Count: 120}, nil }) // 有入参无出参 — HandleNoOut sdk.HandleNoOut(31, "筛选记录", func(ctx *sdk.Context, in FilterIn) error { return nil }) // 无入参有出参 — HandleNoIn sdk.HandleNoIn(5, "可用日期", func(ctx *sdk.Context) (CountOut, error) { return CountOut{Count: 242}, nil }) // 无入参无出参 — HandleVoid sdk.HandleVoid(95, "同步守护", func(ctx *sdk.Context) error { return nil }) ``` ### struct tag 推导规则 | tag | 效果 | |---|---| | `json:"field_name"` | Input/Output 字段名 | | `json:"field,omitempty"` | 可选字段 | | `json:"-"` | 跳过(不出现在 schema 中) | | `required:"true"` | 必填,缺失时 Exec 返回错误 | | `default:"值"` | 默认值,自动填充 | | `example:"…"` | 示例值(展示用) | | `hint:"…"` | 字段提示(展示用) | | `validate:"expr"` | 输入校验表达式,运行前统一校验 | | `validate_message:"…"` | 校验失败时返回的提示 | `validate` 会在 CLI、Web 预检、MCP schema 和 `HandlerContract` 里一起生效,适合把“batch_size 必须 5-100”这类规则集中写在 struct tag 里。 Go 类型自动映射:`string`→string, `int`/`int64`→int, `float64`→float, `bool`→bool, 复杂类型 (slice/map/struct 指针) → string (JSON)。 匿名嵌入 struct 的字段会自动展开,YAML 中同 ID 的旧 inputs/outputs 会被 HandleT 覆盖。 ### 旧写法 ↔ 新写法对比 ```go // ===== 旧写法 ===== app.Handle(1, func(ctx *sdk.Context) error { date := ctx.InputWithDefault("date", "today") // 手工取值 ctx.Output("result", "ok") // 手工写输出 return nil }) // YAML 里还要写: inputs: [{name: date, default: today}], outputs: [result] // ===== 新写法 ===== type MyIn struct { Date string `json:"date" default:"today"` } type MyOut struct { Result string `json:"result"` } HandleT(1, "示例", func(ctx *sdk.Context, in MyIn) (MyOut, error) { return MyOut{Result: "ok"}, nil }) // YAML 里不需要 inputs/outputs——struct 即 schema ``` 新旧写法可混用。已有 `app.Handle` 命令不受影响,新命令可以用 HandleT 省掉 YAML 里的 schema 维护。`HandleT` 可以在 `New()` 前注册,也可以在 registry 已经加载后再注册;后续 `Exec` / `ExecExpr` / `ExecScenario` 会先收集 pending handler,并把新的 schema 叠加到当前 registry。 > 详见 `examples/handlet_demo/`。 --- ## 安全机制 ### AutomationLevel 五档灰度 ``` shadow → review → draft → publish → live (只看) (人审) (警告) (自动) (全自动) ``` | 等级 | review_gate 命令 | auto_safe=false 命令 | |---|---|---| | shadow | ❌ 硬阻止 | ❌ 硬阻止 | | review | ⚠️ CLI 弹 [y/N] | ❌ 硬阻止 | | draft | ⚠️ CLI 弹 [y/N] | ⚠️ 警告后可跳过 | | publish | 自动通过 | ⚠️ 警告后可跳过 | | live | 自动通过 | 自动通过 | 闸门对 **CLI / Web / 程序化 / MCP 四条入口统一生效**(共用 `checkGate`)。非交互入口(Web async/SSE、`Exec*`、MCP)弹不了 `[y/N]`,对「需人工确认」的命令按交互模式下「拒绝」的语义降级: - `review_gate` 命令 → 返回 `ErrGateBlocked`,终止运行(调用方可 `errors.Is` 判别) - `auto_safe=false` 命令 → 跳过该步,继续后续 也就是说:**想让 cron / AI Agent 自动执行高风险命令,必须显式把 `automation.level` 调到 publish/live** —— level 本身就是那个「可调节的刹车」。 对 AI Agent 的价值:**给 LLM 的自主度加一个可调节的刹车**。开发时 shadow,上线后 review,信任后 live。 ### 高风险写操作示例 ```yaml 71: name: prod:delete_records auto_safe: false review_gate: true ``` ```bash $ myapp run 71 -i table=orders -i where="created<2026-01-01" ⚠ 审核闸门: 步骤 71 (删除生产数据) 描述: 调用生产库删除记录(不可逆动作) 是否批准? [y/N]: N → 阻止。AI 写错条件也删不了库。 ``` --- ## 断点续跑 (Resume) 7 步流程在第 4 步挂了,只重试第 4 步开始: ```bash $ myapp run 41 # step 4 由于外部 API 超时失败 → run.json: step 1-3 done, step 4 failed $ myapp resume 20260513_222132_b0c07053 → 从 step 4 开始,step 1-3 的历史完整保留 ``` 不需要担心幂等问题——前面成功的步骤不会再跑。 两次运行都成功、但结果发生变化时,可直接对比运行级输入和每步输出: ```bash $ myapp diff 20260606_160000_001_old 20260607_160000_001_new Inputs: batch_size 30 → 50 (+20) Step 500 批量数据处理: output.score 1.23 → 1.45 (+0.22) output.error_count 15 → 12 (-3) ``` ## 历史回放 场景可按日期范围顺序回放。范围包含首尾,并按自然日展开: ```bash $ myapp run daily --dates 2026-05-01..2026-05-31 ``` SDK 不内置工作日/节假日日历。需要只回放特定日期(如仅工作日)时,提供日期文件: ```text # dates.txt 2026-05-06 2026-05-07 2026-05-08 ``` ```bash $ myapp run daily --replay-file dates.txt ``` 每个日期生成独立 run_id 和运行记录。单日失败不会中断后续日期,结束后统一列出成功数、失败日期和原因;存在失败时命令返回非零退出码。 ## 依赖健康检查 `--check` 在正式执行前检查所选命令的依赖。任一检查失败时不会创建 run,也不会调用 handler: ```bash $ myapp run daily --check ``` 通用文件和 TCP 依赖可直接声明在命令上: ```yaml commands: 500: name: batch_job:run title: 批量数据处理 auto_safe: true checks: - name: 输入数据 type: file path: data/input.parquet max_age: 24h - name: 上游服务 type: tcp address: 127.0.0.1:7709 timeout: 3s ``` `writes` 表示命令产出的资源,不会被当作前置依赖。读取数据最大日期等业务检查通过代码注册: ```go app.HealthCheck(500, "输入数据日期", func(ctx context.Context, inputs map[string]interface{}) (string, error) { maxDate, err := readParquetMaxDate("data/input.parquet") if err != nil { return "", err } if maxDate.Before(time.Now().AddDate(0, 0, -1)) { return "", fmt.Errorf("最新日期 %s,数据已过期", maxDate.Format("2006-01-02")) } return "最新日期 " + maxDate.Format("2006-01-02"), nil }) ``` 历史回放与 `--check` 可以组合;某日检查失败时跳过该日执行,继续检查和运行后续日期。 ## 场景条件分支 场景可根据前序步骤的顶层输出决定是否执行后续步骤: ```yaml scenarios: daily_live: steps: - 600 - 610: when: "#600.candidates_count >= 5" - 900: when: "#600.candidates_count < 5" ``` 条件为假时,该步骤记录为 `skipped`;引用字段不存在或条件求值失败时,运行失败并给出具体引用。条件只能引用列表中更早的步骤。并行执行时,条件引用会自动成为隐式依赖。dry-run 只展示计划,不求值条件。 代码注册使用同一套语义: ```go sdk.Scenario("daily_live"). Step(600). StepWhen(610, "#600.candidates_count >= 5"). StepWhen(900, "#600.candidates_count < 5") ``` --- ## 测试:AI 写单测 3 行起步 ```go import ( "testing" sdk "gitee.com/tjqm/cli-lib/sdk" "gitee.com/tjqm/cli-lib/sdk/testkit" ) func TestMyHandler(t *testing.T) { app := testkit.TestApp(t, ` commands: 1: { name: ping, title: Ping, description: hi, auto_safe: true } `) app.Handle(1, func(ctx *sdk.Context) error { ctx.Output("ok", true) return nil }) out := testkit.MustExec(t, app, []int{1}, nil) // out = {"ok": true} } ``` - `testkit.TestApp(t, yaml)` — 一行构造 App,tempdir 自动清理 - `testkit.SimpleApp(t, "1:a:A", "2:b:B")` — 更短的声明 - `testkit.MustExec(t, app, ids, inputs)` — 失败直接 Fatal 每个 handler 一个测试。AI 看模板照抄,10 秒一个。 --- ## AI 协作工作流 ### 1. 导出工具定义给 LLM ```go tools, _ := app.Schema(sdk.SchemaFormatAnthropic) // 直接传给 Claude API 的 tools 字段 ``` 产出: ```json { "name": "cmd_71_prod_delete_records", "description": "命令 71: 删除生产数据。调用生产库删除记录(不可逆动作)。⚠ review_gate", "input_schema": { "type": "object", "properties": { "table": { "type": "string" }, "where": { "type": "string" } }, "required": ["table", "where"] } } ``` LLM 拿到这些 tool 后,`expression` + `inputs` 就能调命令,比构造嵌套 JSON 嵌套数组少出错。 ### 2. 把 run.json 当复盘数据 每次 run 都自动写 JSON 记录——喂给 AI 分析为什么某个 handler 输出不对: ```bash $ cat reports/runs/.../run.json # 复制 → 粘贴给 AI: # "为什么 step 11 的 rsi 输出 85 但 step 31 的 signal 是 hold?" ``` ### 3. 项目脚手架约定 新项目落地时,推荐这套约定(`examples/demo` 是最小起步骨架): - ID 按业务阶段分区间(如 采集 1-9、加工 11-29、决策 31-49、产出 51-69、高风险写 71-89) - 每个新 handler 复制最近的同类 handler 改 - 不可逆/有副作用的动作必须 `review_gate: true` - 每个 handler 配一份单测(参考 `sdk/testkit/`) 把这套约定当脚手架,新项目从结构到测试都成体系。 --- ### 4. MCP Server — 一行接入 Claude Desktop ```go app.MCP() // 所有命令自动变成 MCP tools ``` 除了命令/场景的执行工具,MCP 还暴露一组**只读内省工具**(`capabilities` / `capability_digest` / `handler_contract` / `recent_failures` / `list_runs` / `get_run` / `trace_artifact`),让 AI Agent 自助发现能力、查契约、看历史失败与产物来源,不必靠外部喂上下文。 然后在 Claude Desktop 的 `claude_desktop_config.json` 中配置: ```json { "mcpServers": { "myapp": { "command": "go", "args": ["run", "/path/to/your/main.go"] } } } ``` Claude Desktop 重启后就能直接发现并使用所有命令: > **用户**: "检查一下系统状态" > **Claude**: 好的,我来调用 system:checkup... > > **用户**: "删除 orders 表里 2026 年前的记录" > **Claude**: ⚠ 这个操作有 review_gate 保护,需要你确认... 每个命令的 YAML inputs、review_gate、auto_safe 等信息自动映射到 MCP tool 的 description 和 inputSchema 中,LLM 不需要额外配置就知道哪些参数必填、哪些操作有风险。 --- ## 表达式速查 | 格式 | 示例 | 结果 | |---|---|---| | 单号 | `41` | [41] | | 列表 | `1,2,3` | [1, 2, 3] | | 范围 | `1-10` | [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | | 排除 | `1-10,!7` | [1, 2, 3, 4, 5, 6, 8, 9, 10] | | 组合 | `1,5-8,!7` | [1, 5, 6, 8] | CLI 也支持 `--skip "31-35,!33"` 和 `--from 21 --until 42`。 --- ## 性能设计要点 | 决策 | 理由 | |---|---| | `map[string]interface{}` 而非 `map[string]any` + typed | 与 YAML/JSON 生态兼容,零序列化开销 | | 无反射、无 ORM、无 DI 容器 | 启动速度最快,二进制最小 | | `sync.Mutex` 窄锁(只锁 registry 加载) | 大部分路径无锁竞争 | | gin 集成而非自建 HTTP | 复用 gin 的零分配 router + 成熟生态 | | Recorder 异步写可选(async 模式) | 不影响 HTTP 响应延迟 | | 固定 striped lock(64 条)而非动态 mutex map | 并发 Web 请求内存上限常数 | 已知上限:单步 handler 开销约 2-5 μs(context 创建 + hook 触发 + logger),主要耗时在用户 handler 内部。十万级 QPS 的 CRUD 场景不要用来做主链路——它定位是编排任务,不是高频在线服务。 --- ## 项目结构 ``` ├── sdk/ 对外包,新项目只引这里 │ ├── sdk.go App / Handle / Exec / ExecCtx / Run │ ├── context.go 命令上下文 (IO + logger + cancel) │ ├── options.go 配置选项 │ ├── schema.go AI function-calling schema 导出 │ ├── web.go Gin 集成(REST + SSE + async + 优雅关闭) │ └── testkit/ 测试辅助(TestApp / SimpleApp / MustExec) ├── core/ 内部实现,一般不用直接碰 │ ├── expr/ 表达式解析 │ ├── registry/ YAML 加载 + 复合命令递归 │ └── recorder/ run.json / SQLite 双实现 + Resume + 幂等存储 ├── examples/ 通用教学示例(无业务、无机器绑定、可独立编译) │ ├── demo/ CLI 全功能演示(旧写法 app.Handle) │ ├── handlet_demo/ HandleT 类型化注册演示(新旧对比) │ ├── web/ Web API 演示(含鉴权示例) │ ├── programmatic/ 程序化调用演示 │ ├── mcp/ MCP Server 示例(Claude Desktop 集成) │ ├── gate_expr/ 动态闸门表达式演示(review_gate_expr / auto_safe_expr) │ ├── print_showcase/ handler 内三线表 / 状态行 / 键值对打印 │ ├── docsdemo/ OpenAPI 文档页演示 │ └── agent_loop/ AI Agent 自助一圈(发现→契约→按名执行→从失败学→撞护栏) ├── config/ 默认配置模板(commands.yaml) ├── docs/ 架构与测试指南(architecture.md / testing.md) ├── FEATURES.md 功能全览速查(16 个 section) ├── QUICKSTART.md 5 分钟快速上手 ├── CHANGELOG.md 变更记录 └── README.md ``` --- ## 完整 API 速览 ```go // 创建 App app := sdk.New(name, desc, opts...) // 注册 handler(旧写法) app.Handle(id, func(ctx *sdk.Context) error { ... }) // 注册 handler(新写法,struct tag 即 schema) sdk.HandleT[In, Out](id, name, func(ctx *sdk.Context, in In) (Out, error) { ... }) sdk.HandleNoIn[Out](id, name, func(ctx *sdk.Context) (Out, error) { ... }) sdk.HandleNoOut[In](id, name, func(ctx *sdk.Context, in In) error { ... }) sdk.HandleVoid(id, name, func(ctx *sdk.Context) error { ... }) // 同步执行 outputs, err := app.Exec([]int{1, 11}, inputs) // Exec 明确传入未知 ID 时返回 ErrCmdNotFound;如需兼容宽范围空号,可加 WithAllowUnknownID。 outputs, err = app.Exec([]int{1, 999}, inputs, sdk.WithAllowUnknownID()) // 同步 + 超时/取消 outputs, err := app.ExecCtx(ctx, []int{1, 11}, inputs) // 按表达式 outputs, err := app.ExecExpr("1-10,!5", inputs) outputs, err := app.ExecExprCtx(ctx, "1-10,!5", inputs) // ExecExpr 默认允许跳过范围里的未定义 ID;如需 typo fail-fast,可加 WithStrictUnknownID。 outputs, err = app.ExecExpr("999", inputs, sdk.WithStrictUnknownID()) // 按场景 outputs, err := app.ExecScenario("daily", inputs) outputs, err := app.ExecScenarioCtx(ctx, "daily", inputs) // 清理过期记录 n, err := app.Cleanup() // Web 集成 web, err := app.Web() web.RegisterRoutes(ginRouter) // /runs(同步+async)、/runs/stream(SSE)、 // /runs/:id/resume、/runs/:id/cancel、/commands… web.RegisterAdmin(ginRouter) // 嵌入管理看板 (GET /) web.RegisterMetrics(ginRouter) // 暴露 /metrics (Prometheus) web.SetEngine(ginEngine) // 设置 engine 引用(供 Serve 优雅关闭) web.Serve(":8080") // 启动 HTTP + 监听信号优雅关闭 web.Close() // 停止后台 goroutine(Serve 时自动调用) // 一站式 daemon(HTTP + cron 调度器 + 健康检查 + 优雅关闭) app.ServeDaemon(":8080") // MCP 服务器(Claude Desktop / Cursor) app.MCP() // AI schema data, _ := app.Schema(sdk.SchemaFormatOpenAI) data, _ := app.Schema(sdk.SchemaFormatAnthropic) // 生命周期 hook app.OnHook(func(a *sdk.App, phase string, ctx *sdk.Context) { // phase: before_step / after_step / after_step_failed }) // Context 方法 ctx.ID() // 当前命令 ID ctx.Command() // 元信息 ctx.Input("key") // 读输入 (interface{}) ctx.InputString("key") // 读输入 (string) ctx.InputWithDefault("key", "fallback") // 带默认 ctx.Bind(&myStruct) // 类型安全:inputs 按 json tag 映射到结构体 ctx.Output("key", val) // 写输出 ctx.OutputFrom(&myStruct) // 类型安全:结构体按 json tag 批量写入 outputs ctx.Outputs() // 所有输出 ctx.Ctx() // context.Context(可取消/超时) ctx.Logger() // *slog.Logger(结构化日志) ctx.IsDryRun() // 是否预演模式 // 配置选项 sdk.WithConfigPath(path) sdk.WithReportDir(dir) sdk.WithVersion("1.0.0") sdk.WithDryRun(true) sdk.WithPassOutput(false) sdk.WithLogger(slogLogger) sdk.WithRequestTimeout(30 * time.Second) sdk.WithAuth(ginMiddleware) // 注入鉴权中间件,所有 Web 路由自动生效 sdk.WithMaxConcurrentRuns(64) // Web 异步(async/SSE)运行并发上限,超出返回 429 sdk.WithExecAllowlist("rsync","curl") // 限制 ExecHandler 只能执行白名单内的命令 sdk.WithRecord() // 程序化/MCP 执行也落 run 记录(进失败经验库,适合 AI Agent) sdk.WithMetrics() // Prometheus metrics(默认 registerer) sdk.WithMetricsRegistry(registerer) // 自定义 registerer sdk.WithStore(store) // 自定义持久化 Store(默认 FileStore) sdk.WithDB("runs.db") // GORM + SQLite 持久化(进程重启不丢) sdk.WithIdempotencyStore(idemStore) // 自定义幂等存储(多副本共享后端) sdk.WithRunTTL(7*24*time.Hour) // 运行记录 TTL(7 天自动删除) sdk.WithCleanupInterval(30*time.Minute) // 后台清理间隔(默认 1h) ``` ### 持久化与多副本 | 选项 | 后端 | 适用 | |---|---|---| | 默认 | `FileStore` — `reports/runs//run.json` | 单实例 / CLI | | `WithDB(dsn)` | GORM + 内嵌 SQLite,进程重启不丢;初始化失败自动降级文件存储 | 单实例长期服务 | | `WithStore(s)` | 任意自定义 `recorder.Store` 实现 | 接 PG / MySQL 等 | 幂等存储默认基于文件(仅单实例有效);设了 `WithDB` 时**自动复用同一 SQLite 连接**,多副本只要共享数据库幂等就跨实例生效。也可用 `WithIdempotencyStore` 注入 Redis 等自定义后端。所有 `runID` 在文件存储层做路径穿越校验。 --- ## 后续规划 ### 已完成(v0.3 / v0.4) - [x] MCP Server — `app.MCP()` - [x] Prometheus metrics — `WithMetrics()` + `/metrics` - [x] Recorder 后端接口化 — `Store` + FileStore + DBStore(SQLite) + `WithStore()` / `WithDB()` - [x] Run 记录 TTL 自动清理 - [x] Idempotency Key — `WithDB` 时自动跨副本生效 - [x] 流式 SSE — `POST /runs/stream` + 程序化 `WithSSEChannel(ch)` - [x] Web Admin 只读仪表盘 — `web.RegisterAdmin(router)` - [x] 命令热加载(fsnotify)— 500ms 去抖 - [x] HandleT 类型化注册 — struct tag 即 schema - [x] DAG 并行执行 + 步骤级 retry/timeout(含 full-jitter 指数退避) - [x] Scenario 级 cron 调度 — `StartScheduler` / `ServeDaemon` - [x] 哨兵错误集中(`errors.Is`)+ registry 引用完整性校验 ### v1.0 (中期) - [ ] 分布式锁(Redis,多实例安全) - [ ] 命名空间(团队隔离) - [ ] 命令版本管理 + 弃用机制 - [ ] Web Admin UI — 完整版(DAG 可视化 + 操作按钮) ### v2.0 (长期) - [ ] 字符串层级 ID (`billing.invoice.generate`) - [ ] Skill 包管理 + 分发 (`skillctl install`) - [ ] 语义搜索(embedding 向量召回) - [ ] 分布式 workflow 引擎(跨进程 retry / saga) --- ## 与其他方案的对比 | | 本 SDK | Temporal | LangChain Tools | 手撸 gin | |---|---|---|---|---| | 三入口复用 | ✅ | ❌ | ❌ | ❌ | | ReviewGate 安全护栏 | ✅ | ❌ | ❌ | ❌ | | 断点续跑 | ✅ | ✅ | ❌ | 需自建 | | AI schema 一键导出 | ✅ | ❌ | ✅ | ❌ | | 学习曲线 | 分钟级 | 周级 | 小时级 | 分钟级 | | 多步骤编排 | ✅ | ✅✅ | ⭐⭐ | 需自建 | | 分布式可靠 | ❌ | ✅✅ | ❌ | 视实现 | | MCP 协议 | ✅ | ❌ | ❌ | ❌ | **定位**:比 Temporal 轻(不需要集群),比 LangChain 严肃(有审计和安全),比手撸 gin 多了编排/回放/护栏。 --- ## 贡献 欢迎提 Issue / PR。 新策略加一份单测(参考 `sdk/testkit/` 模板)、写在 CHANGELOG、跑 `go vet ./... && go test -race ./...`。