draftgo-cli 2.0.9 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1408 +1,104 @@
1
- ---
1
+ ---
2
2
  name: draftgo
3
3
  description: Use this skill when the user is developing, maintaining, or debugging a DraftGo-based application. Activates for tasks involving DraftGo pages, navigation, API calls, roles, DB meta, or any DraftGo-specific development work. ALSO activates when user says "推送", "push", "同步", "sync", "推送到云端", "同步到云端", "push to server", "上传页面", "上传导航", "push pages", "push nav", "push db_meta" — route to /draftgo push skill immediately, do NOT attempt manual API calls.
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  ---
6
6
 
7
7
  # DraftGo 开发助手
8
8
 
9
- > **【DraftGo 开发追求】**
10
- > 使用 DraftGo-CLI 开发的项目,默认追求:**开发的急速感、逻辑与实现的完整、迭代性强**。AI 应快速读懂现有资源,优先交付真实落地闭环、高可用的实现,并让后续修改者能继续迭代。若本地 Agent 环境存在前端 UI 相关 Skills,前端界面开发时优先调用。
11
- >
12
- > **【开发分级 — 最高优先级】**
13
- > 任何开发对话开始时,**必须先读 `{{SKILL_DIR}}/rules/dev-workflow.md` 判断任务级别**,再执行对应流程:
14
- > - **小修** → 直接定位 → 改 → 轻量证据。优先只读目标资源和最小必要规则;无需 Story 门、需求门、Task 文档;changelog / check / push 按影响选择。
15
- > - **轻功能** → 范围复述 → 直接做 → 凭证据闭环。用于简单页面能力、单入口交互、小型数据联动,不强制 Task。
16
- > - **标准功能** → 轻量确认 → 内部短计划 → 执行闭环。若 `.draftgo/story.yaml` 存在则静默加载;不存在时不阻塞开发,但应在完成后提醒补 Story;只有跨资源、多页面协作、并行或高风险时才落 Task。
17
- > - **高风险** → 完整 Story / 计划 / 验证 / 人工确认。无 `.draftgo/story.yaml` 时必须先构建 Story。
18
- >
19
- > **开发中发现请求与已有 Story 冲突时,必须显式提示开发者,不可静默执行。** 详见 `{{SKILL_DIR}}/story/SKILL.md` 冲突检测章节。
20
- >
21
- > **【开发任务硬性流程】**
22
- > 任何"开发 / 修改 / 新建 / 修复 / 重构 / 完善 / 优化"指令,**必须先读 `{{SKILL_DIR}}/rules/dev-workflow.md` 做任务分级和用户意图翻译**。标准功能 / 高风险任务追问用户时必须带上 AI 自己的意图推测(推测 + 2-3 个选项 + 推荐项),不能空着问;能合理推断的轻功能不因模板追问拖慢。前端开发按任务规模读取 `{{SKILL_DIR}}/rules/frontend.md` 的相关规则。
23
- >
24
- > **【真实可用默认原则】**
25
- > 除非用户明确要求“静态 / 纯页面 / demo / mock / 假数据 / 伪功能 / 先看效果”,任何开发任务都优先考虑真实落地闭环、高可用。禁止用前端假数据、静态卡片、无效按钮或伪交互充当功能完成。若平台能力、外部依赖或通用动态 DB 都无法支撑该功能,不要继续编写伪功能;向用户说明阻塞原因,并在确有复用价值时写入 `.draftgo/lessons/`。
26
- >
27
- > **【更新日志记录】**
28
- > changelog 用于让后续 Agent 接手;影响可见功能、跨资源、已 push、已发布或用户明确要求记录时写入 `.draftgo/changelog.md`,格式:`- [HH:MM] [操作类型] 描述`。纯小修、探索、未形成有效改动时可跳过。
29
- >
30
- > **【任务标记】**
31
- > 只有已创建 Task 文档的任务需要维护标记。跨资源、多页面协作、并行开发或高风险任务完成一个阶段后,更新 `.draftgo/Task/<file>.md` 中的 ⬜ → ✅ 标记和证据摘要;小修、轻功能和普通标准功能可用内部短计划,不创建 Task。
32
- >
33
- > **【双端覆盖提醒】**
34
- > 开发业务功能时,优先从完整用户路径链路思考:用户从官网 / 导航 / 首页入口进入,点击到目标页面,完成浏览 / 搜索 / 提交 / 管理等操作,再获得真实反馈。围绕真实落地闭环、高可用判断是否需要对应管理侧页面(后台 CRUD / 配置 / 审核)和同一份真实数据,避免只做静态展示页。
35
- >
36
- > **【页面绑定提醒】**
37
- > 新增页面后必须处理入口绑定:导航栏、首页模块、后台菜单、相关页面按钮至少一处可点击进入;若用户明确要求隐藏页 / 草稿页,才可不绑定,但必须说明原因。只创建页面文件、不能从正常路径进入,不算完成。
38
- >
39
- > **【CLI 辅助工具】**
40
- > 资源关系不清、进入陌生项目、多页面任务或用户描述模糊时,优先运行 `draftgo map` 快速读取页面 / 导航 / DB / 脚本 / AIHub / 外部 API / 文档 / 系统配置资源地图;目标文件清楚的小修可跳过。涉及页面、导航、DB 或脚本改动时,可用 `draftgo check` 辅助发现未绑定入口、缺文件、重复路由和疑似 mock 风险。验证优先用文件回读、静态检查、与改动匹配的轻量证据和必要的 push 输出闭环。
41
-
42
- ## 命令路由
43
-
44
- **不要在此 Skill 中直接执行任何操作。** 根据用户意图路由到对应子命令:
45
-
46
- | 用户意图 | 执行 |
47
- |---|---|
48
- | 初始化项目 / 连接服务器 / `/draftgo init` | 调用 `/draftgo init` Skill |
49
- | 从云端拉取 / 刷新本地数据 / `/draftgo pull` | 调用 `/draftgo pull` Skill |
50
- | 推送本地修改到云端 / `/draftgo push` | 调用 `/draftgo push` Skill |
51
- | 构建 / 查看 / 更新系统 Story / `/draftgo story` | 调用 `{{SKILL_DIR}}/story/SKILL.md` |
52
- | 查看项目资源地图 / `draftgo map` | 直接运行 CLI,快速读取本地页面、导航、DB、脚本、AIHub、外部 API、文档、系统配置、入口引用 |
53
- | 闭环体检 / `draftgo check` | 直接运行 CLI,检查本地 route、入口绑定、文件存在性和疑似 mock 风险 |
54
- | 开发 / 修改 / 新建 / 修复 / 重构页面、导航、API、自定义脚本、AIHub 资产 | **先读 `{{SKILL_DIR}}/rules/dev-workflow.md` 做小修 / 轻功能 / 标准功能 / 高风险分级**。小修优先只读目标资源和最小必要规则;前端任务按需读 `{{SKILL_DIR}}/rules/frontend.md`;页面静默失效或脚本复杂时读 `{{SKILL_DIR}}/rules/debugging-syntax.md`;准备并行、多资源冲突或高风险计划时按需读 `{{SKILL_DIR}}/rules/parallel.md`。收尾按影响选择 changelog / check / push;有 Task 文档时同步打 ✅。 |
9
+ > 默认追求:**开发的急速感、逻辑与实现的完整、迭代性强**。优先交付真实落地闭环;禁止假数据/静态卡片/伪功能充当完成。若本地 Agent 存在前端 UI Skills,前端开发时优先调用。
55
10
 
56
11
  ## 开发前置检查
57
12
 
58
- 进行任何开发任务前,先确认 `.draftgo/config.json` 存在:
59
-
60
- ```
61
- !test -f .draftgo/config.json && echo "OK" || echo "请先运行 /draftgo init"
62
- ```
63
-
64
- 存在后,从 `.draftgo/config.json` 读取服务器地址和 token。
65
-
66
- 开发规范读取顺序(按任务规模渐进读取):
67
- 1. `{{SKILL_DIR}}/rules/dev-workflow.md` — 开发分级与执行流程(小修 / 轻功能 / 标准功能 / 高风险)
68
- 2. `{{SKILL_DIR}}/rules/frontend.md` — 前端技术规范(前端任务按需读取;小修可先只读相关段落)
69
- 3. `{{SKILL_DIR}}/rules/debugging-syntax.md` — 排错指南(页面静默失效、脚本复杂或 console 报错时读取)
70
- 4. `{{SKILL_DIR}}/rules/parallel.md` — 并行开发协议(标准功能 / 高风险且任务数足够、资源互不冲突时读取)
71
-
72
- ## 架构认知(必读)
73
-
74
- **DraftGo 不是传统 SPA,是"数据库驱动的页面资产运行时":**
75
- - 壳层(React + Vite)负责编译后的平台前端、runtime 编排和管理界面,源码在 `frontend/src/`
76
- - 业务页面 HTML 存在数据库 `page.value.html`,运行在 `iframe.srcdoc`
77
- - 导航栏 HTML 存在数据库 `navigation.html`,由壳层按需加载
78
- - 页面与壳层通过 `window.parent.App` API 通信
79
-
80
- **技术栈:**
81
- - 后端:FastAPI 0.115.0 + SQLAlchemy 2.0.36 + Pydantic 2.10.0 + MySQL + Redis
82
- - 前端:React + Vite + shadcn/ui + Tailwind CSS(shadcn/Tailwind 已作为平台前端基线引入)
83
- - 数据库业务页面:仍可使用壳层注入的本地 `/assets/tailwindcss.js`、FontAwesome、GSAP 等静态资源,不需要进入 React 编译链
84
-
85
- **dg-* = shadcn(硬性认知):**
86
- - 在 DraftGo 中,看到 `dg-*` 必须理解为 **shadcn/ui 的 DraftGo HTML 协议表达**。
87
- - `dg-*` 不是自研 UI 组件库,不是 daisyUI / Bootstrap / Ant Design / Element Plus 的别名,也不是任意相似样式的统称。
88
- - 平台壳层管理界面使用 React/TSX 版 shadcn 组件;数据库页面不能直接写 TSX,因此用 `dg-button`、`dg-card`、`dg-form`、`dg-table` 等 HTML 标签表达同一套 shadcn 组件能力。
89
- - 如果 shadcn 有对应组件,DraftGo 必须提供对应 `dg-*` 标签;当前 runtime 已按映射表提供基础协议覆盖,后续 shadcn 新组件也必须追加对应 `dg-*`。
90
- - AI 开发页面时,`dg-*` 只代表可用的 DraftGo HTML 组件协议,不代表视觉风格指令;需要按钮、卡片、表单、表格、弹窗、Tabs、Dropdown、Sheet、Tooltip、Toast、Skeleton 等协议能力时使用对应 `dg-*`。
91
-
92
- ## 连接信息
93
-
94
- - 服务器地址与 Token:存储于 `.draftgo/config.json`(已加入 .gitignore)
95
- - 运行 `/draftgo init` 时写入
96
-
97
- ## 开发规范
98
-
99
- → 前端页面开发:读取 `{{SKILL_DIR}}/rules/frontend.md`
100
- → 页面排错指南:读取 `{{SKILL_DIR}}/rules/debugging-syntax.md`(页面功能不执行时必读!)
101
- - 任何开发,必读!
102
-
103
- ## 开发禁区(违反必报错)
104
-
105
- - ❌ 禁止境外 CDN(googleapis/jsdelivr/cdnjs/unpkg)→ ✅ 允许国内镜像(npmmirror.com/staticfile.net)
106
- - ❌ 禁止 `App()` 写法(App 是对象不是函数)→ ✅ 正确:`const App = window.parent?.App`
107
- - ❌ 禁止 `const App = () => window.parent?.App` → ✅ 去掉箭头函数
108
- - ❌ 禁止 `window.location.search` 读参数 → ✅ 用 `window.__DG_ROUTE_CONTEXT__.query`
109
- - ❌ 禁止 `App?.user?.role` 判断管理员 → `App.user` 不存在!✅ 用 `App.isAdmin` 或 `App.currentUser?.role_code`
110
- - ❌ 禁止 `navigate('/login')` 退出 → ✅ 用 `window.location.href = '/login'`
111
- - ❌ 禁止页面内 `window.location.href = ...` 跳转 → 页面运行在 iframe 中,直接赋值只跳 iframe 自身!✅ 所有导航跳转用 `window.parent.location.href = ...`
112
- - ❌ 禁止 `window.alert/confirm/prompt` → ✅ 用 `App.toast()` / `App.confirm()` / `App.showModal()`
113
- - `App.showSuccess(msg)` 成功提示(绿色 3s)
114
- - `App.showError(msg)` 错误提示(红色 4s)
115
- - `App.showWarning(msg)` 警告提示(黄色 3s)
116
- - `App.showInfo(msg)` 信息提示(蓝色 3s)
117
- - `App.toast(msg, type)` 通用方法 — type: success/error/warning/info
118
- - `App.confirm(msg, title?)` 确认弹窗 — 返回 Promise\<boolean\>
119
- - `App.showModal(msg, title?)` 信息模态框 — 替代 alert()
120
- - `App.showLoading()` / `App.hideLoading()` — 全局 loading
121
- - `App.callApi(code, options?)` — 调用已注册的外部 API(Promise→`{status_code, headers, body, duration_ms, error}`)
122
- - `App.listApis()` — 列出当前用户可调用的外部 API(含 code / name / method / 各 JSON Schema)
123
- - 详见 `{{SKILL_DIR}}/rules/frontend.md` App API 章节
124
- - ❌ 禁止在业务页面内重复实现全局客服、全局反馈、全局浮窗、统计脚本等站点级能力 → ✅ 使用页面管理「全局」分类维护 `frontend_global_*` 固定槽位
125
- - 全局层存储在 `sys_config.category=frontend_global`,不是 page 资产,不允许新增槽位
126
- - Toast 可通过全局项二开样式/位置/默认时长,但页面调用仍使用 `App.toast()` / `App.showSuccess()` 等稳定 API
127
- - ⚠️ 尽量避免硬编码颜色 → 优先用 `var(--dg-accent)` 等 token 以适配主题切换
128
-
129
- ## 主题切换适配
130
-
131
- DraftGo 支持多套配色方案 + 亮暗模式,通过 CSS 变量实现。
132
-
133
- **概念分层:**
134
- | 概念 | 存储键 | 取值 |
135
- |---|---|---|
136
- | 显示模式 | `dg_theme` | `'light'` \| `'dark'` \| `'system'` |
137
- | 配色方案 | `dg_color_scheme` | `'dark-gray-white'` \| `'deep-blue-white'` \| `'warm-retro'` \| `'mint-blue'` \| `'pine-green'` \| `'custom'` |
138
-
139
- **内置配色方案:**
140
- - `dark-gray-white`
141
- - `deep-blue-white`
142
- - `warm-retro`
143
- - `mint-blue`
144
- - `pine-green`
145
-
146
- **颜色 Token(优先使用):**
147
- - `--dg-bg-base` / `--dg-bg-page` / `--dg-bg-surface` — 背景层级
148
- - `--dg-text-primary` / `--dg-text-secondary` / `--dg-text-muted` — 文字层级
149
- - `--dg-accent` / `--dg-accent-hover` / `--dg-accent-subtle` — 主题色
150
- - `--dg-border` / `--dg-success` / `--dg-error` / `--dg-warning` — 功能色
151
-
152
- **App API 切换配色:**
153
- ```javascript
154
- App.setColorScheme('deep-blue-white'); // 切换为预设方案
155
- App.setColorScheme('custom', customVarsObject); // 应用自定义配色
13
+ ```bash
14
+ # 确认已初始化
15
+ test -f .draftgo/config.json && echo "OK" || echo "请先运行 /draftgo init"
156
16
  ```
157
17
 
158
- **硬编码颜色的场景(允许但需注意):**
159
- - 品牌色(如 Logo 固定色)
160
- - 数据可视化图表(需保证对比度)
161
- - 第三方组件库强制要求
162
-
163
- **硬编码时的要求:**
164
- - 同时提供亮色/暗色两套值,通过 `[data-theme="dark"]` 选择器切换
165
- - 确保对比度符合 WCAG AA 标准(文字至少 4.5:1)
166
-
167
- ## 本地开发流程
168
-
169
- **原则:有功能修改就读本地数据,自主决策是否调用 API 完成高质量修改。**
18
+ 服务器地址和 Token 从 `.draftgo/config.json` 读取。
170
19
 
171
- 1. **开发时**:读写 `.draftgo/` 本地文件
172
- - 查元数据:读 `index.json`
173
- - 查/改 HTML:按 `html_file` 字段路径读对应 `.html` 文件
174
- - **新建资源**:在对应 `index.json` 追加一条**不带 `id`** 的记录 + 写好内容文件(html/md/代码),push 时自动 `POST` 创建并回写真实 id(见下方"新建资源")
175
- 2. **修改后**:运行推送脚本推送到服务器(见下方"推送规范")
176
- 3. **同步成功**:本地与服务器数据一致
177
-
178
- **本地数据目录(均在 `.draftgo/` 下):**
20
+ ## 命令路由
179
21
 
180
- | 目录 | 内容 |
22
+ | 用户意图 | 路由 |
181
23
  |---|---|
182
- | pages/ | index.json(无 html)+ 若干 .html |
183
- | navigations/ | index.json(无 html)+ 若干 .html |
184
- | roles/ | index.json |
185
- | users/ | index.json |
186
- | db_meta/ | index.json |
187
- | aihub/ | index.json |
188
- | external_apis/ | index.json(已注册的外部 API 元信息,`auth_config` 已脱敏) |
189
- | docs/articles/ | index.json(无 content)+ 若干 .md(文章正文) |
190
- | doc_categories/ | index.json(文档分类,含 parent_id 树关系) |
191
- | custom_scripts/ | index.json(无 code)+ 若干 .py / .js / .ts / .sh / .go(脚本代码,按 language 落到对应扩展名) |
192
- | system_config/ | index.json |
193
- | changelog.md | 更新日志(单文件,按日期分节) |
194
- | lessons/ | 开发经验与问题记录(按 `YYYY-MM-DD-主题.md` 命名) |
195
- | Task/ | YYYY-MM-DD-\<topic\>.md(每个开发任务一个文件,含需求纪要 / 设计 / 任务清单 / ⬜🟡✅❌⏸ 标记 / 完成回顾) |
24
+ | 初始化项目 / `/draftgo init` | 调用 `/draftgo init` Skill |
25
+ | 从云端拉取 / `/draftgo pull` | 调用 `/draftgo pull` Skill |
26
+ | 推送到云端 / `/draftgo push` | 调用 `/draftgo push` Skill |
27
+ | 构建 / 查看 Story / `/draftgo story` | 调用 `{{SKILL_DIR}}/story/SKILL.md` |
28
+ | 查看资源地图 / `draftgo map` | 直接运行 CLI |
29
+ | 闭环体检 / `draftgo check` | 直接运行 CLI |
30
+ | 任何开发 / 修改 / 新建 / 修复 / 重构任务 | ↓ 见开发任务路由 |
196
31
 
197
- ## 数据库 Schema(关键字段)
32
+ ## 开发任务路由(按任务规模读取)
198
33
 
199
- | | 关键字段 | 说明 |
200
- |---|---|---|
201
- | `page` | `id`, `title`, `route`, `permission`, `value` (JSON: `{html: "..."}`) | value.html 存页面完整 HTML |
202
- | `navigation` | `id`, `code`, `name`, `html`, `order`, `status` | html 字段存导航栏完整 HTML |
203
- | `user` | `id`, `username`, `email`, `role_id`, `status` | role_id 关联 role 表 |
204
- | `role` | `id`, `code`, `name`, `permissions` (JSON) | admin 角色拥有所有权限 |
205
- | `db_meta` | `id`, `type`, `label`, `describe`, `schema` (JSON Schema: `{type:"object", properties:{字段名:{type,title,required,searchable,...}}}`;`searchable` 取 `false`/`"exact"`/`"fuzzy"`/`"range"`/`"contains"`,旧布尔 `true` 按类型推断), `permission` (JSON: `{public,login,admin,owner_field,roles}`,每项 `{read,create,update,delete}` 取值 `none/owner/all`,public 仅 `none/all`), `schema_validation` (0/1), `extra` (JSON) | 动态表结构定义 |
206
- | `db` | `id`, `type`, `data` (JSON), `userid`, `status`, `created_at`, `updated_at` | 动态数据存储(含自动时间戳) |
207
- | `aihub` | `id`, `name`, `type`, `config` (JSON), `status` | AI 服务配置 |
208
- | `sys_config` | `config_key`, `config_value`, `value_type`, `category`, `is_sensitive` | 系统配置 KV |
209
- | `custom_script` | `id`, `name`, `slug`, `code`, `mode`, `status`, `config` (JSON), `triggers` (JSON), `permission` (JSON: `{default, roles}`) | 自定义服务定义,支持 event/route/scheduled 三种模式 |
34
+ **第一步:必读** `{{SKILL_DIR}}/rules/dev-workflow.md`(分级:小修 / 轻功能 / 标准功能 / 高风险)
210
35
 
211
- ## API 速查与示例
212
-
213
- | 模块 | 端点 |
36
+ | 任务类型 | 读取 |
214
37
  |---|---|
215
- | 认证 | POST /api/auth/login, /register, /logout, /refresh, /forgot-password, /reset-password |
216
- | 微信认证 | POST /api/auth/wechat/mp/oauth, /wechat/mini/login, /wechat/mp/qr/create · GET /wechat/mp/qr/poll |
217
- | 用户 | GET/PUT /api/users/me · PUT /users/me/password · POST /users/me/contact-verifications, /confirm · POST /users/me/password/send-code/{channel}, /reset-by-phone, /reset-by-email · GET/POST /api/users · GET/PUT/DELETE /users/{id} · POST /users/{id}/ban, /unban |
218
- | 角色 | GET/POST /api/roles · GET/PUT/DELETE /roles/{id} · POST /roles/{id}/assign/{uid} · DELETE /roles/{id}/revoke/{uid} |
219
- | 页面 | GET/POST /api/pages/ · GET/PUT/DELETE /pages/{id} · POST /pages/{id}/reset-system · GET /pages/trash · DELETE /pages/{id}/trash · GET /pages/by-route · GET /pages/public/{route} · POST /pages/check-permissions · GET /pages/{id}/versions · GET/DELETE /pages/{id}/versions/{vid} · POST /versions/{vid}/restore, /star · PATCH /versions/{vid}/note · GET /versions/{v1}/diff/{v2} · POST /versions/batch-delete |
220
- | 导航栏 | GET/POST /api/navigations · GET /navigations/{code} · PUT/DELETE /navigations/{id} · POST /navigations/{id}/reset-system |
221
- | 系统配置 | GET /api/system/config · GET /system/category/{category} · GET/PUT/DELETE /system/{key} · POST /system/ · POST /system/config/ensure · GET/PATCH /system/config/sensitive-fields |
222
- | 备份恢复(基础) | GET/POST /api/system/backup · POST /system/restore · POST /system/reset · POST /system/backup/selective · POST /system/restore/selective?mode=replace\|merge\|append · POST /system/restore/selective/file?mode=replace\|merge\|append(上传 JSON 文件恢复)· GET /system/backup/logs |
223
- | 备份恢复(增强) | GET /system/backup/package, POST /system/backup/selective/package(.dgbak)· POST /system/restore/package, /restore/package/inspect(上传 .dgbak)· POST /system/restore/inspect, /restore/dry-run(JSON body 只读预览/预演)· POST /system/restore/inspect/file, /restore/dry-run/file(上传 JSON 文件只读预览/预演)· GET /system/storage/health, POST /system/storage/cleanup-orphans(存储健康)· POST /system/backup/logs/{id}/restore, /undo(回溯/撤销) |
224
- | ⚠️ 二次密码 | restore / reset / undo / cleanup-orphans / package restore 都需要先 POST `/auth/reauth { password, scope }` 拿一次性 `confirm_token`,在请求头加 `X-Confirm-Token: <token>` 才能调用。SAT 调用方自动豁免。 |
225
- | 通知公告 | GET/POST /api/notices · GET/PUT/DELETE /notices/{id} |
226
- | 反馈 | GET/POST /api/feedback · GET /feedback/updates · GET/PUT/DELETE /feedback/{id} · DELETE /feedback/batch |
227
- | 日志 | GET /api/logs · GET /logs/{id} |
228
- | 智能体 | GET /api/agents · GET/DELETE /agents/logs · GET /agents/{id}/selectable-models · POST /agents/{id}/chat · POST /agents/{id}/images · POST /agents/{id}/preview-chat |
229
- | 动态DB | GET /api/db/{type}(支持 `filters`/`order_by`/`order` 结构化检索)· POST /db/{type}(对象或数组)· PATCH /db/{type}/batch · GET/PUT/DELETE /db/{type}/{id} |
230
- | DB Meta | GET/POST /api/db-meta(POST 支持对象或数组)· PATCH /db-meta/batch · GET /db-meta/{type} · POST /db-meta/{type}/reconcile-fields · POST /db-meta/reconcile-orphans · PUT/DELETE /db-meta/{id} |
231
- | ⚠️ DB Meta 查询 | GET /db-meta/{type} 用 **type**(如 `patient_profile`),不是 id。用 id 查会返回 "元数据不存在"。PUT/DELETE 才用 id。 |
232
- | AIHub | GET/POST /api/aihub(POST 支持对象或数组)· PATCH /aihub/batch · GET/PUT/DELETE /aihub/{id} · PATCH /aihub/{id}/basic · POST /aihub/{id}/sync · POST /aihub/{id}/test-image-generation, /test-response-format · POST /aihub/discover · POST /aihub/mcp/discover-tools · GET /aihub/types |
233
- | AI推理 | GET /api/v1/models · POST /v1/chat/completions · POST /api/images/generation |
234
- | 外部 API(页面调用端) | GET /api/external-apis/available · POST /api/external-apis/call/{code}<br>页面里**优先使用 `App.callApi(code, options)`**,不要直连这两个端点 |
235
- | 外部 API(管理端,需 admin) | GET/POST /api/external-apis · GET/PUT/DELETE /external-apis/{id} · PATCH /external-apis/{id}/status · POST /external-apis/{id}/test · GET /external-apis/tags · GET /external-apis/logs · POST /external-apis/logs/cleanup?retention_days=N |
236
- | 文件上传 | POST /api/upload |
237
- | 通知测试 | POST /api/system/notifications/test-email, /test-sms · GET /system/notifications/logs |
238
- | 自定义服务 | POST /api/scripts · GET /api/scripts · GET/PUT/DELETE /scripts/{id} · POST /scripts/{id}/enable, /disable, /execute · GET /scripts/{id}/versions · POST /scripts/{id}/versions/{vid}/restore · GET /scripts/{id}/executions · ANY /api/x/{slug}/{path} |
239
- | 文档中心 | GET/POST /api/docs/categories · GET /docs/articles, /docs/articles/{key}, /docs/search · GET /docs/admin/articles, /docs/admin/stats · POST /docs/articles · PUT/DELETE /docs/articles/{id} · PUT/DELETE /docs/categories/{id} |
240
-
241
- ### 统一响应信封
38
+ | 所有开发任务 | `rules/dev-workflow.md` 分级与执行流程 |
39
+ | 前端页面开发 | `rules/frontend.md` 前端规范(小修可只读相关段落) |
40
+ | 页面功能不执行 / 报错 | `rules/debugging-syntax.md` 排错指南(必读) |
41
+ | 标准功能 / 高风险并行任务 | `rules/parallel.md` 并行协议 |
42
+ | 不了解项目结构 | 先运行 `draftgo map` |
43
+ | 标准功能 / 高风险 · 意图模糊 | `practices/dev-declaration.md` 开发声明协议 |
242
44
 
243
- 所有 API 端点统一返回信封格式:
45
+ ## 快速决策树
244
46
 
245
- ```json
246
- {"code": 200, "data": <实际载荷>, "message": "success"}
247
47
  ```
248
-
249
- **例外(不使用信封):**
250
- - `POST /api/v1/chat/completions` OpenAI 兼容格式(SSE 流或 OpenAI JSON)
251
- - `GET /api/v1/models` — OpenAI 兼容格式 `{"object":"list","data":[...]}`
252
- - `POST /api/external-apis/call/{code}` — 代理透传上游原始响应
253
- - `POST /api/external-apis/{id}/test` — 返回 `{ok, status_code, duration_ms, headers, body, error}`
254
- - `ANY /api/x/{slug}/{path}` — 自定义脚本运行时,响应格式由脚本决定
255
-
256
- **错误响应格式:**
257
- ```json
258
- {"code": 400, "data": null, "message": "错误描述"}
48
+ 要开发什么?
49
+ ├── 页面 / 导航 / 交互 → 读 rules/frontend.md
50
+ ├── 数据结构 / 动态 DB → specs/data.md
51
+ ├── API 速查 → quickref/api-endpoints.md
52
+ ├── App 方法忘了 → quickref/app-api.md
53
+ ├── dg-* 组件用法 → quickref/dg-components.md
54
+ ├── 了解平台架构 → core/architecture.md
55
+ ├── 选择开发方案 → core/modules.md
56
+ └── 查开发禁区 → specs/security.md
259
57
  ```
260
58
 
261
- **提取数据的通用逻辑(CLI pull 脚本已内置):**
262
- 1. 响应一定有 `.data` 字段 → 取 `data` 即为业务载荷
263
- 2. 如果 `data` 为 dict 且含 `items` → 分页列表,取 `data.items`
264
- 3. 如果 `data` 为 list → 直接使用
265
-
266
- **前端页面内的权威规则以 `{{SKILL_DIR}}/rules/frontend.md` 为准:`App.get/post/put/patch/delete` 原样返回后端信封,不做剥壳;页面代码必须检查 `res.code`,再从 `res.data` 取业务载荷。** SAT / CLI 脚本直接调 API 时也统一取响应信封里的 `.data`。
267
-
268
- ### 高频 API 示例
269
-
270
- **创建页面(需 admin):**
271
- ```
272
- POST /api/pages/
273
- Body: { "title": "新页面", "route": "/new", "permission": {"default": "public"}, "value": {"html": "<html>...</html>"} }
274
- 说明:permission 是对象,不是字符串。`default` 取值 `public`(任何人) / `login`(登录用户) / `admin`(管理员);可选 `roles: {角色code: "public|login|admin"}` 做角色级覆盖。
275
- 保留路由(不可被业务页面占用):`/setup`(首次部署引导)。创建/更新时若 route 重复,后端返回 400 "route 已存在"。
276
- 系统页:由后端从 `backend/init/pages/` 初始化,`tag == "系统"` 标识;不会在容器重启时自动对比或更新。官方版本提示页面模板有更新时,管理员需要在页面管理点击「重置系统页面」,或调用 `POST /api/pages/{id}/reset-system` 从当前安装包内置模板覆盖重置。
277
- Response: { "code": 200, "data": { "id": 123, "title": "新页面", "route": "/new", ... }, "message": "success" }
278
- ```
279
-
280
- **更新页面 HTML(需 admin):**
281
- ```
282
- PUT /api/pages/123
283
- Body: { "value": {"html": "<html>更新后...</html>"} }
284
- ```
285
-
286
- **重置系统页面(需 admin):**
287
- ```
288
- POST /api/pages/123/reset-system
289
- Body: 空
290
- 说明:从 backend/init/pages/ 重新加载该页面
291
- ```
292
-
293
- **创建动态数据(普通用户自动绑定 userid):**
294
- ```
295
- POST /api/db/patient
296
- Body: { "data": {"name": "张三", "age": 30} }
297
- Response: { "code": 200, "data": [{ "id": 456 }], "message": "success" }
298
- ```
299
-
300
- **查询动态数据(分页 + 结构化检索):**
301
- ```
302
- GET /api/db/patient?page=1&page_size=20
303
- GET /api/db/patient?filters=name:like:张&filters=age:gte:18&order_by=age&order=desc
304
- Response: { "code": 200, "data": { "items": [...], "total": 100, "page": 1, "page_size": 20 }, "message": "success" }
305
- 说明:filters 每项为 "字段:操作符:值",可多次传入(AND)。操作符 eq/like/gte/lte/gt/lt/in/contains,省略默认 like。
306
- 字段须在 db_meta schema 标 searchable,且操作符匹配其检索模式(exact/fuzzy/range/contains),否则 400。
307
- ```
308
-
309
- **批量更新动态数据:**
310
- ```
311
- PATCH /api/db/patient/batch
312
- Body: [
313
- { "id": 456, "data": {"name": "张三", "age": 31} },
314
- { "id": 457, "data": {"name": "李四", "age": 28}, "status": 1 }
315
- ]
316
- Response: { "code": 200, "data": [{ "id": 456 }, { "id": 457 }], "message": "success" }
317
- 说明:批量更新是一次事务;任意一条权限/schema/存在性校验失败,整批不提交。`data` 是完整业务 JSON 替换,不是字段级 merge。
318
- ```
319
-
320
- **调用外部 API(页面里):**
321
- ```javascript
322
- // 在页面 HTML 里:壳层会自动注入认证、做参数校验、写日志
323
- const r = await App.callApi('weather-now', {
324
- path_params: { city: 'beijing' }, // 替换 path 模板里的 {city}
325
- query_params: { unit: 'metric' }, // ?unit=metric
326
- // body: { ... }, // POST/PUT/PATCH 才生效
327
- // headers: { 'X-Trace': 't1' }, // 不会覆盖后端注入的认证头
328
- });
329
- if (r.status_code >= 200 && r.status_code < 300) {
330
- console.log(r.body); // JSON 自动解析
331
- } else {
332
- App.showError(`上游返回 ${r.status_code}`);
333
- }
334
- ```
335
- - 不要在页面里写 API Key / Bearer Token,由管理员在「外部 API」管理页注册即可
336
- - API code 不知道时先 `await App.listApis()` 查询
337
- - `r.error` 仅在代理层错误时(超时 / 网络 / 校验失败)非空;上游业务错误请看 `r.status_code` + `r.body`
338
-
339
- ## 用自然语言注册/管理外部 API(SAT 直连管理端)
340
-
341
- > 当用户说"帮我接入和风天气"、"注册一个 OpenAI 兼容接口"、"把这个 API 加进来"等,**走管理端 API 注册**,不要让用户去后台手动配置。
342
-
343
- ### 1. 先查本地缓存
344
-
345
- 读 `.draftgo/external_apis/index.json` 确认是否已注册过相同 `code`。
346
-
347
- ### 2. 调用管理端 API(用 SAT,需 admin)
348
-
349
- 读 `.draftgo/config.json` 拿到 `server` + `token`,按下面字段构造 payload,调用 `POST /api/external-apis`:
350
-
351
- ```json
352
- {
353
- "code": "weather-now",
354
- "name": "和风天气-实时",
355
- "base_url": "https://devapi.qweather.com",
356
- "method": "GET",
357
- "path": "/v7/weather/now",
358
- "headers": {},
359
- "auth_type": "api_key_query",
360
- "auth_config": { "name": "key", "value": "用户提供的 KEY" },
361
- "timeout_ms": 30000,
362
- "permission": { "default": "login", "roles": [] },
363
- "param_schema": {
364
- "query": {
365
- "type": "object",
366
- "properties": { "location": { "type": "string" } },
367
- "required": ["location"]
368
- }
369
- },
370
- "tags": ["weather"],
371
- "description": "实时天气查询"
372
- }
373
- ```
374
-
375
- **字段约定:**
376
- - `auth_type`:`none` / `api_key_header` / `api_key_query` / `bearer` / `basic`
377
- - `api_key_header` / `api_key_query`:`auth_config = {name, value}`
378
- - `bearer`:`auth_config = {token}`
379
- - `basic`:`auth_config = {username, password}`
380
- - `permission.default`:`public`(匿名)/ `login`(登录用户)/ `deny`(仅指定角色)
381
- - `param_schema.{path,query,body}`:均为 JSON Schema Draft 2020-12,可省略
382
- - `path` 支持 `{var}` 模板,对应 `param_schema.path` 中字段
383
- - 认证字段由后端 AES-256-GCM 加密落库;后续 GET 详情会返回脱敏值
384
-
385
- ### 3. 注册后立即测试 + 写更新日志
386
-
387
- ```
388
- POST /api/external-apis/{id}/test
389
- Body: { "path_params": {...}, "query_params": {...}, "body": ... }
390
- 返回: { ok, status_code, duration_ms, error }
391
- ```
392
-
393
- 测试通过后写更新日志:`[新增] 注册外部 API <code>`
394
-
395
- ### 4. 常见管理操作
396
-
397
- | 用户意图 | API 调用 |
398
- |---|---|
399
- | 列出所有 API | `GET /api/external-apis?page=1&page_size=50` |
400
- | 改名/换地址/换 KEY | `PUT /api/external-apis/{id}`(仅传需要改的字段;不传 `auth_config` 则保留原 KEY) |
401
- | 启用/禁用 | `PATCH /api/external-apis/{id}/status` body `{status:"active|disabled"}` |
402
- | 删除 | `DELETE /api/external-apis/{id}` |
403
- | 查看调用日志 | `GET /api/external-apis/logs?api_code=<code>&page=1&page_size=50` |
404
- | 清理日志 | `POST /api/external-apis/logs/cleanup?retention_days=30` |
405
- | 列出已用标签 | `GET /api/external-apis/tags` |
406
-
407
- ### 5. 用户场景对话样例
408
-
409
- - 用户:"给我接一个查天气的 API,KEY 是 xxx"
410
- → AI 询问:服务商?请求方法?需要哪些参数?
411
- → AI 调用 `POST /api/external-apis` 注册 → 调用 `/test` 验证 → 告知 code
412
- → 用户在页面里直接 `App.callApi('weather-now', { query_params: { location: 'beijing' } })`
413
-
414
- - 用户:"禁用 weather-now 这个接口"
415
- → AI 先 `GET /api/external-apis?keyword=weather-now` 拿 id → `PATCH /{id}/status` → 写更新日志
416
-
417
- ## URL 参数读取
418
-
419
- 页面运行在 `iframe.srcdoc` 中,`window.location.search` 不可靠。框架通过 `<script data-dg-route-bridge>` 注入路由上下文。
59
+ ## 连接信息
420
60
 
421
- ```javascript
422
- // 标准三阶回落(推荐)
423
- const routeContext =
424
- window.__DG_ROUTE_CONTEXT__
425
- || window.__DG_GET_ROUTE_CONTEXT__?.()
426
- || window.parent?.App?.getCurrentRouteContext?.()
427
- || { query: {} };
428
- const query = routeContext.query || {};
61
+ - 存储于 `.draftgo/config.json`(已加入 .gitignore)
62
+ - 运行 `/draftgo init` 时写入
429
63
 
430
- // 使用示例
431
- const patientId = query.patientId; // "42"
432
- const visitId = query.visitId; // "7"
433
- ```
64
+ ## 核心原则(硬性)
434
65
 
435
- 禁止:`new URLSearchParams(window.location.search)` — iframe 中取不到壳层 URL。
66
+ - **开发分级**:任何任务先读 `rules/dev-workflow.md` 分级,再执行
67
+ - **真实闭环**:不用假数据/伪交互充当完成;无法实现则停止并说明
68
+ - **入口绑定**:新增页面必须绑定导航/首页/菜单至少一处
69
+ - **dg-* = shadcn**:`dg-*` 是 shadcn 的 HTML 协议表达,不是其他 UI 库
70
+ - **Story 冲突**:发现与 `.draftgo/story.yaml` 冲突必须显式提示,不可静默执行
71
+ - **changelog**:影响可见功能/跨资源/已 push 时写入 `.draftgo/changelog.md`
436
72
 
437
- ## 常见问题排查
73
+ ## 本地数据目录
438
74
 
439
- | 问题 | 排查步骤 |
75
+ | 目录 | 内容 |
440
76
  |---|---|
441
- | 同步失败 | 1. 检查 `.draftgo/config.json` token 是否有效<br>2. 检查服务器连接<br>3. 查看 `.draftgo/changelog.md` 最近操作记录 |
442
- | 页面加载空白 | 1. 检查 `permission` 字段与当前用户角色是否匹配<br>2. 检查路由是否正确(`/api/pages/by-route?route=/xxx`)<br>3. 检查页面脚本错误、运行日志或最近改动 |
443
- | API 返回 401 | Token 过期,前端会自动用 refresh token 刷新,无需手动处理 |
444
- | API 返回 403 | 权限不足,检查当前用户角色是否有对应权限 |
445
- | DB Meta GET 返回"元数据不存在" | 你用了 id,应该用 **type**(如 `/api/db-meta/patient_profile`)。PUT/DELETE 才用 id。 |
446
- | 颜色不生效 | 检查是否用了 `var(--dg-*)` 而非硬编码 hex/rgb |
447
- | URL 参数读取失败 | 检查是否用了 `window.__DG_ROUTE_CONTEXT__.query` 而非 `window.location.search` |
448
- | 退出登录后导航栏未更新 | 检查是否用了 `window.location.href = '/login'` 而非 `navigate('/login')` |
449
-
450
- ---
451
-
452
- ## 更新日志规范
453
-
454
- 更新日志用于让后续 Agent 和开发者快速接手。影响可见功能、跨资源、已 push、已发布或用户明确要求记录时,写入 `.draftgo/changelog.md`;纯小修、探索、未形成有效改动时可跳过。
455
-
456
- ### 规则
457
-
458
- - 单文件,用 `## YYYY-MM-DD` 做日期分节
459
- - 每条格式:`- [HH:MM] [操作类型] 描述`
460
- - 操作类型:`新增` / `修复` / `修改` / `完善` / `删除` / `重构`
461
- - 描述精简,一句话,不超过 30 字
462
-
463
- ### 示例
464
-
465
- ```markdown
466
- ## 2026-05-18
467
-
468
- - [14:23] [新增] 用户登录页面表单验证逻辑
469
- - [15:10] [修复] 脉象输入组件在 Safari 下无法聚焦
470
- - [16:05] [修改] 问诊表单字段顺序,主诉移至首位
471
-
472
- ## 2026-05-17
473
-
474
- - [09:30] [新增] 患者档案列表页面
475
- - [11:45] [完善] 导航栏权限控制逻辑
476
- ```
477
-
478
- ### 操作流程
479
-
480
- 1. 判断本次改动是否需要记录
481
- 2. 检查 `.draftgo/changelog.md` 是否存在
482
- 3. 查找当天日期标题(`## YYYY-MM-DD`)
483
- 4. 存在则在该日期段落末尾追加新条目;不存在则在文件顶部新增日期段落
484
- 5. 无需告知用户(静默执行)
485
-
486
- ## 经验记录规范(lessons)
487
-
488
- 每次开发过程中遇到阻碍、踩坑、发现更好的范式时,写入 `.draftgo/lessons/` 目录。
489
-
490
- ### 文件命名
491
-
492
- `YYYY-MM-DD-主题关键词.md`,例如:
493
- - `2026-05-18-sync中文路径编码.md`
494
- - `2026-05-18-iframe参数传递范式.md`
495
-
496
- ### 文件结构
497
-
498
- ```markdown
499
- # 主题
500
-
501
- ## 现象
502
-
503
- 基于 DraftGo 基座和 CLI 开发时遇到 xxxxxxx
504
-
505
- ## 原因
506
-
507
- xxx
508
-
509
- ## 解法
510
-
511
- 可能有效的解决方案建议:xxxxx
512
-
513
- ## 适用场景
514
-
515
- 什么时候会再次遇到这个问题
516
- ```
517
-
518
- ### 什么时候写
519
-
520
- 开发过程中只要遇到阻碍或获得经验就写入,没有则跳过(不强制)。典型场景:
521
-
522
- - CLI 引导错误或调用失败
523
- - API 返回非预期结果
524
- - 前端开发发现更好的范式
525
- - 框架运行时行为与预期不符
526
- - 任何花了 5 分钟以上才解决的问题
527
- - 发现了一个值得记录的开发技巧或最佳实践
528
-
529
- ### 操作流程
530
-
531
- 1. 开发完成后回顾:本次有没有遇到阻碍或获得新经验?
532
- 2. 有 → 确定主题关键词,创建 `.draftgo/lessons/YYYY-MM-DD-主题.md`,按上述结构填写
533
- 3. 没有 → 跳过,不需要写
534
-
535
- ---
536
-
537
- ## 推送规范
538
-
539
- > 推送用于把本地资源同步到云端。用户明确要求推送、任务目标包含云端生效、资源已形成可交付结果、或修改涉及多资源联动时执行;探索性修改、未完成草稿和目标明确的小修可先保留本地。
540
-
541
- ### 新建资源(无 id 自动创建)
542
-
543
- push 脚本按 index.json 条目**有无 `id`** 决定走更新还是创建:
544
-
545
- - **有 id** → `PUT` 更新(PUT 404 时自动转创建,兼容删了重建 / 跨环境)
546
- - **无 id** → `POST` 创建,成功后**自动回写新 id 到 index.json**;有独立内容文件的资源会同步重命名为 pull 约定的 `{prefix}_{id}_{slug}.{ext}`
547
-
548
- 支持创建的类型:**pages / nav / db_meta / aihub / external_apis / docs / doc_categories / custom_scripts**。
549
-
550
- **新建页面 = 写 html 文件 + 在 `pages/index.json` 加一条不带 id 的记录 + `push pages`**。脚本回写 id 后即与普通更新无异,无需手动调 `POST /api/pages/`、无需手动维护 id。各类型创建必填字段见 push skill(`{{SKILL_DIR}}/push/SKILL.md`「新建资源」节)。
551
-
552
- > ⚠️ 创建后以脚本回写的 index 为准,不要手动猜 id。route/slug/code 等唯一性字段冲突时后端返回 400。
553
-
554
- ### 推送支持的类型
555
-
556
- 推送脚本覆盖 init 拉取的全部 11 种类型:
557
-
558
- | 类型 | 命令 | 说明 |
559
- |---|---|---|
560
- | pages | `python {{SKILL_SCRIPTS}}/draftgo_push.py pages [page_id]` | 页面推送 |
561
- | nav | `python {{SKILL_SCRIPTS}}/draftgo_push.py nav [nav_id]` | 导航栏推送 |
562
- | db_meta | `python {{SKILL_SCRIPTS}}/draftgo_push.py db_meta [id]` | 数据库元数据推送 |
563
- | aihub | `python {{SKILL_SCRIPTS}}/draftgo_push.py aihub [id]` | AI 资产推送 |
564
- | external_apis | `python {{SKILL_SCRIPTS}}/draftgo_push.py external_apis [id]` | 外部 API 注册推送 |
565
- | system_config | `python {{SKILL_SCRIPTS}}/draftgo_push.py system_config [config_key]` | 系统配置推送 |
566
- | docs | `python {{SKILL_SCRIPTS}}/draftgo_push.py docs [article_id]` | 文档中心文章推送(正文从 .md 文件读取) |
567
- | doc_categories | `python {{SKILL_SCRIPTS}}/draftgo_push.py doc_categories [id]` | 文档分类推送 |
568
- | custom_scripts | `python {{SKILL_SCRIPTS}}/draftgo_push.py custom_scripts [id]` | ⚠️ 需向用户二次确认(覆盖云端脚本代码,不修改 mode/status) |
569
- | roles | `python {{SKILL_SCRIPTS}}/draftgo_push.py roles [id]` | ⚠️ 需向用户二次确认 |
570
- | users | `python {{SKILL_SCRIPTS}}/draftgo_push.py users [id]` | ⚠️ 需向用户二次确认(不下发 password / role_ids) |
571
-
572
- > `{{SKILL_SCRIPTS}}` 在 CLI 渲染时会替换为当前 AI 工具的脚本路径,例如 `.claude/skills/draftgo/scripts`、`.codex/skills/draftgo/scripts`、`.cursor/commands/draftgo/scripts` 等。
573
-
574
- ### 推送规则
575
-
576
- - 修改了哪个页面的 HTML → 推送该页面:`python {{SKILL_SCRIPTS}}/draftgo_push.py pages <page_id>`
577
- - 修改了哪个导航栏的 HTML → 推送该导航:`python {{SKILL_SCRIPTS}}/draftgo_push.py nav <nav_id>`
578
- - 修改了 DB Meta / AI 资产 / 外部 API / 系统配置 / 文档 / 文档分类 → 用对应模式推送:`python {{SKILL_SCRIPTS}}/draftgo_push.py <mode> <id>`
579
- - 修改了 custom_scripts → **必须先向用户确认**(覆盖云端运行脚本),确认后再运行 `custom_scripts` 模式
580
- - 修改了 roles/users → **必须先向用户确认**,确认后再运行 `roles` / `users` 模式
581
- - 同时修改多个 → 使用批量推送:`python {{SKILL_SCRIPTS}}/draftgo_push.py --batch <mode1> <id1>,<id2> <mode2> <id3>`
582
- - 推送失败时告知用户,不得静默忽略
583
-
584
- ### 批量推送(并行开发后使用)
585
-
586
- 当并行执行产出多个资源修改时,使用 `--batch` 一次性推送:
587
-
588
- ```bash
589
- python {{SKILL_SCRIPTS}}/draftgo_push.py --batch pages 1,2,3 nav 4 custom_scripts 7
590
- ```
591
-
592
- 等价于依次推送 pages 1、2、3 + nav 4 + custom_scripts 7,共享同一 config 加载。
593
-
594
- ### ⚠️ roles / users 推送注意事项
595
-
596
- roles 和 users 涉及权限与账号安全,虽然脚本已支持,**仍必须人工确认后执行**:
597
- 1. 告知用户即将推送的变更内容(角色名/权限变更 或 用户信息变更)
598
- 2. **等待用户明确确认**后,再运行 `draftgo_push.py roles` / `users`
599
- 3. 用户拒绝则不推送,仅保留本地修改
600
-
601
- ### 收尾操作顺序(按影响选择)
602
-
603
- 1. 判断是否需要写更新日志
604
- 2. 写经验记录(有阻碍或新经验时写入 lessons/,没有则跳过)
605
- 3. 需要云端生效时运行推送脚本 / 调用 API(roles/users 需二次确认)
606
- 4. 告知用户本次证据与是否已推送
607
-
608
- > **Lessons 提醒机制**:当 `.draftgo/config.json` 中 `lessons_on_push` 为 `true` 时,push 脚本执行完毕会输出一段回顾提醒。此提醒仅为兜底安全网;是否写 lessons 仍按“有阻碍或新经验”判断。
609
-
610
- ---
611
-
612
- ## 推送命令快捷入口
613
-
614
- - `/draftgo push pages` — 触发 push skill
615
- - `/draftgo push nav` — 触发 push skill
616
- - `/draftgo push db_meta` — 触发 push skill
617
- - `/draftgo push aihub` — 触发 push skill
618
- - `/draftgo push external_apis` — 触发 push skill
619
- - `/draftgo push system_config` — 触发 push skill
620
- - `/draftgo push docs` — 触发 push skill
621
- - `/draftgo push doc_categories` — 触发 push skill
622
- - `/draftgo push custom_scripts` — 触发 push skill(需人工确认)
623
- - `/draftgo push roles` — 触发 push skill(需人工确认)
624
- - `/draftgo push users` — 触发 push skill(需人工确认)
625
- - `/draftgo pull` — 触发 pull skill(从云端拉取,支持按类型)
626
- - `/draftgo pull --all` — 全量拉取
627
- - `/draftgo init` — 触发 init skill
628
-
629
- ---
630
-
631
- ## 自定义服务(Custom Scripts)开发指南
632
-
633
- > 为用户编写或修改 `.draftgo/custom_scripts/` 下的脚本时,**必须遵循以下规范**。违反将导致脚本加载失败或运行时报错。
634
-
635
- ### 核心机制
636
-
637
- DraftGo 脚本引擎使用 `exec()` 在受限沙箱中执行脚本代码。引擎在执行前自动注入 `on`、`route`、`scheduled` 三个装饰器到脚本命名空间。
638
-
639
- ### 装饰器用法(不需要 import)
640
-
641
- ```python
642
- # ✅ 直接使用——引擎已注入到命名空间
643
- @route("GET /hello")
644
- def hello(sdk, ctx):
645
- return sdk.response({"message": "Hello!"})
646
-
647
- @on("user.registered")
648
- def on_user(sdk, ctx):
649
- sdk.notify.send(ctx["payload"]["user_id"], "欢迎", "欢迎加入!")
650
-
651
- @scheduled("0 2 * * *")
652
- def cleanup(sdk, ctx):
653
- sdk.log.info("每日清理")
654
- ```
655
-
656
- ```python
657
- # ❌ 这些写法都会报错
658
- from draftgo import route # 没有 draftgo 这个内置模块
659
- from sdk import route # 可以但多余,直接用就行
660
- import sdk # 可以但多余
661
-
662
- @route("/hello") # ❌ 缺少 HTTP 方法
663
- @route("GET", "/hello") # ❌ 不是逗号分隔
664
- def handler(request): # ❌ 签名必须是 (sdk, ctx)
665
- return {"data": 1} # ❌ route 模式必须用 sdk.response()
666
- ```
667
-
668
- ### 装饰器格式
669
-
670
- | 模式 | 格式 | 示例 |
671
- |------|------|------|
672
- | Route | `@route("METHOD /path")` | `@route("POST /orders")` |
673
- | Event | `@on("domain.action")` | `@on("db.created")` |
674
- | Scheduled | `@scheduled("分 时 日 月 周")` | `@scheduled("30 8 * * 1-5")` |
675
-
676
- ### Handler 签名
677
-
678
- 所有模式统一 `(sdk, ctx)` 两个参数:
679
-
680
- - **sdk** — SDK 实例,提供 `sdk.db`、`sdk.users`、`sdk.notify`、`sdk.external_api`、`sdk.cache`、`sdk.log`、`sdk.auth`、`sdk.config`、`sdk.response()`、`sdk.now()`
681
- - **ctx** — 上下文,内容因模式而异:
682
- - Route: `{body, query_params, headers, method, path, user}`
683
- - Event: `{event, payload, timestamp}`
684
- - Scheduled: `{trigger_type, trigger_name, payload, config}`
685
-
686
- ### 可导入模块
687
-
688
- 自定义脚本只允许管理员创建和编辑,因此脚本引擎**不再做 import 白名单限制**。脚本可以导入当前后端运行环境中已经安装的 Python 模块;如果目标环境没有安装该依赖,脚本加载或执行时会报 `ImportError`。
689
-
690
- 建议优先使用标准库和 DraftGo SDK。需要新增第三方依赖时,要确认部署环境的 `requirements.txt` / 镜像中已经包含该包,避免本地可用、线上不可用。
691
-
692
- **推荐做法:**
693
-
694
- | 需求 | 推荐 |
695
- |------|------|
696
- | 调外部 HTTP | 优先 `sdk.external_api.get/post/put/delete(url, headers, timeout)`,也可在依赖可用时自行导入 HTTP 客户端 |
697
- | 缓存 | 优先 `sdk.cache.get/set/delete(key)`,自动按脚本隔离 key |
698
- | 日志 | 用 `sdk.log.info/warn/error/debug(msg)`,会进入执行记录 |
699
- | 当前时间 | 用 `sdk.now()` 获取平台时区时间;也可导入 `datetime` / `time` |
700
- | 系统配置和密钥 | 用 `sdk.config.get(key)`,不要把密钥硬编码在脚本里 |
701
- | 文件和系统命令 | 尽量避免;脚本与主进程同进程运行,误操作会影响后端服务 |
702
-
703
- ### SDK 速查表(与基座源码对齐)
704
-
705
- > 来源:`backend/app/engine/sdk/modules.py`。所有方法均为实例方法,签名以源码为准。
706
-
707
- #### `sdk.db` — 通用数据操作(7 个方法,无 `list`)
708
-
709
- | 方法 | 签名 | 说明 |
710
- |------|------|------|
711
- | `create` | `(type: str, data: dict, userid: int = None) -> dict` | 创建记录,返回完整 dict |
712
- | `create_many` | `(type: str, items: list[dict]) -> list[dict]` | 批量创建;每项可传裸业务字典,或 `{data, userid, status}` 包装对象 |
713
- | `get` | `(type: str, id: int) -> dict \| None` | 查不到返回 `None`(不抛异常) |
714
- | `update` | `(type: str, id: int, data: dict) -> dict` | 部分更新,`data` 传**裸业务字典**,SDK 内部自动包装 `{"data": data}` 提交,**禁止**手动再包一层 |
715
- | `update_many` | `(type: str, items: list[dict]) -> list[dict]` | 批量更新;每项传 `{id, data}`,或 `{id, 字段...}` 让 SDK 自动包装为 `data` |
716
- | `delete` | `(type: str, id: int) -> bool` | 失败返回 `False`,不抛异常 |
717
- | `query` | `(type: str, filters: dict = None, page: int = 1, page_size: int = 20, order_by: str = None, order: str = "desc") -> dict` | 返回 `{items, total, page, page_size}` |
718
-
719
- **`filters` 语义(重要)**:结构化条件查询,直接对记录的 `data` JSON 字段做 WHERE,**支持** `eq` / `like` / `gte` / `lte` / `gt` / `lt` / `in` / `contains`。多字段是 AND 关系。字段必须在 db_meta schema 里标 `searchable`,且操作符要匹配字段的检索模式(exact/fuzzy/range/contains),否则后端报 400。
720
-
721
- **filters 写法**:
722
- | 形式 | 含义 | 示例 |
723
- |--------|------|------|
724
- | `{field: 值}` | 精确等于(eq) | `{"status": "paid"}` |
725
- | `{field: {"op": 操作符, "value": 值}}` | 指定操作符 | `{"title": {"op": "like", "value": "公告"}}` |
726
- | `{field: [...]}` | 命中其一(in) | `{"status": ["paid", "pending"]}` |
727
- | `{field: bool}` | 精确等于(SDK 转 `"true"`/`"false"`) | `{"done": True}` |
728
- | `{field: None}` | **跳过**,不参与查询 | `{"field": None}` |
729
-
730
- 范围/排序示例:`sdk.db.query("article", {"views": {"op": "gte", "value": 100}}, order_by="views", order="desc")`
731
-
732
- **上限**:`page_size` 内部 `min(page_size, 1000)`,超出静默截断。
733
-
734
- #### `sdk.external_api` — 外部 HTTP 调用(仅 4 个方法,**只接受裸 URL**)
735
-
736
- | 方法 | 签名 |
737
- |------|------|
738
- | `get` | `(url, headers: dict = None, timeout: int = 10) -> dict` |
739
- | `post` | `(url, body=None, headers: dict = None, timeout: int = 10) -> dict` |
740
- | `put` | `(url, body=None, headers: dict = None, timeout: int = 10) -> dict` |
741
- | `delete` | `(url, headers: dict = None, timeout: int = 10) -> dict` |
742
-
743
- **返回结构统一**:
744
-
745
- ```python
746
- # 正常
747
- {"status_code": int, "headers": dict, "data": json_or_text}
748
- # 异常(不抛异常,统一以 dict 返回)
749
- {"status_code": 0, "error": "请求超时 (30s)"}
750
- {"status_code": code, "error": "响应体超过最大限制 (5242880 bytes)"}
751
- ```
752
-
753
- **硬上限(被静默截断)**:
754
-
755
- | 项 | 值 | 行为 |
756
- |---|---|---|
757
- | `timeout` | 1 ≤ t ≤ **30s** | 传 120 会被静默改成 30 |
758
- | 响应体 | **5MB** | 超出返回 `{status_code, error}`,无 `data` |
759
-
760
- **没有 `call()` 方法**:调用 `sdk.external_api.call(...)` 会抛 `AttributeError`。脚本里**没有**"按已注册 API code 走代理"的能力,只能裸 URL。
761
-
762
- #### 两套外部 API 调用路径(不共享配置!)
763
-
764
- | 路径 | 在哪里用 | 是否读「外部 API 接入」注册表 |
765
- |------|---------|-------------------------------|
766
- | `sdk.external_api.*(url, headers, timeout)` | **脚本内** | ❌ 不读。headers / auth_config / base_url 都不复用 |
767
- | `App.callApi(code, {...})` / `POST /api/external-apis/call/{code}` | 页面壳层 / 前端 | ✅ 走注册表,自动带 headers、auth、base_url |
768
-
769
- **结论**:在脚本里想"复用已注册 API",必须**自己重写 headers**。`.draftgo/external_apis/index.json` 中的 `auth_config` 已脱敏,不要从那里拷敏感字段;建议把密钥写入系统配置后用 `sdk.config.get(key)` 读取。
770
-
771
- 最小 helper(按需复制到脚本顶部):
772
-
773
- ```python
774
- def call_registered(sdk, base_url: str, path: str, method: str = "GET",
775
- body=None, extra_headers: dict = None, timeout: int = 15):
776
- """脚本内复用已注册 API 的最小封装:自带通用头,密钥走 sdk.config。"""
777
- headers = {
778
- "User-Agent": "DraftGo-Script/1.0",
779
- "Accept": "application/json",
780
- }
781
- token = sdk.config.get("MY_API_TOKEN") # 在系统配置里事先写好
782
- if token:
783
- headers["Authorization"] = f"Bearer {token}"
784
- if extra_headers:
785
- headers.update(extra_headers)
786
- url = base_url.rstrip("/") + "/" + path.lstrip("/")
787
- fn = getattr(sdk.external_api, method.lower())
788
- return fn(url, body=body, headers=headers, timeout=timeout) if method.upper() in ("POST","PUT") \
789
- else fn(url, headers=headers, timeout=timeout)
790
- ```
791
-
792
- #### `sdk.aihub` — 调用 AIHub Agent / 图片生成
793
-
794
- 脚本里需要复用 AIHub 时,用 `sdk.aihub`,不要自己保存模型 API Key。必须在脚本 `config.aihub.enabled=true` 后才可用。
795
-
796
- **身份规则**:
797
-
798
- | 触发来源 | AIHub 身份 |
77
+ | `.draftgo/pages/` | index.json + .html 文件 |
78
+ | `.draftgo/navigations/` | index.json + .html 文件 |
79
+ | `.draftgo/db_meta/` | index.json(含 schema/permission) |
80
+ | `.draftgo/custom_scripts/` | index.json + 脚本文件 |
81
+ | `.draftgo/external_apis/` | index.json(auth_config 已脱敏) |
82
+ | `.draftgo/aihub/` | index.json |
83
+ | `.draftgo/docs/articles/` | index.json + .md 文件 |
84
+ | `.draftgo/system_config/` | index.json |
85
+ | `.draftgo/changelog.md` | 更新日志 |
86
+ | `.draftgo/lessons/` | 开发经验(YYYY-MM-DD-主题.md) |
87
+ | `.draftgo/Task/` | 任务文档(仅跨资源/高风险任务) |
88
+
89
+ ## 文档索引
90
+
91
+ | 文档 | 读取时机 |
799
92
  |---|---|
800
- | 登录用户调用 route | 当前登录用户 |
801
- | 匿名 public route | 默认禁止 |
802
- | scheduled | system |
803
- | event | payload 有 `user_id` / `actor_user_id` 则继承用户,否则 system |
804
- | 管理员手动 execute | 点击执行的管理员 |
805
- | Agent tool -> custom script | 发起 Agent 请求的用户 |
806
-
807
- **config 示例**:
808
-
809
- ```json
810
- {
811
- "timeout": 120,
812
- "aihub": {
813
- "enabled": true,
814
- "allowed_agents": [12, 18],
815
- "allowed_models": [],
816
- "allow_direct_model_call": false,
817
- "allow_images": true
818
- }
819
- }
820
- ```
821
-
822
- **代码示例**:
823
-
824
- ```python
825
- @route("POST /summarize")
826
- def summarize(sdk, ctx):
827
- body = ctx["body"] or {}
828
- result = sdk.aihub.chat(agent_id=12, message=body.get("text", ""))
829
- return sdk.response(result)
830
-
831
- @route("POST /poster")
832
- def poster(sdk, ctx):
833
- body = ctx["body"] or {}
834
- image = sdk.aihub.images.generate(
835
- agent_id=18,
836
- prompt=body.get("prompt", ""),
837
- size="1024x1024",
838
- response_format="url",
839
- )
840
- return sdk.response(image)
841
- ```
842
-
843
- 优先用 `agent_id`,让提示词、工具、图片默认参数和可选用户选模型都在 AIHub Agent 中统一管理。只有平台内部脚本确实需要裸模型时才开启 `allow_direct_model_call`。
844
-
845
- #### `sdk.cache` — Redis 缓存
846
-
847
- | 方法 | 签名 | 上限 |
848
- |------|------|------|
849
- | `set` | `(key, value, ttl: int = 3600)` | TTL 1–**86400s**(超出截断),value 序列化后 ≤ **1MB** |
850
- | `get` | `(key) -> Any \| None` | JSON 反序列化失败时回退原始字符串 |
851
- | `delete` | `(key) -> bool` | — |
852
-
853
- **Key 自动加前缀** `script:{script_id}:`,跨脚本不互相覆盖。
854
-
855
- #### `sdk.log` — 日志(持久化到 Execution_Record)
856
-
857
- | 方法 | 行为 |
858
- |------|------|
859
- | `info / warn / error / debug(message: str)` | 内存收集,handler 结束后落库 |
860
-
861
- **上限**:单条消息 **4096 字符**(超出截断),单次执行 **500 条**(超出**静默丢弃**,不报错)。日志爆掉调试需求时,自己合并/采样。
862
-
863
- **`POST /scripts/{id}/execute` 返回结构**:
864
-
865
- ```json
866
- {
867
- "code": 200,
868
- "data": {
869
- "status": "success | error | timeout",
870
- "output": <handler 返回值>,
871
- "error": "错误信息(成功时为 null)",
872
- "error_detail": "完整错误堆栈(成功时为 null)",
873
- "duration_ms": 123,
874
- "logs": [{"level": "info", "message": "...", "timestamp": "..."}]
875
- }
876
- }
877
- ```
878
-
879
- `logs` 字段为 `sdk.log.*()` 收集的日志数组,handler 结束后随执行结果一起返回。`GET /scripts/{id}/executions` 历史记录中同样包含 `logs`(JSON 字符串形式,需 `JSON.parse`)。
880
-
881
- #### 双层超时模型(极易混淆)
882
-
883
- | 层 | 控制处 | 默认 | 上限 | 触发后 |
884
- |---|---|---|---|---|
885
- | **脚本整体超时** | `config.timeout`(脚本配置) | **30s** | 由后端调度器决定 | 状态变 `timeout`,提示 `执行超时 (Ns)` |
886
- | **单次 HTTP 超时** | `sdk.external_api.*(timeout=...)` | 10s | **30s(硬上限)** | 该次调用返回 `{status_code: 0, error: "请求超时 (30s)"}` |
887
-
888
- **铁律**:`config.timeout` 必须 **≥ 所有 HTTP 调用 timeout 之和 + 余量**。串行多次外部请求时务必显式调大 `config.timeout`(建议 120–300)。
889
-
890
- ### 脚本模板(复制即用)
891
-
892
- **Route 模式:**
893
- ```python
894
- import json
895
- import datetime
896
- import uuid
897
-
898
- @route("GET /health")
899
- def health(sdk, ctx):
900
- return sdk.response({
901
- "status": "ok",
902
- "timestamp": sdk.now().isoformat(),
903
- "request_id": str(uuid.uuid4())
904
- })
905
-
906
- @route("POST /process")
907
- def process(sdk, ctx):
908
- body = ctx["body"]
909
- if not body:
910
- return sdk.response({"error": "请求体为空"}, status_code=400)
911
- sdk.log.info(f"收到数据: {json.dumps(body, ensure_ascii=False)[:200]}")
912
- return sdk.response({"ok": True})
913
- ```
914
-
915
- **Event 模式:**
916
- ```python
917
- @on("user.registered")
918
- def welcome(sdk, ctx):
919
- user_id = ctx["payload"]["user_id"]
920
- sdk.notify.send(user_id, "欢迎", "欢迎加入平台!", type="info")
921
- sdk.log.info(f"欢迎通知已发送: user_id={user_id}")
922
- ```
923
-
924
- **Scheduled 模式(简单版):**
925
- ```python
926
- @scheduled("0 9 * * 1")
927
- def weekly_report(sdk, ctx):
928
- result = sdk.users.list(page=1, page_size=1)
929
- sdk.log.info(f"周报: 用户总数={result.get('total', 0)}")
930
- ```
931
-
932
- **Scheduled 模式(多源串行抓取 — 完整最佳实践):**
933
-
934
- > 演示要点:显式 UA、单次 timeout ≤ 30、用 `sdk.cache` 去重、用 `sdk.db.query`(不是 `list`)、按需配 `config.timeout=300`。
935
-
936
- ```python
937
- import json, hashlib
938
-
939
- SOURCES = [
940
- ("source_a", "https://example.com/api/a"),
941
- ("source_b", "https://example.com/api/b"),
942
- ]
943
-
944
- @scheduled("*/30 * * * *")
945
- def fetch_all(sdk, ctx):
946
- headers = {"User-Agent": "Mozilla/5.0 (compatible; DraftGo-Script/1.0)"}
947
- saved = 0
948
- for name, url in SOURCES:
949
- resp = sdk.external_api.get(url, headers=headers, timeout=20)
950
- if resp.get("status_code") != 200:
951
- sdk.log.warn(f"{name} 拉取失败: {resp.get('error') or resp.get('status_code')}")
952
- continue
953
- for item in (resp.get("data") or {}).get("items", []):
954
- uniq = hashlib.md5(f"{name}:{item.get('id')}".encode()).hexdigest()
955
- if sdk.cache.get(uniq):
956
- continue
957
- sdk.db.create("trend_item", {"source": name, "raw": item})
958
- sdk.cache.set(uniq, 1, ttl=86400)
959
- saved += 1
960
- sdk.log.info(f"抓取完成,新增 {saved} 条")
961
- ```
962
-
963
- > 同步该脚本时 `config` 必须给到:`{"timeout": 300, "max_retries": 0}`,否则脚本整体 30s 默认超时会先于循环结束。
964
-
965
- ### 配置字段说明
966
-
967
- 创建/更新脚本时的 JSON 字段:
968
-
969
- ```json
970
- {
971
- "name": "服务名称",
972
- "slug": "url-safe-slug",
973
- "code": "脚本代码(完整 Python)",
974
- "mode": "route | event | scheduled",
975
- "triggers": {},
976
- "config": {"timeout": 30},
977
- "permission": {"default": "public"}
978
- }
979
- ```
980
-
981
- | 字段 | 必填 | 说明 |
982
- |------|------|------|
983
- | `name` | ✅ | 显示名称 |
984
- | `slug` | ✅ | URL 标识(route 模式的访问路径:`/api/x/{slug}/{path}`) |
985
- | `code` | ✅ | 完整 Python 代码 |
986
- | `mode` | ✅ | `event` / `route` / `scheduled` |
987
- | `triggers` | 否 | 当前主要作为 event/route 的管理端元数据保留。event 模式可写 `{"events": ["user.registered"]}`,route 模式可写路径/方法;scheduled 模式不需要写 `triggers`,cron 只读取代码中的 `@scheduled(...)` 装饰器,后端会清空 scheduled 的 trigger 元数据 |
988
- | `config` | 否 | `{"timeout": 30, "max_retries": 0}`;`timeout` 是**脚本整体超时**(默认 30s),与 `sdk.external_api` 的单次 HTTP timeout(默认 10s、硬上限 30s)是**两层独立机制**。多次外部调用串行场景务必上调到 120–300 |
989
- | `permission` | 否 | 仅 route 模式有效:`{"default": "public|login|admin", "roles": [...]}` |
990
-
991
- **Route 配置推荐写法:**
992
-
993
- ```json
994
- {
995
- "config": {
996
- "timeout": 20,
997
- "route_security": {
998
- "auth_required": true,
999
- "rate_limit_per_minute": 60,
1000
- "burst_limit": 10,
1001
- "max_body_size_kb": 256,
1002
- "timeout_ms": 15000,
1003
- "ip_allowlist": ["10.0.0.0/8"]
1004
- }
1005
- }
1006
- }
1007
- ```
1008
-
1009
- 当前后端兼容把这些字段直接写在 `config` 顶层,但为了避免混乱,文档统一按 `config.route_security` 说明。
1010
-
1011
- **Route 配置推荐写法:**
1012
-
1013
- ```json
1014
- {
1015
- "config": {
1016
- "timeout": 20,
1017
- "route_security": {
1018
- "auth_required": true,
1019
- "rate_limit_per_minute": 60,
1020
- "burst_limit": 10,
1021
- "max_body_size_kb": 256,
1022
- "timeout_ms": 15000,
1023
- "ip_allowlist": ["10.0.0.0/8"]
1024
- }
1025
- }
1026
- }
1027
- ```
1028
-
1029
- 当前后端兼容把这些字段直接写在 `config` 顶层,但为了避免混乱,文档统一按 `config.route_security` 说明。
1030
-
1031
- ### 权限配置(仅 route 模式)
1032
-
1033
- | 场景 | permission 值 |
1034
- |------|--------------|
1035
- | 任何人可访问 | `{"default": "public"}` |
1036
- | 登录才能访问 | `{"default": "login"}` |
1037
- | 指定角色 | `{"default": "login", "roles": ["vip"]}` |
1038
- | 仅管理员 | `{"default": "admin"}` |
1039
-
1040
- ### 可监听事件列表
1041
-
1042
- | 事件 | Payload |
1043
- |------|---------|
1044
- | `user.registered` | `{user_id, username, email}` |
1045
- | `user.login` | `{user_id, login_at}` |
1046
- | `user.updated` | `{user_id, changes}` |
1047
- | `db.created` | `{type, record_id}` |
1048
- | `db.updated` | `{type, record_id, changes}` |
1049
- | `db.deleted` | `{type, record_id}` |
1050
- | `page.created` | `{page_id, route, title}` |
1051
- | `page.updated` | `{page_id, changes}` |
1052
- | `script.executed` | `{script_id, execution_id, status, duration_ms}` |
1053
-
1054
- ### 访问路径与调度来源
1055
-
1056
- Route 模式脚本通过 `/api/x/{slug}/{path}` 访问:
1057
- - slug 为 `order-api`,handler 为 `@route("GET /list")` → 访问 `GET /api/x/order-api/list`
1058
- - slug 为 `health-check`,handler 为 `@route("GET /health")` → 访问 `GET /api/x/health-check/health`
1059
-
1060
- Scheduled 模式的 cron **写在代码装饰器里**:
1061
- - `@scheduled("0 2 * * *")` → 每天凌晨 2 点执行
1062
- - 当前引擎加载 scheduled handler 时直接读取装饰器里的 cron 表达式
1063
- - scheduled 模式不需要触发器 cron 元数据;即使旧客户端传入也会被后端清空
1064
-
1065
- Event 模式同理:
1066
- - `@on("user.registered")` 才是真正注册事件的来源
1067
- - `triggers.events` 建议与代码保持一致,作为管理端元数据保存
1068
-
1069
- **Route 安全机制(必须考虑)**:
1070
-
1071
- 所有 `/api/x/{slug}/{path}` 请求在进入脚本前会经过平台安全守卫:Route 总开关、HTTP method 白名单、IP 黑白名单、每分钟限流、10 秒突发限流、默认登录要求、请求体大小上限、执行超时上限。全局默认来自系统配置 `script_route_*`,单个脚本可在 `config.route_security` 覆盖。
1072
-
1073
- 当前后端也兼容把 `auth_required`、`rate_limit_per_minute`、`burst_limit`、`max_body_size_kb`、`timeout_ms`、`ip_allowlist`、`ip_blocklist` 直接写在 `config` 顶层,但**推荐统一写在 `config.route_security` 下**:
1074
-
1075
- ```json
1076
- {
1077
- "timeout": 20,
1078
- "route_security": {
1079
- "auth_required": true,
1080
- "rate_limit_per_minute": 60,
1081
- "burst_limit": 10,
1082
- "max_body_size_kb": 256,
1083
- "timeout_ms": 15000,
1084
- "ip_allowlist": ["10.0.0.0/8"]
1085
- }
1086
- }
1087
- ```
1088
-
1089
- 公开 Route 接口不要关闭限流;涉及外部回调、支付、Webhook、AI 调用等高频/高成本接口时,必须显式设置请求体上限、限流和合理超时。
1090
-
1091
- ### 开发常见错误速查
1092
-
1093
- | 症状 | 原因 | 修复 |
1094
- |------|------|------|
1095
- | 脚本状态 error: `ImportError` / `No module named ...` | 运行环境没有安装该依赖,或模块名写错 | 改用标准库 / SDK,或先把依赖加入后端运行环境 |
1096
- | 脚本状态 error: "代码语法错误" | Python 语法问题 | 本地用 `python -c "import ast; ast.parse(open('x.py').read())"` 验证 |
1097
- | 访问 /api/x/slug/path 返回 404 | slug 或 path 不匹配 | 确认 slug 和 @route 中的 path 拼写 |
1098
- | 访问 /api/x/slug/path 返回 405 | HTTP 方法不匹配 | GET 请求但装饰器写了 POST |
1099
- | handler 执行报错 "参数不匹配" | handler 签名不是 (sdk, ctx) | 修正签名 |
1100
- | route handler 返回 None | 没有用 sdk.response() | 添加 return sdk.response(...) |
1101
- | 500 Internal Server Error(FK constraint) | SAT 调用时 user_id=0 | Service 层需处理 id=0 → None |
1102
- | `'ExternalAPIModule' object has no attribute 'call'` | 误以为脚本里能按 code 调注册过的 API | 用 `sdk.external_api.get/post/put/delete(url)`,注册表配置仅页面侧 `App.callApi` 复用 |
1103
- | 拉取公开 HTTP 接口返回 403 | 注册表里配的 UA 不会被脚本复用 | 在脚本里显式 `headers={"User-Agent": "..."}` |
1104
- | 多平台串行调用时脚本整体超时(但单次 HTTP 没报超时) | `config.timeout` 默认 30s,被先触发 | 提交脚本时把 `config.timeout` 调到 120–300 |
1105
- | `AttributeError: 'DBModule' object has no attribute 'list'` | 文档老版本误写 `sdk.db.list` | 用 `sdk.db.query(type, filters, page, page_size)` |
1106
- | `sdk.db.query` 加了 `gte` / `in` 等条件报 400 | 该字段未标 searchable,或操作符与字段的检索模式不匹配 | 在 db_meta schema 给字段标对应 searchable 模式(range 才支持 gte/lte,exact/fuzzy 支持 eq/in) |
1107
- | `sdk.db.query(filters={"done": True})` 返回 0 条 | 字段未标 searchable,或 boolean 字段未按 exact 模式配置 | 给字段标 `searchable`,boolean 传 `True`/`False`(SDK 内部转 `"true"`/`"false"`) |
1108
- | `sdk.db.update` 报 schema 校验失败或字段丢失 | 第三个参数 `data` 已被 SDK 自动包装,禁止手动再包 `{"data": {...}}` | 直接传裸字典:`sdk.db.update("type", id, {"field": val})` |
1109
- | `sdk.db.update_many` 后部分数据没更新 | 批量更新是一次事务,任意一条失败会整批回滚 | 先确认每项都有 `id`,且 `{id, data}` 中的 `data` 满足 schema |
1110
- | `sdk.external_api.*(timeout=120)` 仍然 30s 超时 | 单次 HTTP timeout 硬上限 30s | 改造业务逻辑,把单次请求拆短;或改用分批拉取 |
1111
-
1112
- ---
1113
-
1114
- ## AIHub 资产配置指南
1115
-
1116
- > 用户说"帮我配一个 MCP"、"给 Agent 加工具"、"配一个模型供应商"时,参照本节通过 API 操作。
1117
-
1118
- ### 资产类型总览
1119
-
1120
- | type | 说明 | 创建后需要 |
1121
- |------|------|-----------|
1122
- | `model` | 模型供应商(OpenAI 兼容) | 同步模型列表 |
1123
- | `prompt` | 提示词 | — |
1124
- | `agent` | 智能体(用户对话入口) | 绑定模型 + 可选绑定工具 |
1125
- | `mcp` | MCP Server(远程工具服务) | discover-tools 发现工具 → 绑定到 Agent |
1126
- | `skill` | Skill(平台内置工具) | 绑定到 Agent |
1127
-
1128
- ### 创建模型供应商
1129
-
1130
- ```
1131
- POST /api/aihub
1132
- Body:
1133
- {
1134
- "type": "model",
1135
- "name": "供应商名称",
1136
- "priority": 100,
1137
- "data": {
1138
- "provider": "openai-compatible",
1139
- "base_url": "https://api.example.com/v1",
1140
- "api_key": "sk-xxx",
1141
- "models": [],
1142
- "enabled": true,
1143
- "timeout": 120,
1144
- "image_generation_path": "/images/generations",
1145
- "headers": {}
1146
- }
1147
- }
1148
- ```
1149
-
1150
- 创建后同步模型列表:`POST /api/aihub/{id}/sync`
1151
-
1152
- 图片生成能力不在模型资产上打标签。需要判断某模型是否支持生图时,可选调用:
1153
-
1154
- ```
1155
- POST /api/aihub/{id}/test-image-generation
1156
- Body: {"model": "gpt-image-1", "prompt": "A simple red square", "size": "1024x1024", "response_format": "url"}
1157
- ```
1158
-
1159
- 结果只写入 `data.image_generation_probe` 供配置人员参考,不作为后续调用门禁。真实调用仍以 `/api/images/generation` 或 `/api/agents/{id}/images` 的上游结果为准。
1160
-
1161
- ### 创建 MCP 资产
1162
-
1163
- ⚠️ **管理界面仅支持远程协议**:`sse` 和 `streamable_http`。stdio 后端保留兼容但前端已移除。
1164
-
1165
- ```
1166
- POST /api/aihub
1167
- Body:
1168
- {
1169
- "type": "mcp",
1170
- "name": "MCP Server 名称",
1171
- "data": {
1172
- "transport": "sse",
1173
- "url": "https://mcp-server.example.com/sse",
1174
- "headers": {},
1175
- "api_key": null,
1176
- "timeout_ms": 30000,
1177
- "tools": [],
1178
- "tool_filter": [],
1179
- "enabled": true
1180
- }
1181
- }
1182
- ```
1183
-
1184
- **transport 选择**:
1185
- - `sse`:服务端推送(Server-Sent Events),适合大多数远程 MCP Server
1186
- - `streamable_http`:HTTP POST 请求 + 流式响应,适合新版 MCP 协议
1187
-
1188
- **创建后必须发现工具**:
1189
- ```
1190
- POST /api/aihub/mcp/discover-tools
1191
- Body: {"id": <mcp_asset_id>}
1192
- 返回: { "code": 200, "data": { "tools": [{"name": "search", "description": "...", "inputSchema": {...}}, ...], "count": N } }
1193
- ```
1194
-
1195
- 发现的工具会写入该 MCP 资产的 `data.tools` 数组。
1196
-
1197
- ### 创建 Agent(智能体)
1198
-
1199
- ```
1200
- POST /api/aihub
1201
- Body:
1202
- {
1203
- "type": "agent",
1204
- "name": "智能体名称",
1205
- "describe": "功能描述",
1206
- "data": {
1207
- "schema_version": "agent.v3",
1208
- "spec": {
1209
- "model_id": 1,
1210
- "system_prompt_template": "你是一个助手。",
1211
- "runtime": {
1212
- "temperature": 0.7,
1213
- "max_tokens": null,
1214
- "stream": true
1215
- },
1216
- "context": {
1217
- "max_history": 20
1218
- }
1219
- }
1220
- }
1221
- }
1222
- ```
1223
-
1224
- 聊天型 Agent 默认 `spec.mode="chat"`,调用 `POST /api/agents/{id}/chat`。
1225
-
1226
- ### Agent 用户自主选择模型
1227
-
1228
- Agent 支持把“主模型 + 备用模型”作为用户可选模型白名单暴露给页面。开启方式是在 Agent 的 `data.spec.model_selection` 中写入配置:
1229
-
1230
- ```json
1231
- {
1232
- "data": {
1233
- "schema_version": "agent.v3",
1234
- "spec": {
1235
- "model": "gpt-4.1",
1236
- "fallback_models": ["gpt-4.1-mini", "claude-3-5-sonnet"],
1237
- "model_selection": {
1238
- "user_selectable": true,
1239
- "on_invalid": "ignore"
1240
- }
1241
- }
1242
- }
1243
- }
1244
- ```
1245
-
1246
- 获取该 Agent 允许用户选择的模型列表:
1247
-
1248
- ```
1249
- GET /api/agents/{agent_id}/selectable-models
1250
- ```
1251
-
1252
- 返回:
1253
-
1254
- ```json
1255
- {
1256
- "user_selectable": true,
1257
- "models": ["gpt-4.1", "gpt-4.1-mini", "claude-3-5-sonnet"]
1258
- }
1259
- ```
1260
-
1261
- 注意:
1262
-
1263
- - `models` 是 Agent 白名单,由 `spec.model` + `spec.fallback_models` 去重派生,不是供应商全量模型列表。
1264
- - 未开启 `user_selectable` 时返回 `{ "user_selectable": false, "models": [] }`。
1265
- - `on_invalid="ignore"` 表示传入非白名单模型时回落到主模型;`reject` 表示拒绝请求。
1266
- - 页面端优先使用 `DraftGoAI.getSelectableModels(agentId)`,再把用户选中的模型作为 `options.model` 传给 `DraftGoAI.chat` 或 `DraftGoAI.images`。
1267
-
1268
- ### Agent 结构化 JSON 输出
1269
-
1270
- Agent 需要稳定 JSON 返回时,不要只在提示词里口头要求;应配置 `data.spec.output_format`:
1271
-
1272
- ```json
1273
- {
1274
- "data": {
1275
- "schema_version": "agent.v3",
1276
- "spec": {
1277
- "model_id": 1,
1278
- "model": "gpt-4.1",
1279
- "output_format": {
1280
- "mode": "json",
1281
- "json": {
1282
- "strategy": "auto",
1283
- "schema_name": "agent_output",
1284
- "schema": {
1285
- "type": "object",
1286
- "properties": {
1287
- "answer": { "type": "string" },
1288
- "confidence": { "type": "number" }
1289
- },
1290
- "required": ["answer"],
1291
- "additionalProperties": false
1292
- }
1293
- }
1294
- }
1295
- }
1296
- }
1297
- }
1298
- ```
1299
-
1300
- 字段规则:
1301
-
1302
- | 字段 | 说明 |
1303
- |------|------|
1304
- | `output_format.mode` | `native`(默认)或 `json` |
1305
- | `output_format.json.strategy` | `auto` / `native` / `prompt`;推荐 `auto` |
1306
- | `output_format.json.schema` | 可选 JSON Schema;有 schema 时优先尝试 `json_schema` |
1307
- | `output_format.json.schema_name` | 传给 OpenAI 兼容 `response_format.json_schema.name` |
1308
-
1309
- 模型资产可调用 `POST /api/aihub/{model_id}/test-response-format` 探测 `response_format` 支持情况,结果写回 `data.supports_response_format` 与 `data.supports_json_schema`。`strategy=auto` 会优先使用原生 `response_format`,遇到上游不支持时降级为提示词约束。流式 JSON 输出结束时会追加 `json.validation` SSE 事件,用于前端判断解析/Schema 校验是否通过。
1310
-
1311
- ### 创建图片生成型 Agent
1312
-
1313
- ```
1314
- POST /api/aihub
1315
- Body:
1316
- {
1317
- "type": "agent",
1318
- "name": "海报生成智能体",
1319
- "describe": "根据提示词生成图片",
1320
- "data": {
1321
- "schema_version": "agent.v3",
1322
- "spec": {
1323
- "mode": "image_generation",
1324
- "model_id": 1,
1325
- "model": "gpt-image-1",
1326
- "system_prompt_template": "统一生成商业海报,画面清晰。",
1327
- "image_generation": {
1328
- "n": 1,
1329
- "size": "1024x1024",
1330
- "quality": "auto",
1331
- "response_format": "url"
1332
- }
1333
- }
1334
- }
1335
- }
1336
- ```
1337
-
1338
- 图片生成型 Agent 调用:
1339
-
1340
- ```
1341
- POST /api/agents/{agent_id}/images
1342
- Body: {"prompt": "生成一张夏季饮品海报", "size": "1024x1024", "model": "gpt-image-1"}
1343
- ```
1344
-
1345
- 页面里使用 `DraftGoAI.images(agentId, prompt, options)`;不要把图片生成请求发到 `/chat`。`options.model` 同样遵循 Agent 的用户选模型白名单。
1346
-
1347
- ### 给 Agent 绑定工具
1348
-
1349
- 通过 `PUT /api/aihub/{agent_id}` 更新 Agent 的 `data.spec.tools` 配置:
1350
-
1351
- ```json
1352
- {
1353
- "data": {
1354
- "spec": {
1355
- "tools": {
1356
- "enabled": true,
1357
- "max_iterations": 10,
1358
- "parallel_calls": true,
1359
- "sources": [
1360
- {"type": "mcp", "id": 10, "name": "GitHub MCP"},
1361
- {"type": "external_api", "id": 5, "name": "天气查询"},
1362
- {"type": "custom_script", "id": 3, "name": "数据查询"}
1363
- ]
1364
- }
1365
- }
1366
- }
1367
- }
1368
- ```
1369
-
1370
- **sources 字段说明**:
1371
-
1372
- | type | id 含义 | 说明 |
1373
- |------|---------|------|
1374
- | `mcp` | MCP 资产 ID | 先 discover-tools 确保有工具 |
1375
- | `external_api` | 外部 API 资产 ID | 已注册到「外部 API 接入」的 API |
1376
- | `custom_script` | 自定义脚本 ID | 已创建的 route 模式脚本 |
1377
-
1378
- 可选 `tool_filter: ["tool_a", "tool_b"]` 限制暴露哪些工具(空=全部暴露)。
1379
-
1380
- ### 完整操作流程示例:配一个 MCP 并绑定到 Agent
1381
-
1382
- 1. 创建 MCP 资产 → 得到 `mcp_id`
1383
- 2. `POST /api/aihub/mcp/discover-tools {"id": mcp_id}` → 确认工具列表
1384
- 3. 查找或创建 Agent → 得到 `agent_id`
1385
- 4. `PUT /api/aihub/{agent_id}` 更新 `data.spec.tools.sources` 添加 `{"type": "mcp", "id": mcp_id, "name": "xxx"}`
1386
- 5. 测试:`POST /api/agents/{agent_id}/chat {"message": "...", "stream": true}`
1387
-
1388
- ### Agent 对话流式事件(前端对接参考)
1389
-
1390
- Agent 开启工具后,流式响应中除常规 SSE chunk 外会插入自定义事件:
1391
-
1392
- ```
1393
- data: {"object":"tool.executing","tool_call_id":"call_xxx","name":"mcp_10_search","source_type":"mcp"}
1394
- data: {"object":"tool.result","tool_call_id":"call_xxx","name":"mcp_10_search","content":"...","success":true,"duration_ms":320,"source_type":"mcp"}
1395
- ```
1396
-
1397
- - `source_type`:`mcp` / `external_api` / `custom_script`
1398
- - `tool.executing`:工具开始执行(前端可显示 loading 状态)
1399
- - `tool.result`:工具执行完成(含结果和耗时)
1400
-
1401
- ### 常见配置问题
1402
-
1403
- | 问题 | 排查 |
1404
- |------|------|
1405
- | discover-tools 超时 | 检查 MCP url 是否可达、timeout_ms 是否够长 |
1406
- | Agent 对话不调工具 | 确认 `spec.tools.enabled=true` 且 `sources` 非空 |
1407
- | 工具调用 404 | source id 对应的资产不存在或已禁用 |
1408
- | MCP 连接失败 | 确认 transport 类型与服务端协议匹配(sse vs streamable_http) |
93
+ | `core/architecture.md` | 进入陌生项目·理解平台机制(3分钟) |
94
+ | `core/modules.md` | 评估功能可行性·选择开发路径 |
95
+ | `specs/runtime.md` | 处理 token/路由/事件/全局层问题 |
96
+ | `specs/data.md` | 动态 DB·filters·db_meta 操作 |
97
+ | `specs/ui-protocol.md` | dg-* 完整映射表 |
98
+ | `specs/security.md` | 开发禁区速查 |
99
+ | `practices/dev-declaration.md` | 标准功能/高风险任务开始前 |
100
+ | `practices/best-practices.md` | 开发决策·Code Review |
101
+ | `practices/anti-patterns.md` | 检查反模式 |
102
+ | `quickref/app-api.md` | App API 速查 |
103
+ | `quickref/api-endpoints.md` | 后端端点速查 |
104
+ | `quickref/dg-components.md` | dg-* 组件代码示例 |