# 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-09-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Command SDK — AI-友好的命令编排框架 **写一遍业务逻辑,CLI / Web / Cron / 程序化 / MCP(AI Agent) 五点复用。** > **🤖 看 2 分钟实录,胜过读 2 小时文档:** [DEMO.cast](tools/opsdemo/DEMO.cast)(asciinema 录屏) > —— AI 预审发布计划 → 被 schema 漂移真实打断 → `explain` 自诊 → 修复 → > `resume` 断点续跑 → 生产部署被 review_gate 拦下、人工批准 → 全绿。 > 完整逐幕解说见 [tools/opsdemo/DEMO.md](tools/opsdemo/DEMO.md)。 > **English:** [README_EN.md](README_EN.md) · 设计宣言: [MANIFESTO.md](MANIFESTO.md) 三行接入,**1,000+ 个测试函数**(含子测试;`go test ./... -v` 实测),`go test -race` 全绿,`go vet` 干净。 > **状态:main(v0.10 开发线;v0.9.0 已发布,见 tag)** — 功能稳定、测试齐全,但在 v1.0 之前 API 仍可能调整(详见 [CHANGELOG.md](CHANGELOG.md))。生产用请固定到具体版本号。已知代码债见 [docs/tech-debt.md](docs/tech-debt.md)(当前未清为零),v1.0 准入清单见 [docs/v1-checklist.md](docs/v1-checklist.md)。 > > **License:** [MIT](LICENSE) --- ## 5 秒看懂 ```go import sdk "gitee.com/tjqm/cli-lib/sdk" app := sdk.New("myapp", "My App", sdk.WithConfigPath("cmds.yaml")) // 写一遍 handler app.Handle(1, func(ctx *sdk.Context) error { ctx.Output("status", "healthy") return nil }) // ① CLI: $ myapp run 1-10 // ② HTTP API: POST /api/v1/runs {"expression":"1-10"} // ③ 程序里: app.Exec([]int{1,2,3}, nil) // ④ 你自己的服务: app.ExecCtx(ctx, ids, inputs) // gRPC/HTTP/MQ 消费者里直接调 // ⑤ AI Agent: app.Schema(sdk.SchemaFormatOpenAI) → function-calling JSON // 或使用 HandleT:struct tag 即 schema,不用在 YAML 里重复定义 sdk.HandleT(2, "抓取日K线", func(ctx *sdk.Context, in DailyIn) (DailyOut, error) { return DailyOut{Count: 120, Source: "akshare"}, nil }) ``` 每个命令在 YAML 里声明元信息: ```yaml commands: 1: { name: ping, title: Ping, description: 健康检查, auto_safe: true } 78: name: publish title: 发布 auto_safe: false # 有副作用 review_gate: true # 需要人工审核 rollback: 79 # 出错时回滚 scenarios: daily: { command: "1-10" } automation: level: review # shadow → live 五级 ``` **名字就是主键**(v0.8 起):配置可以完全不写数字 ID——`data:fetch: {...}` 名字做 key, `requires: [data:fetch]`、`rollback:`、场景 steps/`#名字.字段` 全部认名字,CLI 直接 `myapp run data:fetch`;改名用 `aliases` 兼容。数字 ID 退为内部句柄(由名字哈希派生, 稳定不变),旧数字 key 配置与 `Handle(id, ...)` 无需迁移。详见 GUIDE「名字是主键」。 --- ## 多种入口,同一次注册 | 入口 | 代码 | 典型场景 | |---|---|---| | CLI | `app.Run()` 后用 `myapp run 41` | 运维终端操作 | | **Daemon** | `myapp serve -a :8080` | 长驻服务:HTTP + cron + 健康检查 | | Web | `app.Web()` → 标准库 HTTP 路由 | 管理后台 / 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 app := sdk.New("demo", "示例", sdk.WithAuth(yourAuthMiddleware)) // 标准中间件 func(http.Handler) http.Handler r := gin.Default() web, _ := app.Web() ginlib.Mount(r.Group("/api/v1"), web) // contrib/gin:把框架 API 面(含仪表盘)挂到你的 gin 分组 r.Run(":8080") // 不用 gin 也可以(核心不依赖任何第三方 router): // mux := http.NewServeMux() // mux.Handle("/api/v1/", web.Handler()) ``` ```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.MetricsHandler() // 仅指标面,可单独端口暴露 // 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/HTTP handler,带超时 + 可取消) ctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() outputs, err := app.ExecCtx(ctx, ids, req.Inputs) // 幂等键:同 key 同请求复用句柄,异请求 409(配合分布式后端即"补跑不重复执行") outputs, err = app.ExecCtx(ctx, ids, inputs, sdk.WithIdempotencyKey("daily-2026-09-18")) ``` --- ## 类型化注册 (HandleT) — 新写法 旧写法 `app.Handle` 需要同时维护 YAML 里的 `inputs`/`outputs` 和 Go 代码里的 `ctx.InputWithDefault`——两处定义易不同步。HandleT 让 **struct tag 成为 schema 的唯一来源**。 ### 四种注册 API ```go // 有入参有出参 — HandleT sdk.HandleT(2, "日K线", func(ctx *sdk.Context, in DailyIn) (DailyOut, error) { return DailyOut{Count: 120}, nil }) // 有入参无出参 — HandleNoOut sdk.HandleNoOut(31, "选股", func(ctx *sdk.Context, in SelectIn) 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:"…"` | 字段提示(展示用) | 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 维护。 > 详见 `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: live:place_order auto_safe: false review_gate: true ``` ```bash $ myapp run 71 -i symbol=AAPL -i side=buy -i qty=100 ⚠ 审核闸门: 步骤 71 (实盘下单) 描述: 调用券商 API 下单(不可逆动作) 是否批准? [y/N]: N → 阻止。AI 写错策略也下不了单。 ``` --- ## 执行后端(分布式执行,v0.9.0) Core 保证纪律(schema/闸门/审计/回放),Backend 决定任务在哪里跑——**换后端不换纪律**。 内置 PostgreSQL 队列后端([riverqueue/river](https://riverqueue.com)),五能力全部经多副本实战验证: ```go import "gitee.com/tjqm/cli-lib/contrib/river" pool, _ := pgxpool.New(ctx, dsn) river.Migrate(ctx, pool) backend, _ := river.New(pool) // 崩溃重投窗口默认 3 分钟 app := sdk.New("mytool", "我的工具", sdk.WithConfigPath("commands.yaml"), sdk.WithBackend(backend), // 一行接入:CLI/Web/程序化全部持久化 ) backend.Start(ctx) ``` - **不丢**:提交即持久化,进程崩溃后任务仍在队列,rescuer 自动重投续跑 - **不重**:`WithIdempotencyKey` 同请求复用句柄、异请求 409,补跑不重复执行 - **定时**:`backend.SubmitScheduled(ctx, req, runAt)` 到点执行,可提前取消 - **审批门**:命令声明 `pause_for_approval`,跨副本 `POST /runs/:id/approve|reject` - **脱敏**:命令声明 `sensitive_inputs/sensitive_outputs`,执行记录与日志回显自动打码 - **保留期**:`river.WithWebRunRetention(24*time.Hour)` 自动清理终态记录 Web 侧零改动:`POST /runs {"async":true}` 自动入队,`GET /runs/:id` 跨副本可查, `/runs/stream` 支持实况跟随与断线重连。实战样例:`tools/stockdaily`(`STOCKDAILY_SERVE=1`), 设计与边界详见 [docs/web-distributed-execution.md](docs/web-distributed-execution.md)。 --- ## 试验中的通用 Go 应用运行底座 除现有 CLI/Task API 外,仓库正在建设可选的 `appruntime` 与本地 `realtime`:前者管理长生命周期服务及资源,后者提供有界、进程内的事件广播。`contrib/` 适配器和量化、AI 实时流、HTTP、GUI 嵌入示例展示了不同应用如何组合这些能力;消费者不需要因此接入完整 Task 栈。AI 示例默认使用本地 demo,也可配置接入 OpenAI-compatible Chat Completions SSE endpoint。 ```go import ( "gitee.com/tjqm/cli-lib/appruntime" "gitee.com/tjqm/cli-lib/contrib/runtimehttp" "gitee.com/tjqm/cli-lib/realtime" ) host, _ := appruntime.New( // 不启 goroutine、不装信号、不做 I/O appruntime.WithStartupTimeout(10*time.Second), appruntime.WithShutdownTimeout(10*time.Second), ) srv, _ := runtimehttp.New(&http.Server{Addr: ":8080", Handler: mux}) host.Add("db", dbSvc) // Owned 资源关闭时恰关一次 host.Add("http", srv, appruntime.DependsOn("db")) // 依赖须已登记且实现 Readiness host.Run(ctx) // Start + Wait;ctx 是生存期信号,不是启动超时 ``` `realtime.Engine` 是有界、易失、进程内广播器:`Publish` 返回 `PublishResult`(`Matched = Accepted + Rejected`,`Dropped`/`Coalesced` 不参与该等式),`nil error` **不代表已处理**,调用方必须检查结果。`WithOverflowLatestByKey` 下每 key 只留最新 1 条(`Coalesced` 计被替换数,是定义行为而非丢失),无 `PartitionKey` 的事件被拒且原因经 `RejectedReasons` 带出。 这部分仍处于实验与验收阶段,不代表 API 已稳定,也不等于生产级通用框架:Realtime 暂无持久化和跨进程传输(`realtime/durable` 只有 `FakeBroker` 协议骨架,不是真的跨进程实现),Runtime 没有服务热替换/自动重启/信号接管,真实外部 provider、目标业务长时容量与外部消费者仍待验证。AI 适配器已由本地 HTTP/SSE 服务完成端到端验证,但尚无真实供应商调用证据。见[架构决策](docs/adr/0001-go-application-runtime-boundaries.md)、[实施与验证状态](docs/runtime-realtime-skeleton.md)和[Realtime 负载验收](docs/realtime-benchmark.md)。 在仓库根目录可运行 HTTP 宿主与本地实时层示例: ```bash go run ./examples/runtime-http go run ./examples/realtime-runtime go run ./examples/ai-stream # 默认本地 demo;也支持配置 OpenAI-compatible SSE provider ``` --- ## 断点续跑 (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 的历史完整保留 ``` 不需要担心幂等问题——前面成功的步骤不会再跑。 恢复时会**从场景定义重建** `when` 条件与步骤级 `inputs`,并从运行记录回填已完成 步骤的产出:所以恢复点之后的步骤引用恢复点之前的输出时,条件与引用都能正常求值 (此前这两样在恢复路径上会丢——条件被整体忽略、步骤级入参静默变零值)。场景定义 已不存在时直接报错,而不是悄悄降级成「无条件执行」。 --- ## 测试: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": "live_place_order", "description": "命令 71: 实盘下单。调用券商 API 下单(不可逆动作)。⚠ 需要人工审核(review_gate)", "input_schema": { "type": "object", "properties": { "symbol": { "type": "string" }, "side": { "type": "string" }, "qty": { "type": "string" } }, "required": ["symbol", "side", "qty"] } } ``` 工具名是**语义名的净化形态**(`data:fetch` → `data_fetch`),不是数字 ID——v0.8 起语义名才是主键。 无名字的命令回退 `cmd_`(**仍受支持**);历史上曾生成 `cmd__<名>` 复合形态,v0.10 起废弃。 MCP 入站各形态都认,旧会话回放不受影响。 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/quant/` 是一个可直接复制的量化骨架,附带 CLAUDE.md 规则: - ID 编号区间约定(数据 1-9、因子 11-29、信号 31-49、回测 51-69、实盘 71-89) - 每个新 handler 复制最近的模板改 - 实盘动作必须 `review_gate: true` - 必须写单测 把这个当脚手架,新项目直接从结构到测试都有模有样。 --- ### 4. MCP Server — 一行接入 Claude Desktop ```go app.MCP() // 所有命令自动变成 MCP tools ``` 然后在 Claude Desktop 的 `claude_desktop_config.json` 中配置: ```json { "mcpServers": { "myapp": { "command": "go", "args": ["run", "/path/to/your/main.go"] } } } ``` Claude Desktop 重启后就能直接发现并使用所有命令: > **用户**: "检查一下系统状态" > **Claude**: 好的,我来调用 system:checkup... > > **用户**: "下单 100 股 AAPL" > **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 生态兼容,零序列化开销 | | 反射只在冷路径(schema 推导/结构体绑定),step 内核无反射;ORM 只在可选 SQLite 持久化内部(`WithDB`);无 DI 容器 | 热路径开销可控,二进制不背不必要的依赖 | | `sync.Mutex` 窄锁(只锁 registry 加载) | 大部分路径无锁竞争 | | 核心走标准库 `net/http`(不绑定第三方 router) | 消费者不必跟着引入 gin:实测 `import sdk` 拉进的 gin 包 **0**;gin 只在 `contrib/gin` 作为可选适配器 | | Recorder 异步写可选(async 模式) | 不影响 HTTP 响应延迟 | | 固定 striped lock(64 条)而非动态 mutex map | 并发 Web 请求内存上限常数 | 框架侧开销以 `docs/bench-baseline.txt` 为准(含 `BenchmarkExecSerialSteps` 等;重跑命令写在文件头), 主要耗时仍在用户 handler 内部。十万级 QPS 的 CRUD 场景不要用来做主链路——它定位是编排任务,不是高频在线服务。 --- ## 项目结构 ``` ├── sdk/ 对外包,新项目只引这里 │ ├── sdk.go App / Handle / Exec / ExecCtx / Run │ ├── context.go 命令上下文 (IO + logger + cancel) │ ├── options.go 配置选项 │ ├── schema.go AI function-calling schema 导出 │ ├── backend.go 执行后端接缝(Backend / BackendCapabilities) │ ├── undo*.go 可逆效应(补偿登记 + 效应日志 + 卸载即回收) │ ├── web.go Gin 集成(REST + SSE + async + 优雅关闭) │ └── testkit/ 测试辅助(TestApp / SimpleApp / MustExec / Verify) ├── process/ 跨平台进程组管理(优雅终止宽限期) ├── appruntime/ 实验性:通用 Go 应用运行底座(有界启停 / 就绪依赖 / 退出监督) ├── realtime/ 实验性:传输无关事件契约(进程内广播 / 订阅隔离 / 过载策略) ├── contrib/ 可选重依赖适配器,不 import 零成本 │ ├── river/ PostgreSQL 队列执行后端 + Web 分布式存储(多副本) │ ├── hatchet/ Hatchet Worker 集成 │ └── runtime*/ Runtime 五类托管适配 + Prometheus 指标 ├── core/ 内部实现,一般不用直接碰 │ ├── expr/ 表达式解析 │ ├── registry/ YAML 加载 + 复合命令递归 │ └── recorder/ run.json / SQLite 双实现 + Resume + 幂等存储 + 运行事件日志 ├── examples/ 可直接复制的样板(23 个) │ ├── quant/ 量化骨架(推荐新人从这里开始) │ ├── demo/ CLI 全功能演示(旧写法 app.Handle) │ ├── handlet_demo/ HandleT 类型化注册演示(新旧对比) │ ├── gate_expr/ 表达式动态闸门(review_gate_expr / auto_safe_expr) │ ├── web/ programmatic/ mcp/ Web API / 程序化调用 / MCP Server 三入口演示 │ ├── agent_loop/ 「AI Agent 自助一圈」:发现能力 → 看契约 → 执行 → 从失败学 │ ├── runtime-*/ Runtime 骨架 / HTTP 宿主 / SDK 内嵌 / GUI 内嵌 │ ├── realtime-*/ Realtime 运行时与 JSONL 回放 │ ├── ai-stream/ chatbus/ loghub/ backupd/ 真实小应用(模型流 / 聊天广播 / 日志告警 / 定时备份) │ └── consumer-app/ 仓库内独立 Go module,演示外部消费者视角接入 ├── tools/ 15 个真实任务形态的工具,兼作活体集成测试(见 tools/README.md) ├── config/ 默认配置模板 ├── docs/ 架构与设计文档(architecture.md / testing.md / tech-debt.md / │ web-distributed-execution.md / runtime-realtime-*.md / adr/) ├── FEATURES.md 功能全览速查(按能力分节,覆盖到 v0.10) ├── 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) // 同步 + 超时/取消 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) // 按场景 outputs, err := app.ExecScenario("daily", inputs) outputs, err := app.ExecScenarioCtx(ctx, "daily", inputs) // 清理过期记录 n, err := app.Cleanup() // Web 集成 web, err := app.Web() web.Handler() // 框架 API 面(/api/v1 下:runs 同步+async、 // runs/stream(SSE)、resume/cancel/approve、 // commands…、管理看板;含 metrics) web.HealthHandler() // 仅 /healthz web.MetricsHandler() // 仅 /metrics 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(yourAuthMiddleware) // 注入鉴权中间件(标准形状 func(http.Handler) http.Handler),所有 Web 路由自动生效 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(文件存储按天粒度删除) sdk.WithCleanupInterval(30*time.Minute) // 后台清理间隔(默认 1h) sdk.WithRecord() // 程序化/MCP 路径也落 run 记录(默认轻量不落盘) sdk.WithBackend(backend) // 执行后端(默认 LocalBackend;见「执行后端」章) sdk.WithRequiredBackendCapabilities(sdk.CapDurable) // 启动期能力准入,不满足 panic sdk.WithLocker(myLocker) // 自定义跨进程锁(默认 FileLocker/flock) sdk.WithBeforeApp(fn) / WithAfterApp(fn) // 启动前/关闭时 hook(初始化与清理) sdk.WithExecutionPolicy(policy) // 所有入口共用的执行授权策略 // v0.10「声明必须可信」一族(默认全关,行为不变) sdk.WithStrictConfig() // 未知/废弃 YAML 键 → 加载失败 sdk.WithRequireToolKind() // 缺 tool_kind → 启动报错 sdk.WithPinnedAutomationLevel(sdk.LevelReview) // 钉死闸门级别(高于 CLI_LEVEL 与环境感知) // undo(可逆效应) sdk.WithUndoLimit(1000) // 效应日志条数上限(默认 1000) sdk.WithUndoJournalPath("undo.jsonl") // 效应日志落盘(默认纯内存) sdk.WithUndoRequirement(sdk.UndoPrompt) // undo 准入闸门(默认 UndoOptional) // 程序化执行的 ExecOption app.ExecCtx(ctx, ids, inputs, sdk.WithIdempotencyKey("k-1")) // 请求级幂等 app.ExecCtx(ctx, ids, inputs, sdk.WithConcurrency(4)) // 并行(按 requires 依赖图) app.ExecCtx(ctx, ids, inputs, sdk.WithoutLock()) // 单次跳过执行锁 app.ExecCtx(ctx, ids, inputs, sdk.WithRecordRun()) // 单次落 run 记录 app.ServeMCP(os.Stdin, os.Stdout) // 进程内驱动 MCP(白盒断言 tools/list 用) ``` ### 持久化与多副本 | 选项 | 后端 | 适用 | |---|---|---| | 默认 | `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(core) + SQLite 后端(contrib/sqlitestore) + `WithStore()` / `WithDB()` - [x] Run 记录 TTL 自动清理 - [x] Idempotency Key — `WithDB` 时自动跨副本生效 - [x] 流式 SSE — `POST /runs/stream` + 程序化 `WithSSEChannel(ch)` - [x] Web Admin 只读仪表盘 — 含在 `web.Handler()` 里 - [x] 命令热加载(fsnotify)— 500ms 去抖 - [x] HandleT 类型化注册 — struct tag 即 schema - [x] DAG 并行执行 + 步骤级 retry/timeout(含 full-jitter 指数退避) - [x] Scenario 级 cron 调度 — `StartScheduler` / `ServeDaemon` - [x] 哨兵错误集中(`errors.Is`)+ registry 引用完整性校验 ### 已完成(v0.9.0)— 执行后端(分布式执行) - [x] Backend 执行后端接缝 — 能力位图 + LocalBackend 参考实现 + 启动准入 - [x] River PostgreSQL 队列后端 — Remote/Durable/Cancel/Scheduled/Distributed 五能力 - [x] 请求级幂等 — `WithIdempotencyKey` 贯通程序化与 Web(PG 唯一索引仲裁) - [x] Web `/runs` 分布式执行 — 跨副本查询/SSE 实况重连/取消审批恢复持久路由(409/404) - [x] 敏感参数脱敏 — `sensitive_inputs/sensitive_outputs` 全路径生效 - [x] Web 运行记录保留期 — `WithWebRunRetention` 周期清理 - [x] 跨进程执行锁(flock)— 同流程互斥 + 分区分片(`WithLockPartition`) ### v1.0 (中期) - [ ] 分布式锁后端扩展(Redis / DB advisory;flock 执行锁与幂等已内置 v0.9) - [ ] 命名空间(团队隔离) - [ ] 命令版本管理 + 弃用机制 - [ ] Web Admin UI — 完整版(DAG 可视化 + 操作按钮) ### v2.0 (长期) - [ ] 字符串层级 ID (`billing.invoice.generate`) - [ ] Skill 包管理 + 分发 (`skillctl install`) - [ ] 语义搜索(embedding 向量召回) - [ ] 分布式 workflow 引擎(跨进程 saga 编排;持久队列重投/审批/回滚纪律已内置 v0.9) --- ## 与其他方案的对比 | | 本 SDK | Temporal | LangChain Tools | 手撸 gin | |---|---|---|---|---| | 三入口复用 | ✅ | ❌ | ❌ | ❌ | | ReviewGate 安全护栏 | ✅ | ❌ | ❌ | ❌ | | 断点续跑 | ✅ | ✅ | ❌ | 需自建 | | AI schema 一键导出 | ✅ | ❌ | ✅ | ❌ | | 学习曲线 | 分钟级 | 周级 | 小时级 | 分钟级 | | 多步骤编排 | ✅ | ✅✅ | ⭐⭐ | 需自建 | | 分布式可靠 | ✅ 持久队列/幂等/多副本(v0.9) | ✅✅ | ❌ | 视实现 | | MCP 协议 | ✅ | ❌ | ❌ | ❌ | **定位**:比 Temporal 轻(不需要集群),比 LangChain 严肃(有审计和安全),比手撸 gin 多了编排/回放/护栏。 --- ## 贡献 欢迎提 Issue / PR。 新策略加一份单测(参考 `sdk/testkit/` 模板)、写在 CHANGELOG、跑 `go vet ./... && go test -race ./...`。 贡献前可先看 [docs/code-review.md](docs/code-review.md) 了解当前已知的代码债与重构优先级。