draftgo-cli 1.0.0 → 1.1.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.
package/README.md CHANGED
@@ -35,7 +35,7 @@ draftgo init all # 所有支持的工具
35
35
  | 命令 | 说明 |
36
36
  |---|---|
37
37
  | `draftgo init [target]...` | 安装 skill。不传 target 时自动识别;`all` 表示全部。 |
38
- | `draftgo update [target]...` | 覆盖 skill 内容到 CLI 内置版本,同时刷新已安装的入口;保留本地 `config / iteration / bugs / pages` 等运行时数据。 |
38
+ | `draftgo update [target]...` | 覆盖 skill 内容到 CLI 内置版本,同时刷新已安装的入口;保留本地 `config / iteration / bugs / pages` 等运行时数据。**若 npm 上存在更新的 CLI 版本,会先自动升级 CLI 再重跑自己**。 |
39
39
  | `draftgo uninstall [target]...` | 移除指定 AI 工具的入口文件;全量卸载时同时删除 `.draftgo/skill/`。加 `--purge` 会连 `.draftgo/` 整个删掉。 |
40
40
  | `draftgo status` | 查看当前项目装了哪些 AI 工具入口、skill 版本。 |
41
41
  | `draftgo doctor` | 诊断:Python 是否可用、检测到哪些 AI 工具、各入口状态。 |
@@ -47,8 +47,25 @@ draftgo init all # 所有支持的工具
47
47
 
48
48
  - `--project <dir>`:对指定目录操作(默认当前工作目录)。
49
49
  - `--force`:`init/update` 时覆盖已存在的 `.draftgo/skill/`。
50
+ - `--skip-update-check`:`update` 时不去 npm 查最新版,直接用当前 CLI 执行。
50
51
  - `--purge`:`uninstall` 时连 `.draftgo/`(含 config / 日志 / 本地缓存)一起删。
51
52
 
53
+ 可以通过环境变量 `DRAFTGO_NO_UPDATE_CHECK=1` 全局关闭自动升级检查(离线、CI 等场景)。
54
+
55
+ ## 更新
56
+
57
+ ```bash
58
+ cd <your-project>
59
+ draftgo update
60
+ ```
61
+
62
+ 就这一条命令。`update` 会自己去 npm 查最新版:
63
+
64
+ - 如果 CLI 已是最新 → 直接把内置 skill 资源覆盖到 `.draftgo/skill/`
65
+ - 如果 CLI 过时 → 自动执行 `npm install -g draftgo-cli@latest`,然后用新 CLI 重跑一遍 `update`
66
+
67
+ 整个过程中,你本地的 `.draftgo/config.json`、`iteration/`、`bugs/`、`pages/` 等运行时数据都不会被动到。
68
+
52
69
  ## 自动识别的依据
53
70
 
54
71
  | AI 工具 | 探测信号(任一命中即视为在用) |
@@ -73,7 +90,6 @@ draftgo init all # 所有支持的工具
73
90
  ├── .draftgo/
74
91
  │ ├── skill/ # CLI 管理,不要手动改
75
92
  │ │ ├── SKILL.md
76
- │ │ ├── CLAUDE.md
77
93
  │ │ ├── init/SKILL.md
78
94
  │ │ ├── sync/SKILL.md
79
95
  │ │ ├── bug/SKILL.md
package/bin/draftgo.js CHANGED
@@ -1,3 +1,3 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  'use strict';
3
3
  require('../src/index.js').run(process.argv.slice(2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "draftgo-cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Install and manage the DraftGo skill across AI coding agents (Claude Code, Codex, Cursor, Windsurf, Antigravity, Copilot, Gemini, Kiro).",
5
5
  "bin": {
6
6
  "draftgo": "bin/draftgo.js"
@@ -15,21 +15,337 @@ version: 1.0.0
15
15
  | 用户意图 | 执行 |
16
16
  |---|---|
17
17
  | 初始化项目 / 连接服务器 / `/draftgo init` | 调用 `/draftgo init` Skill |
18
- | 同步页面或导航 / `/draftgo sync` | 调用 `/draftgo sync` Skill |
19
- | 开发页面 / 修改页面 / 新建导航 | 读取 `CLAUDE.md` + `.draftgo/rules/frontend.md` + `.draftgo/rules/debugging-syntax.md` 后开始开发,**完成后必须:1) 写迭代记录 2) 自动同步到服务器**(见下方规范) |
18
+ | 同步页面、导航或数据库 / `/draftgo sync` | 调用 `/draftgo sync` Skill |
19
+ | 开发页面 / 修改页面 / 新建导航 | 读取本文件下方"架构认知"及 `.draftgo/rules/frontend.md` + `.draftgo/rules/debugging-syntax.md` 后开始开发,**完成后必须:1) 写迭代记录 2) 自动同步到服务器**(见下方规范) |
20
20
 
21
21
  ## 开发前置检查
22
22
 
23
- 进行任何开发任务前,先确认:
23
+ 进行任何开发任务前,先确认 `.draftgo/config.json` 存在:
24
24
 
25
25
  ```
26
- !test -f CLAUDE.md && echo "OK" || echo "请先运行 /draftgo init"
26
+ !test -f .draftgo/config.json && echo "OK" || echo "请先运行 /draftgo init"
27
27
  ```
28
28
 
29
- `CLAUDE.md` 存在后,读取它获取页面列表、导航列表、服务器地址等上下文。
29
+ 存在后,从 `.draftgo/config.json` 读取服务器地址和 token。
30
30
 
31
31
  开发规范读取:`.draftgo/rules/frontend.md`
32
32
 
33
+ ---
34
+
35
+ ## 架构认知(必读)
36
+
37
+ **DraftGo 不是传统 SPA,是"数据库驱动的页面资产运行时":**
38
+ - 壳层(Vue 3 + Vite)只负责 runtime 编排,源码在 `frontend/src/core/`
39
+ - 业务页面 HTML 存在数据库 `page.value.html`,运行在 `iframe.srcdoc`
40
+ - 导航栏 HTML 存在数据库 `navigation.html`,由壳层按需加载
41
+ - 页面与壳层通过 `window.parent.App` API 通信
42
+
43
+ **技术栈:**
44
+ - 后端:FastAPI 0.115.0 + SQLAlchemy 2.0.36 + Pydantic 2.10.0 + MySQL + Redis
45
+ - 前端:Vue 3 + Vite + Tailwind CSS(本地 `/assets/tailwindcss.js`)
46
+
47
+ ## 连接信息
48
+
49
+ - 服务器地址与 Token:存储于 `.draftgo/config.json`(已加入 .gitignore)
50
+ - 运行 `/draftgo init` 时写入
51
+
52
+ ## 开发规范
53
+
54
+ → 前端页面开发:读取 `.draftgo/rules/frontend.md`
55
+ → 页面排错指南:读取 `.draftgo/rules/debugging-syntax.md`(页面功能不执行时必读!)
56
+ - 任何开发,必读!
57
+
58
+ ## 开发禁区(违反必报错)
59
+
60
+ - ❌ 禁止境外 CDN(googleapis/jsdelivr/cdnjs/unpkg)→ ✅ 允许国内镜像(npmmirror.com/staticfile.net)
61
+ - ❌ 禁止 `App()` 写法(App 是对象不是函数)→ ✅ 正确:`const App = window.parent?.App`
62
+ - ❌ 禁止 `const App = () => window.parent?.App` → ✅ 去掉箭头函数
63
+ - ❌ 禁止 `window.location.search` 读参数 → ✅ 用 `window.__DG_ROUTE_CONTEXT__.query`
64
+ - ❌ 禁止 `App?.user?.role` 判断管理员 → `App.user` 不存在!✅ 用 `App.isAdmin` 或 `App.currentUser?.role_code`
65
+ - ❌ 禁止 `navigate('/login')` 退出 → ✅ 用 `window.location.href = '/login'`
66
+ - ❌ 禁止 `window.alert/confirm/prompt` → ✅ 用 `App.toast()` / `App.confirm()` / `App.showModal()`
67
+ - `App.showSuccess(msg)` 成功提示(绿色 3s)
68
+ - `App.showError(msg)` 错误提示(红色 4s)
69
+ - `App.showWarning(msg)` 警告提示(黄色 3s)
70
+ - `App.showInfo(msg)` 信息提示(蓝色 3s)
71
+ - `App.toast(msg, type)` 通用方法 — type: success/error/warning/info
72
+ - `App.confirm(msg, title?)` 确认弹窗 — 返回 Promise\<boolean\>
73
+ - `App.showModal(msg, title?)` 信息模态框 — 替代 alert()
74
+ - `App.showLoading()` / `App.hideLoading()` — 全局 loading
75
+ - `App.callApi(code, options?)` — 调用已注册的外部 API(Promise→`{status_code, headers, body, duration_ms, error}`)
76
+ - `App.listApis()` — 列出当前用户可调用的外部 API(含 code / name / method / 各 JSON Schema)
77
+ - 详见 `.draftgo/rules/frontend.md` App API 章节
78
+ - ⚠️ 尽量避免硬编码颜色 → 优先用 `var(--dg-accent)` 等 token 以适配主题切换
79
+
80
+ ## 主题切换适配
81
+
82
+ DraftGo 支持多套配色方案 + 亮暗模式,通过 CSS 变量实现。
83
+
84
+ **概念分层:**
85
+ | 概念 | 存储键 | 取值 |
86
+ |---|---|---|
87
+ | 显示模式 | `dg_theme` | `'light'` \| `'dark'` \| `'system'` |
88
+ | 配色方案 | `dg_color_scheme` | `'dark-gray-white'` \| `'deep-blue-white'` \| `'orange-white'` \| `'custom'` |
89
+
90
+ **内置配色方案:**
91
+ - `dark-gray-white` 深灰白(默认)— 冷调深灰,干净利落
92
+ - `deep-blue-white` 深蓝白 — 专业稳重,企业风
93
+ - `orange-white` 橙白 — 温暖醒目,创意风
94
+
95
+ **颜色 Token(优先使用):**
96
+ - `--dg-bg-base` / `--dg-bg-page` / `--dg-bg-surface` — 背景层级
97
+ - `--dg-text-primary` / `--dg-text-secondary` / `--dg-text-muted` — 文字层级
98
+ - `--dg-accent` / `--dg-accent-hover` / `--dg-accent-subtle` — 主题色
99
+ - `--dg-border` / `--dg-success` / `--dg-error` / `--dg-warning` — 功能色
100
+
101
+ **App API 切换配色:**
102
+ ```javascript
103
+ App.setColorScheme('deep-blue-white'); // 切换为预设方案
104
+ App.setColorScheme('custom', customVarsObject); // 应用自定义配色
105
+ ```
106
+
107
+ **硬编码颜色的场景(允许但需注意):**
108
+ - 品牌色(如 Logo 固定色)
109
+ - 数据可视化图表(需保证对比度)
110
+ - 第三方组件库强制要求
111
+
112
+ **硬编码时的要求:**
113
+ - 同时提供亮色/暗色两套值,通过 `[data-theme="dark"]` 选择器切换
114
+ - 确保对比度符合 WCAG AA 标准(文字至少 4.5:1)
115
+
116
+ ## 本地开发流程
117
+
118
+ **原则:有功能修改就读本地数据,自主决策是否调用 API 完成高质量修改。**
119
+
120
+ 1. **开发时**:读写 `.draftgo/` 本地文件
121
+ - 查元数据:读 `index.json`
122
+ - 查/改 HTML:按 `html_file` 字段路径读对应 `.html` 文件
123
+ 2. **修改后**:运行同步脚本推送到服务器(见下方"同步规范")
124
+ 3. **同步成功**:本地与服务器数据一致
125
+
126
+ **本地数据目录(均在 `.draftgo/` 下):**
127
+
128
+ | 目录 | 内容 |
129
+ |---|---|
130
+ | pages/ | index.json(无 html)+ 若干 .html |
131
+ | navigations/ | index.json(无 html)+ 若干 .html |
132
+ | roles/ | index.json |
133
+ | users/ | index.json |
134
+ | db_meta/ | index.json |
135
+ | aihub/ | index.json |
136
+ | external_apis/ | index.json(已注册的外部 API 元信息,`auth_config` 已脱敏) |
137
+ | system_config/ | index.json |
138
+ | iteration/ | YYYY-MM-DD.txt(迭代日志) |
139
+ | error/ | YYYY-MM-DD.txt(错误日志) |
140
+
141
+ ## 数据库 Schema(关键字段)
142
+
143
+ | 表 | 关键字段 | 说明 |
144
+ |---|---|---|
145
+ | `page` | `id`, `title`, `route`, `permission`, `value` (JSON: `{html: "..."}`) | value.html 存页面完整 HTML |
146
+ | `navigation` | `id`, `code`, `name`, `html`, `order`, `status` | html 字段存导航栏完整 HTML |
147
+ | `user` | `id`, `username`, `email`, `role_id`, `status` | role_id 关联 role 表 |
148
+ | `role` | `id`, `code`, `name`, `permissions` (JSON) | admin 角色拥有所有权限 |
149
+ | `db_meta` | `id`, `type`, `fields` (JSON: `[{name, type, required}]`) | 动态表结构定义 |
150
+ | `db` | `id`, `type`, `data` (JSON), `userid`, `status`, `created_at`, `updated_at` | 动态数据存储(含自动时间戳) |
151
+ | `aihub` | `id`, `name`, `type`, `config` (JSON), `status` | AI 服务配置 |
152
+ | `sys_config` | `config_key`, `config_value`, `value_type`, `category`, `is_sensitive` | 系统配置 KV |
153
+
154
+ ## API 速查与示例
155
+
156
+ | 模块 | 端点 |
157
+ |---|---|
158
+ | 认证 | POST /api/auth/login, /register, /logout, /refresh, /forgot-password, /reset-password |
159
+ | 微信认证 | POST /api/auth/wechat/mp/oauth, /wechat/mini/login, /wechat/mp/qr/create · GET /wechat/mp/qr/poll |
160
+ | 用户 | GET/PUT /api/users/me · GET/POST /api/users · GET/PUT/DELETE /users/{id} · POST /users/{id}/ban, /unban |
161
+ | 角色 | GET/POST /api/roles · GET/PUT/DELETE /roles/{id} · POST /roles/{id}/assign/{uid} · DELETE /roles/{id}/revoke/{uid} |
162
+ | 页面 | GET/POST /api/pages/ · GET/PUT/DELETE /pages/{id} · POST /pages/{id}/reset-system · GET /pages/by-route · GET /pages/public/{route} |
163
+ | 导航栏 | GET/POST /api/navigations · GET /navigations/{code} · PUT/DELETE /navigations/{id} · POST /navigations/{id}/reset-system |
164
+ | 系统配置 | GET /api/system/config · GET /system/category/{category} · GET/PUT/DELETE /system/{key} · POST /system/ · POST /system/config/ensure |
165
+ | 备份恢复(基础) | GET/POST /api/system/backup · POST /system/restore · POST /system/reset · POST /system/backup/selective · POST /system/restore/selective?mode=replace\|merge\|append · GET /system/backup/logs |
166
+ | 备份恢复(增强) | 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(只读预览/预演)· GET /system/storage/health, POST /system/storage/cleanup-orphans(存储健康)· POST /system/backup/logs/{id}/restore, /undo(回溯/撤销) |
167
+ | ⚠️ 二次密码 | restore / reset / undo / cleanup-orphans / package restore 都需要先 POST `/auth/reauth { password, scope }` 拿一次性 `confirm_token`,在请求头加 `X-Confirm-Token: <token>` 才能调用。SAT 调用方自动豁免。 |
168
+ | 通知公告 | GET/POST /api/notices · GET/PUT/DELETE /notices/{id} |
169
+ | 反馈 | GET/POST /api/feedback · GET /feedback/updates · GET/PUT/DELETE /feedback/{id} · DELETE /feedback/batch |
170
+ | 日志 | GET /api/logs · GET /logs/{id} |
171
+ | 智能体 | GET /api/agents · GET /agents/logs · POST /agents/{id}/chat · POST /agents/{id}/preview-chat |
172
+ | 动态DB | GET/POST /api/db/{type} · GET/PUT/DELETE /db/{type}/{id} |
173
+ | DB Meta | GET/POST /api/db-meta · GET /db-meta/{type} · PUT/DELETE /db-meta/{id} |
174
+ | ⚠️ DB Meta 查询 | GET /db-meta/{type} 用 **type**(如 `patient_profile`),不是 id。用 id 查会返回 "元数据不存在"。PUT/DELETE 才用 id。 |
175
+ | AIHub | GET/POST /api/aihub · GET/PUT/DELETE /aihub/{id} · PATCH /aihub/{id}/basic · POST /aihub/{id}/sync · POST /aihub/discover · GET /aihub/types |
176
+ | AI推理 | GET /api/v1/models · POST /v1/chat/completions |
177
+ | 外部 API(页面调用端) | GET /api/external-apis/available · POST /api/external-apis/call/{code}<br>页面里**优先使用 `App.callApi(code, options)`**,不要直连这两个端点 |
178
+ | 外部 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 |
179
+ | 文件上传 | POST /api/upload |
180
+ | 通知测试 | POST /api/system/notifications/test-email, /test-sms · GET /system/notifications/logs |
181
+
182
+ ### 高频 API 示例
183
+
184
+ **创建页面(需 admin):**
185
+ ```
186
+ POST /api/pages/
187
+ Body: { "title": "新页面", "route": "/new", "permission": "public", "value": {"html": "<html>...</html>"} }
188
+ Response: { "id": 123, "title": "新页面", "route": "/new", ... }
189
+ ```
190
+
191
+ **更新页面 HTML(需 admin):**
192
+ ```
193
+ PUT /api/pages/123
194
+ Body: { "value": {"html": "<html>更新后...</html>"} }
195
+ ```
196
+
197
+ **重置系统页面(需 admin):**
198
+ ```
199
+ POST /api/pages/123/reset-system
200
+ Body: 空
201
+ 说明:从 backend/init/pages/ 重新加载该页面
202
+ ```
203
+
204
+ **创建动态数据(普通用户自动绑定 userid):**
205
+ ```
206
+ POST /api/db/patient
207
+ Body: { "data": {"name": "张三", "age": 30} }
208
+ Response: { "id": 456, "type": "patient", "data": {...}, "userid": 当前用户id, "status": 1, "created_at": "...", "updated_at": "..." }
209
+ ```
210
+
211
+ **查询动态数据(带分页):**
212
+ ```
213
+ GET /api/db/patient?page=1&page_size=20
214
+ Response: { "items": [...], "total": 100, "page": 1, "page_size": 20 }
215
+ ```
216
+
217
+ **调用外部 API(页面里):**
218
+ ```javascript
219
+ // 在页面 HTML 里:壳层会自动注入认证、做参数校验、写日志
220
+ const r = await App.callApi('weather-now', {
221
+ path_params: { city: 'beijing' }, // 替换 path 模板里的 {city}
222
+ query_params: { unit: 'metric' }, // ?unit=metric
223
+ // body: { ... }, // POST/PUT/PATCH 才生效
224
+ // headers: { 'X-Trace': 't1' }, // 不会覆盖后端注入的认证头
225
+ });
226
+ if (r.status_code >= 200 && r.status_code < 300) {
227
+ console.log(r.body); // JSON 自动解析
228
+ } else {
229
+ App.showError(`上游返回 ${r.status_code}`);
230
+ }
231
+ ```
232
+ - 不要在页面里写 API Key / Bearer Token,由管理员在「外部 API」管理页注册即可
233
+ - API code 不知道时先 `await App.listApis()` 查询
234
+ - `r.error` 仅在代理层错误时(超时 / 网络 / 校验失败)非空;上游业务错误请看 `r.status_code` + `r.body`
235
+
236
+ ## 用自然语言注册/管理外部 API(SAT 直连管理端)
237
+
238
+ > 当用户说"帮我接入和风天气"、"注册一个 OpenAI 兼容接口"、"把这个 API 加进来"等,**走管理端 API 注册**,不要让用户去后台手动配置。
239
+
240
+ ### 1. 先查本地缓存
241
+
242
+ 读 `.draftgo/external_apis/index.json` 确认是否已注册过相同 `code`。
243
+
244
+ ### 2. 调用管理端 API(用 SAT,需 admin)
245
+
246
+ 读 `.draftgo/config.json` 拿到 `server` + `token`,按下面字段构造 payload,调用 `POST /api/external-apis`:
247
+
248
+ ```json
249
+ {
250
+ "code": "weather-now",
251
+ "name": "和风天气-实时",
252
+ "base_url": "https://devapi.qweather.com",
253
+ "method": "GET",
254
+ "path": "/v7/weather/now",
255
+ "headers": {},
256
+ "auth_type": "api_key_query",
257
+ "auth_config": { "name": "key", "value": "用户提供的 KEY" },
258
+ "timeout_ms": 30000,
259
+ "permission": { "default": "login", "roles": [] },
260
+ "param_schema": {
261
+ "query": {
262
+ "type": "object",
263
+ "properties": { "location": { "type": "string" } },
264
+ "required": ["location"]
265
+ }
266
+ },
267
+ "tags": ["weather"],
268
+ "description": "实时天气查询"
269
+ }
270
+ ```
271
+
272
+ **字段约定:**
273
+ - `auth_type`:`none` / `api_key_header` / `api_key_query` / `bearer` / `basic`
274
+ - `api_key_header` / `api_key_query`:`auth_config = {name, value}`
275
+ - `bearer`:`auth_config = {token}`
276
+ - `basic`:`auth_config = {username, password}`
277
+ - `permission.default`:`public`(匿名)/ `login`(登录用户)/ `deny`(仅指定角色)
278
+ - `param_schema.{path,query,body}`:均为 JSON Schema Draft 2020-12,可省略
279
+ - `path` 支持 `{var}` 模板,对应 `param_schema.path` 中字段
280
+ - 认证字段由后端 AES-256-GCM 加密落库;后续 GET 详情会返回脱敏值
281
+
282
+ ### 3. 注册后立即测试 + 写迭代记录
283
+
284
+ ```
285
+ POST /api/external-apis/{id}/test
286
+ Body: { "path_params": {...}, "query_params": {...}, "body": ... }
287
+ 返回: { ok, status_code, duration_ms, error }
288
+ ```
289
+
290
+ 测试通过后写迭代记录:`[新增] 注册外部 API <code>`
291
+
292
+ ### 4. 常见管理操作
293
+
294
+ | 用户意图 | API 调用 |
295
+ |---|---|
296
+ | 列出所有 API | `GET /api/external-apis?page=1&page_size=50` |
297
+ | 改名/换地址/换 KEY | `PUT /api/external-apis/{id}`(仅传需要改的字段;不传 `auth_config` 则保留原 KEY) |
298
+ | 启用/禁用 | `PATCH /api/external-apis/{id}/status` body `{status:"active|disabled"}` |
299
+ | 删除 | `DELETE /api/external-apis/{id}` |
300
+ | 查看调用日志 | `GET /api/external-apis/logs?api_code=<code>&page=1&page_size=50` |
301
+ | 清理日志 | `POST /api/external-apis/logs/cleanup?retention_days=30` |
302
+ | 列出已用标签 | `GET /api/external-apis/tags` |
303
+
304
+ ### 5. 用户场景对话样例
305
+
306
+ - 用户:"给我接一个查天气的 API,KEY 是 xxx"
307
+ → AI 询问:服务商?请求方法?需要哪些参数?
308
+ → AI 调用 `POST /api/external-apis` 注册 → 调用 `/test` 验证 → 告知 code
309
+ → 用户在页面里直接 `App.callApi('weather-now', { query_params: { location: 'beijing' } })`
310
+
311
+ - 用户:"禁用 weather-now 这个接口"
312
+ → AI 先 `GET /api/external-apis?keyword=weather-now` 拿 id → `PATCH /{id}/status` → 写迭代记录
313
+
314
+ ## URL 参数读取
315
+
316
+ 页面运行在 `iframe.srcdoc` 中,`window.location.search` 不可靠。框架通过 `<script data-dg-route-bridge>` 注入路由上下文。
317
+
318
+ ```javascript
319
+ // 标准三阶回落(推荐)
320
+ const routeContext =
321
+ window.__DG_ROUTE_CONTEXT__
322
+ || window.__DG_GET_ROUTE_CONTEXT__?.()
323
+ || window.parent?.App?.getCurrentRouteContext?.()
324
+ || { query: {} };
325
+ const query = routeContext.query || {};
326
+
327
+ // 使用示例
328
+ const patientId = query.patientId; // "42"
329
+ const visitId = query.visitId; // "7"
330
+ ```
331
+
332
+ 禁止:`new URLSearchParams(window.location.search)` — iframe 中取不到壳层 URL。
333
+
334
+ ## 常见问题排查
335
+
336
+ | 问题 | 排查步骤 |
337
+ |---|---|
338
+ | 同步失败 | 1. 检查 `.draftgo/config.json` token 是否有效<br>2. 检查服务器连接<br>3. 查看 `.draftgo/error/YYYY-MM-DD.txt` 错误日志 |
339
+ | 页面加载空白 | 1. 检查 `permission` 字段与当前用户角色是否匹配<br>2. 检查路由是否正确(`/api/pages/by-route?route=/xxx`)<br>3. 浏览器控制台查看 JS 错误 |
340
+ | API 返回 401 | Token 过期,前端会自动用 refresh token 刷新,无需手动处理 |
341
+ | API 返回 403 | 权限不足,检查当前用户角色是否有对应权限 |
342
+ | DB Meta GET 返回"元数据不存在" | 你用了 id,应该用 **type**(如 `/api/db-meta/patient_profile`)。PUT/DELETE 才用 id。 |
343
+ | 颜色不生效 | 检查是否用了 `var(--dg-*)` 而非硬编码 hex/rgb |
344
+ | URL 参数读取失败 | 检查是否用了 `window.__DG_ROUTE_CONTEXT__.query` 而非 `window.location.search` |
345
+ | 退出登录后导航栏未更新 | 检查是否用了 `window.location.href = '/login'` 而非 `navigate('/login')` |
346
+
347
+ ---
348
+
33
349
  ## 迭代记录规范
34
350
 
35
351
  每次对系统进行开发/修复/修改/完善/删除/重构等操作后,**必须**在 `.draftgo/iteration/` 目录下按日期写入迭代记录。
@@ -57,19 +373,80 @@ version: 1.0.0
57
373
  3. 存在则 Read 后追加新行;不存在则直接 Write
58
374
  4. 无需告知用户(静默执行)
59
375
 
376
+ ## 错误日志规范
377
+
378
+ **遇到任何开发/运行时错误,必须自动追加到 `.draftgo/error/YYYY-MM-DD.txt`:**
379
+
380
+ 格式:`[HH:MM:SS] [错误类型] 描述`
381
+
382
+ 示例:
383
+ ```
384
+ [14:23:45] [同步失败] pages/123 同步到服务器失败:401 Unauthorized
385
+ [14:30:12] [API错误] GET /api/db/patient 返回 500:数据库连接超时
386
+ [15:10:33] [页面错误] admin-users.html 第 42 行:App is not a function
387
+ ```
388
+
389
+ 操作流程:
390
+ 1. 捕获错误后,获取当前日期和时间
391
+ 2. 检查 `.draftgo/error/YYYY-MM-DD.txt` 是否存在
392
+ 3. 存在则 Read 后追加新行;不存在则直接 Write
393
+ 4. 无需告知用户(静默执行)
394
+
395
+ ---
396
+
60
397
  ## 自动同步规范
61
398
 
62
399
  > **每次修改页面或导航栏 HTML 后,必须立即自动同步到服务器。这是强制步骤,不得跳过,不得等用户提醒。**
63
400
 
401
+ ### 同步支持的类型
402
+
403
+ 同步脚本覆盖 init 拉取的全部 8 种类型:
404
+
405
+ | 类型 | 命令 | 说明 |
406
+ |---|---|---|
407
+ | pages | `python <skill_scripts>/draftgo_sync.py pages [page_id]` | 页面同步 |
408
+ | nav | `python <skill_scripts>/draftgo_sync.py nav [nav_id]` | 导航栏同步 |
409
+ | db_meta | `python <skill_scripts>/draftgo_sync.py db_meta [id]` | 数据库元数据同步 |
410
+ | aihub | `python <skill_scripts>/draftgo_sync.py aihub [id]` | AI 资产同步 |
411
+ | external_apis | `python <skill_scripts>/draftgo_sync.py external_apis [id]` | 外部 API 注册同步 |
412
+ | system_config | `python <skill_scripts>/draftgo_sync.py system_config [config_key]` | 系统配置同步 |
413
+ | roles | `python <skill_scripts>/draftgo_sync.py roles [id]` | ⚠️ 需向用户二次确认 |
414
+ | users | `python <skill_scripts>/draftgo_sync.py users [id]` | ⚠️ 需向用户二次确认(不下发 password / role_ids) |
415
+
416
+ > `<skill_scripts>` 为同步脚本实际路径,取决于安装方式(`.draftgo/skill/scripts/` 或 `.claude/skills/draftgo/scripts/`)。
417
+
64
418
  ### 同步规则
65
419
 
66
- - 修改了哪个页面的 HTML → 同步该页面:`python .draftgo/skill/scripts/draftgo_sync.py pages <page_id>`
67
- - 修改了哪个导航栏的 HTML → 同步该导航:`python .draftgo/skill/scripts/draftgo_sync.py nav <nav_id>`
420
+ - 修改了哪个页面的 HTML → 同步该页面:`python <skill_scripts>/draftgo_sync.py pages <page_id>`
421
+ - 修改了哪个导航栏的 HTML → 同步该导航:`python <skill_scripts>/draftgo_sync.py nav <nav_id>`
422
+ - 修改了 DB Meta / AI 资产 / 外部 API / 系统配置 → 用对应模式同步:`python <skill_scripts>/draftgo_sync.py <mode> <id>`
423
+ - 修改了 roles/users → **必须先向用户确认**,确认后再运行 `roles` / `users` 模式
68
424
  - 同时修改多个 → 逐个同步
69
425
  - 同步失败时告知用户,不得静默忽略
70
426
 
427
+ ### ⚠️ roles / users 同步注意事项
428
+
429
+ roles 和 users 涉及权限与账号安全,虽然脚本已支持,**仍必须人工确认后执行**:
430
+ 1. 告知用户即将同步的变更内容(角色名/权限变更 或 用户信息变更)
431
+ 2. **等待用户明确确认**后,再运行 `draftgo_sync.py roles` / `users`
432
+ 3. 用户拒绝则不同步,仅保留本地修改
433
+
71
434
  ### 完整操作顺序(每次开发任务结束时)
72
435
 
73
436
  1. 写迭代记录(静默)
74
- 2. 运行同步脚本(必须执行)
437
+ 2. 运行同步脚本 / 调用 API(必须执行,roles/users 需二次确认)
75
438
  3. 告知用户同步结果
439
+
440
+ ---
441
+
442
+ ## 同步命令快捷入口
443
+
444
+ - `/draftgo sync pages` — 触发 sync skill
445
+ - `/draftgo sync nav` — 触发 sync skill
446
+ - `/draftgo sync db_meta` — 触发 sync skill
447
+ - `/draftgo sync aihub` — 触发 sync skill
448
+ - `/draftgo sync external_apis` — 触发 sync skill
449
+ - `/draftgo sync system_config` — 触发 sync skill
450
+ - `/draftgo sync roles` — 触发 sync skill(需人工确认)
451
+ - `/draftgo sync users` — 触发 sync skill(需人工确认)
452
+ - `/draftgo init` — 触发 init skill
@@ -38,12 +38,12 @@ allowed-tools: Bash(python:*), Bash(find:*), Read
38
38
 
39
39
  ---
40
40
 
41
- ## 第三步:读取结果
41
+ ## 第三步:确认结果
42
42
 
43
- 脚本执行完毕后,只读取 `CLAUDE.md` 确认生成成功:
43
+ 脚本执行完毕后,确认 `.draftgo/config.json` 生成成功:
44
44
 
45
45
  ```
46
- !test -f CLAUDE.md && echo "OK" || echo "FAIL"
46
+ !test -f .draftgo/config.json && echo "OK" || echo "FAIL"
47
47
  ```
48
48
 
49
49
  ---
@@ -58,6 +58,9 @@ allowed-tools: Bash(python:*), Bash(find:*), Read
58
58
 
59
59
  ## 完成提示
60
60
 
61
- 将脚本的输出摘要(页面数、导航数、服务器地址)告知用户,并提示:
61
+ 将脚本的输出摘要(页面数、导航数、外部 API 数、服务器地址)告知用户,并提示:
62
62
  - 下一步:描述要开发的功能,或运行 `/draftgo sync` 同步修改
63
63
  - 如需刷新数据:重新运行 `/draftgo init`
64
+ - 已拉取的外部 API 列表存于 `.draftgo/external_apis/index.json`(`auth_config` 已脱敏,仅供 AI 了解可调用 API;如需新增/修改请走管理端 API,详见根 SKILL.md "用自然语言注册/管理外部 API")
65
+
66
+ > 注意:拉取 `external_apis` 需要 SAT 具备 admin 权限。若该项返回 0 条且用户期望应有数据,提醒用户检查 token 角色。
@@ -253,10 +253,29 @@ App.showLoading(); await someAsyncOp(); App.hideLoading();
253
253
 
254
254
  // 文件上传
255
255
  const result = await App.uploadFile(file, progress => console.log(progress));
256
+
257
+ // 外部 API 调用(已注册到「外部 API 接入」)
258
+ // 认证由后端注入;不要在页面里写 API Key / Bearer
259
+ const r = await App.callApi('weather-now', {
260
+ path_params: { city: 'beijing' }, // 替换 path 模板里的 {city}
261
+ query_params: { unit: 'metric' },
262
+ // body: { ... }, // POST/PUT/PATCH 才生效
263
+ // headers: { 'X-Trace': 't1' }, // 不会覆盖后端注入的认证头
264
+ });
265
+ // 返回:{ status_code, headers, body, duration_ms, error }
266
+ // body 已按 Content-Type 自动解析;上游业务错误看 status_code,不是 error
267
+
268
+ // 列出当前用户可调用的外部 API(含 code / name / 各 JSON Schema)
269
+ const apis = await App.listApis();
256
270
  ```
257
271
 
258
272
  路径解析:`https://...` 直接请求 · `/api/...` 拼接 origin · 其他相对路径拼接 apiBase
259
273
 
274
+ **外部 API 注意事项:**
275
+ - code 不知道时先 `await App.listApis()` 查询
276
+ - `r.error` 仅在代理层错误(超时 / 网络 / 校验失败)时非空
277
+ - API 未注册 / 已禁用 / 无权限 → 后端以 4xx 抛错,会被 `App.callApi` 当成异常 throw
278
+
260
279
  ---
261
280
 
262
281
  ## 颜色 Token(强制)
@@ -104,14 +104,7 @@ def explode_html_field(out_dir, raw, html_field, fname_prefix, rel_prefix):
104
104
  return len(index)
105
105
 
106
106
 
107
- def build_claude_md(server, script_dir):
108
- """复制 skill 的 CLAUDE.md 模板,仅替换服务器地址。"""
109
- template_path = script_dir.parent / "CLAUDE.md"
110
- if not template_path.exists():
111
- return f"# DraftGo 项目上下文\n\n> 由 draftgo-init 自动生成。\n\n## 连接信息\n- 服务器:{server}\n"
112
- content = template_path.read_text(encoding="utf-8")
113
- content = content.replace("https://dev.draftgo.cn", server)
114
- return content
107
+
115
108
 
116
109
 
117
110
  def main():
@@ -150,6 +143,7 @@ def main():
150
143
  "users": "/api/users?page=1&page_size=500",
151
144
  "db_meta": "/api/db-meta",
152
145
  "aihub": "/api/aihub",
146
+ "external_apis": "/api/external-apis?page=1&page_size=500",
153
147
  "system_config": "/api/system/config",
154
148
  }
155
149
  raw = {}
@@ -172,8 +166,17 @@ def main():
172
166
  )
173
167
  print(f" OK .draftgo/navigations/ ({counts['navigations']} navs)")
174
168
 
175
- for name in ("roles", "users", "db_meta", "aihub", "system_config"):
169
+ for name in ("roles", "users", "db_meta", "aihub", "external_apis", "system_config"):
176
170
  items = extract_items(raw[name])
171
+ if name == "external_apis" and items:
172
+ # 列表接口仅返回 summary,缺 param_schema/headers/auth_config;
173
+ # 逐个 GET 详情合并,确保 AI 上下文包含调用所需 schema。
174
+ hydrated = []
175
+ for it in items:
176
+ api_id = it.get("id")
177
+ detail = fetch(server, token, f"/api/external-apis/{api_id}") if api_id else None
178
+ hydrated.append({**it, **detail} if isinstance(detail, dict) else it)
179
+ items = hydrated
177
180
  write_index(dg_dir / name, items)
178
181
  counts[name] = len(items)
179
182
  print(f" OK .draftgo/{name}/ ({counts[name]} items)")
@@ -184,7 +187,6 @@ def main():
184
187
 
185
188
  # rules 已经由 CLI 安装到 .draftgo/skill/rules/,这里不再重复写入。
186
189
 
187
- (root / "CLAUDE.md").write_text(build_claude_md(server, SCRIPT_DIR), encoding="utf-8")
188
190
  print(f"\nDone: {counts['pages']} pages, {counts['navigations']} navs, server: {server}")
189
191
 
190
192