cckit 0.3.0__tar.gz → 0.3.1__tar.gz

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.
Files changed (134) hide show
  1. {cckit-0.3.0 → cckit-0.3.1}/Docs/05-design-log.md +6 -3
  2. {cckit-0.3.0 → cckit-0.3.1}/Docs/11-web-panel.md +22 -1
  3. {cckit-0.3.0 → cckit-0.3.1}/Docs/README.md +11 -2
  4. {cckit-0.3.0 → cckit-0.3.1}/Docs/cli-spec.md +21 -0
  5. {cckit-0.3.0 → cckit-0.3.1}/PKG-INFO +6 -2
  6. {cckit-0.3.0 → cckit-0.3.1}/README.md +5 -1
  7. cckit-0.3.1/hatch_build.py +34 -0
  8. {cckit-0.3.0 → cckit-0.3.1}/pyproject.toml +1 -1
  9. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/__init__.py +1 -1
  10. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/alt.py +15 -5
  11. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/cli.py +3 -1
  12. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/web/app.py +49 -8
  13. cckit-0.3.1/tests/test_web_base.py +51 -0
  14. {cckit-0.3.0 → cckit-0.3.1}/uv.lock +1 -1
  15. cckit-0.3.0/web/dist/assets/index-Cm-V9D5_.js → cckit-0.3.1/web/dist/assets/index-CSuOYCbo.js +15 -15
  16. cckit-0.3.0/web/dist/assets/index-RaOeRefK.css → cckit-0.3.1/web/dist/assets/index-Cp1Xoa9J.css +1 -1
  17. {cckit-0.3.0 → cckit-0.3.1}/web/dist/index.html +13 -13
  18. {cckit-0.3.0 → cckit-0.3.1}/web/index.html +12 -12
  19. {cckit-0.3.0 → cckit-0.3.1}/web/package-lock.json +6233 -6233
  20. {cckit-0.3.0 → cckit-0.3.1}/web/package.json +38 -38
  21. {cckit-0.3.0 → cckit-0.3.1}/web/src/components/AddKitDialog.tsx +10 -3
  22. {cckit-0.3.0 → cckit-0.3.1}/web/src/components/AppSidebar.tsx +1 -1
  23. {cckit-0.3.0 → cckit-0.3.1}/web/src/lib/api.ts +27 -16
  24. {cckit-0.3.0 → cckit-0.3.1}/web/tsconfig.node.tsbuildinfo +1 -1
  25. {cckit-0.3.0 → cckit-0.3.1}/web/vite.config.js +3 -0
  26. {cckit-0.3.0 → cckit-0.3.1}/web/vite.config.ts +26 -23
  27. cckit-0.3.0/hatch_build.py +0 -27
  28. {cckit-0.3.0 → cckit-0.3.1}/.gitignore +0 -0
  29. {cckit-0.3.0 → cckit-0.3.1}/Docs/.gitignore +0 -0
  30. {cckit-0.3.0 → cckit-0.3.1}/Docs/01-overview.md +0 -0
  31. {cckit-0.3.0 → cckit-0.3.1}/Docs/02-architecture.md +0 -0
  32. {cckit-0.3.0 → cckit-0.3.1}/Docs/03-manifest-spec.md +0 -0
  33. {cckit-0.3.0 → cckit-0.3.1}/Docs/04-cli-spec.md +0 -0
  34. {cckit-0.3.0 → cckit-0.3.1}/Docs/06-platform-findings.md +0 -0
  35. {cckit-0.3.0 → cckit-0.3.1}/Docs/07-security.md +0 -0
  36. {cckit-0.3.0 → cckit-0.3.1}/Docs/08-installation.md +0 -0
  37. {cckit-0.3.0 → cckit-0.3.1}/Docs/09-state-api.md +0 -0
  38. {cckit-0.3.0 → cckit-0.3.1}/Docs/10-kit-authoring.md +0 -0
  39. {cckit-0.3.0 → cckit-0.3.1}/Docs/examples/video-toolkit/cckit.yaml +0 -0
  40. {cckit-0.3.0 → cckit-0.3.1}/Docs/kit-development.md +0 -0
  41. {cckit-0.3.0 → cckit-0.3.1}/Docs/pics/architecture.md +0 -0
  42. {cckit-0.3.0 → cckit-0.3.1}/Docs/pics/directory-layout.md +0 -0
  43. {cckit-0.3.0 → cckit-0.3.1}/Docs/pics/four-state-model.md +0 -0
  44. {cckit-0.3.0 → cckit-0.3.1}/Docs/pics/install-flow.md +0 -0
  45. {cckit-0.3.0 → cckit-0.3.1}/Docs/pics/pain-points-solution.md +0 -0
  46. {cckit-0.3.0 → cckit-0.3.1}/Docs/pics/state-transitions.md +0 -0
  47. {cckit-0.3.0 → cckit-0.3.1}/Docs/schema/cckit.schema.json +0 -0
  48. {cckit-0.3.0 → cckit-0.3.1}/Docs/test/hello-skill/SKILL.md +0 -0
  49. {cckit-0.3.0 → cckit-0.3.1}/Docs/test/test-skill-dependency/.gitignore +0 -0
  50. {cckit-0.3.0 → cckit-0.3.1}/Docs/test/test-skill-dependency/README.md +0 -0
  51. {cckit-0.3.0 → cckit-0.3.1}/Docs/test/test-skill-dependency/json-pretty/SKILL.md +0 -0
  52. {cckit-0.3.0 → cckit-0.3.1}/Docs/test/test-skill-dependency/json-pretty/scripts/pretty.js +0 -0
  53. {cckit-0.3.0 → cckit-0.3.1}/Docs/test/test-skill-dependency/package.json +0 -0
  54. {cckit-0.3.0 → cckit-0.3.1}/Docs/test/test-skill-dependency/plain-advisor/SKILL.md +0 -0
  55. {cckit-0.3.0 → cckit-0.3.1}/Docs/test/test-skill-dependency/requirements.txt +0 -0
  56. {cckit-0.3.0 → cckit-0.3.1}/Docs/test/test-skill-dependency/test-skill-dependency/SKILL.md +0 -0
  57. {cckit-0.3.0 → cckit-0.3.1}/Docs/test/test-skill-dependency/test-skill-dependency/scripts/test.py +0 -0
  58. {cckit-0.3.0 → cckit-0.3.1}/LICENSE +0 -0
  59. {cckit-0.3.0 → cckit-0.3.1}/skills/kit-builder/cckit.yaml +0 -0
  60. {cckit-0.3.0 → cckit-0.3.1}/skills/kit-builder/kit-builder/SKILL.md +0 -0
  61. {cckit-0.3.0 → cckit-0.3.1}/skills/kit-builder/kit-builder/kit-authoring.md +0 -0
  62. {cckit-0.3.0 → cckit-0.3.1}/skills/kit-builder/kit-builder/manifest-spec.md +0 -0
  63. {cckit-0.3.0 → cckit-0.3.1}/skills/kit-builder/kit-builder/templates/SKILL.md +0 -0
  64. {cckit-0.3.0 → cckit-0.3.1}/skills/kit-builder/kit-builder/templates/cckit.yaml +0 -0
  65. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/__main__.py +0 -0
  66. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/cckit.schema.json +0 -0
  67. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/config.py +0 -0
  68. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/doctor.py +0 -0
  69. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/env.py +0 -0
  70. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/errors.py +0 -0
  71. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/exec.py +0 -0
  72. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/installer.py +0 -0
  73. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/link.py +0 -0
  74. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/lint.py +0 -0
  75. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/manifest.py +0 -0
  76. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/projects.py +0 -0
  77. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/registry.py +0 -0
  78. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/schema.py +0 -0
  79. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/state.py +0 -0
  80. {cckit-0.3.0 → cckit-0.3.1}/src/cckit/web/__init__.py +0 -0
  81. {cckit-0.3.0 → cckit-0.3.1}/tests/conftest.py +0 -0
  82. {cckit-0.3.0 → cckit-0.3.1}/tests/helpers.py +0 -0
  83. {cckit-0.3.0 → cckit-0.3.1}/tests/test_alt.py +0 -0
  84. {cckit-0.3.0 → cckit-0.3.1}/tests/test_budget.py +0 -0
  85. {cckit-0.3.0 → cckit-0.3.1}/tests/test_env.py +0 -0
  86. {cckit-0.3.0 → cckit-0.3.1}/tests/test_exec.py +0 -0
  87. {cckit-0.3.0 → cckit-0.3.1}/tests/test_installer.py +0 -0
  88. {cckit-0.3.0 → cckit-0.3.1}/tests/test_link.py +0 -0
  89. {cckit-0.3.0 → cckit-0.3.1}/tests/test_lint.py +0 -0
  90. {cckit-0.3.0 → cckit-0.3.1}/tests/test_manifest.py +0 -0
  91. {cckit-0.3.0 → cckit-0.3.1}/tests/test_registry.py +0 -0
  92. {cckit-0.3.0 → cckit-0.3.1}/tests/test_state.py +0 -0
  93. {cckit-0.3.0 → cckit-0.3.1}/tests/test_web_alt.py +0 -0
  94. {cckit-0.3.0 → cckit-0.3.1}/web/.gitignore +0 -0
  95. {cckit-0.3.0 → cckit-0.3.1}/web/components.json +0 -0
  96. {cckit-0.3.0 → cckit-0.3.1}/web/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  97. {cckit-0.3.0 → cckit-0.3.1}/web/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  98. {cckit-0.3.0 → cckit-0.3.1}/web/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  99. {cckit-0.3.0 → cckit-0.3.1}/web/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  100. {cckit-0.3.0 → cckit-0.3.1}/web/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  101. {cckit-0.3.0 → cckit-0.3.1}/web/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  102. {cckit-0.3.0 → cckit-0.3.1}/web/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  103. {cckit-0.3.0 → cckit-0.3.1}/web/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  104. {cckit-0.3.0 → cckit-0.3.1}/web/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  105. {cckit-0.3.0 → cckit-0.3.1}/web/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  106. {cckit-0.3.0 → cckit-0.3.1}/web/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  107. {cckit-0.3.0 → cckit-0.3.1}/web/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  108. {cckit-0.3.0 → cckit-0.3.1}/web/src/App.tsx +0 -0
  109. {cckit-0.3.0 → cckit-0.3.1}/web/src/components/AddScopeDialog.tsx +0 -0
  110. {cckit-0.3.0 → cckit-0.3.1}/web/src/components/DoctorDialog.tsx +0 -0
  111. {cckit-0.3.0 → cckit-0.3.1}/web/src/components/KitList.tsx +0 -0
  112. {cckit-0.3.0 → cckit-0.3.1}/web/src/components/ui/badge.tsx +0 -0
  113. {cckit-0.3.0 → cckit-0.3.1}/web/src/components/ui/button.tsx +0 -0
  114. {cckit-0.3.0 → cckit-0.3.1}/web/src/components/ui/card.tsx +0 -0
  115. {cckit-0.3.0 → cckit-0.3.1}/web/src/components/ui/dialog.tsx +0 -0
  116. {cckit-0.3.0 → cckit-0.3.1}/web/src/components/ui/dropdown-menu.tsx +0 -0
  117. {cckit-0.3.0 → cckit-0.3.1}/web/src/components/ui/input.tsx +0 -0
  118. {cckit-0.3.0 → cckit-0.3.1}/web/src/components/ui/popover.tsx +0 -0
  119. {cckit-0.3.0 → cckit-0.3.1}/web/src/components/ui/progress.tsx +0 -0
  120. {cckit-0.3.0 → cckit-0.3.1}/web/src/components/ui/separator.tsx +0 -0
  121. {cckit-0.3.0 → cckit-0.3.1}/web/src/components/ui/sheet.tsx +0 -0
  122. {cckit-0.3.0 → cckit-0.3.1}/web/src/components/ui/sidebar.tsx +0 -0
  123. {cckit-0.3.0 → cckit-0.3.1}/web/src/components/ui/skeleton.tsx +0 -0
  124. {cckit-0.3.0 → cckit-0.3.1}/web/src/components/ui/sonner.tsx +0 -0
  125. {cckit-0.3.0 → cckit-0.3.1}/web/src/components/ui/tooltip.tsx +0 -0
  126. {cckit-0.3.0 → cckit-0.3.1}/web/src/hooks/use-mobile.ts +0 -0
  127. {cckit-0.3.0 → cckit-0.3.1}/web/src/hooks/useSSE.ts +0 -0
  128. {cckit-0.3.0 → cckit-0.3.1}/web/src/index.css +0 -0
  129. {cckit-0.3.0 → cckit-0.3.1}/web/src/lib/utils.ts +0 -0
  130. {cckit-0.3.0 → cckit-0.3.1}/web/src/main.tsx +0 -0
  131. {cckit-0.3.0 → cckit-0.3.1}/web/tsconfig.json +0 -0
  132. {cckit-0.3.0 → cckit-0.3.1}/web/tsconfig.node.json +0 -0
  133. {cckit-0.3.0 → cckit-0.3.1}/web/tsconfig.tsbuildinfo +0 -0
  134. {cckit-0.3.0 → cckit-0.3.1}/web/vite.config.d.ts +0 -0
@@ -430,9 +430,12 @@ Web 把全局 skill 单独折叠一组、用独立覆盖选择器(不复用四
430
430
  - **Claude Code 只做改造与审计,不做安装。** 安装仍是 `stage_install →
431
431
  execute_install` 的确定性管线。审计是语义层补充,不可绕过的安全闸门仍是
432
432
  manifest 校验 + lint + 安装计划确认 + 回滚。
433
- - **`kit-builder` 的唯一可信来源是当前源码树。** 同名 Skill 在网络上不可依赖,
434
- 所以自动安装只能等价 `cckit add <abs>/skills/kit-builder --local` 到全局。
435
- 检测到同名冲突、来源冲突、项目作用域安装时一律停止,不静默覆盖。
433
+ - **`kit-builder` 的唯一可信来源是随 cckit 分发的 `./skills/kit-builder`。** 同名
434
+ Skill 在网络上不可依赖,所以自动安装只能等价 `cckit add <abs>/skills/kit-builder
435
+ --local` 到全局。该目录由 `alt.kit_builder_source_dir()` 在「源码树 → 随包
436
+ `cckit/skills/kit-builder`」两处候选里定位(后者由 hatch_build.py 打进 wheel),
437
+ 因而 `uv tool install` 后仍能安装。检测到同名冲突、来源冲突、项目作用域安装时
438
+ 一律停止,不静默覆盖。
436
439
  - **不伪造 SHA。** 改造后临时目录已非原始仓库状态,本地目录可能根本没有 Git。
437
440
  alt 最终按本地目录交给安装接口,registry 不记录改造后 SHA,只保留原始来源 URL/ref。
438
441
  - **权限用 `auto`,不设 `can_use_tool`、不用 `bypassPermissions`。** 一旦设了
@@ -1,6 +1,6 @@
1
1
  # Web 管理面板
2
2
 
3
- 配套 v0.3.0 的可选管理面板:平时展示 skill 列表,支持导入(add)、卸载(remove)、
3
+ 配套 v0.3.1 的可选管理面板:平时展示 skill 列表,支持导入(add)、卸载(remove)、
4
4
  四态开关切换、诊断(doctor)。**纯增量**——不装 `[web]` 额外依赖时,`cckit` 本体
5
5
  与命令行工具完全不变。
6
6
 
@@ -24,6 +24,7 @@
24
24
  | 提示 | sonner(shadcn 包装) | 四类 toast + 弹窗/toast 分工 |
25
25
  | 依赖隔离 | `[web]` 可选 extra | `uv tool install cckit` 仍是 3 依赖纯本体 |
26
26
  | 安全 | 后端默认 `127.0.0.1` | Web 能装 kit(跑作者代码)、能改状态,不放开监听 |
27
+ | 子路径部署 | `--base` 运行时挂载 + `window.__CCKIT_BASE__` 注入 + Vite `base: "./"` | 嵌入 dashboard 子路径时避免与宿主 `/api` 冲突,无需重建前端 |
27
28
 
28
29
  ## 架构
29
30
 
@@ -195,6 +196,24 @@ uv run cckit web --host 0.0.0.0 --port 8000 # 自动定位前端,缺省也
195
196
  不设 `frame-ancestors` CSP(Starlette 默认即可嵌)。HTTPS 宿主嵌 `http://<server>`
196
197
  需在目标浏览器实测(Safari 较严)。
197
198
 
199
+ **子路径部署(`--base`)**:当面板要挂到宿主 dashboard 的某个子路径(如 `/cckit/`)下,
200
+ 且不想与宿主页自身的 `/api` 冲突时,启动时指定根路径前缀:
201
+
202
+ ```bash
203
+ cckit web --host 0.0.0.0 --base /cckit
204
+ ```
205
+
206
+ 此时后端把整站(所有 `/api/*`、`/assets`、SPA fallback)整体 mount 到 `/cckit` 下,
207
+ 反向代理只需把 `/cckit/*` **原样转发**(无需 strip 前缀),宿主页
208
+ `<iframe src="http://<server>:<port>/cckit/">` 即可。实现要点:
209
+
210
+ - **运行时注入**:后端 serve 时把 `window.__CCKIT_BASE__` 注入 `index.html` 的 `<head>`,
211
+ 前端据此给 `/api/*` 拼前缀(SSE 同走 `fetch`,一并覆盖)。根部署(base 为空)不注入、
212
+ 行为与从前完全一致。
213
+ - **资源相对化**:前端 Vite 用 `base: "./"` 构建,`./assets/...` 与字体 `url(...)` 自动
214
+ 跟随当前子路径,因此**同一份产物**在根部署与任意子路径部署下都可用,`--base` 是纯运行
215
+ 时配置,无需重新构建前端。
216
+
198
217
  ## 验证
199
218
 
200
219
  - `uv run pytest -q` 82 项全绿(状态层 root 推广、installer 拆分后 CLI 行为不回归)。
@@ -203,3 +222,5 @@ uv run cckit web --host 0.0.0.0 --port 8000 # 自动定位前端,缺省也
203
222
  均可达,非法 API 路径返回 404 而非 index.html。
204
223
  - 纯净性:`uv build`(不先跑前端构建)产出的 wheel 不含前端,`[web]` 依赖也不随
205
224
  `uv tool install cckit` 安装;前端产物仅在前端 build 后才由 hatch 钩子打进 wheel。
225
+ `skills/kit-builder`(alt 的自举工具)则**始终**随 wheel 分发到 `cckit/skills/kit-builder`,
226
+ 与前端是否构建无关。
@@ -54,7 +54,7 @@
54
54
 
55
55
  ## 当前状态
56
56
 
57
- **v0.3.0 已实现**:`cckit` 命令行工具与全部 9 个命令(`add` / `list` / `enable` /
57
+ **v0.3.1 已实现**:`cckit` 命令行工具与全部 9 个命令(`add` / `list` / `enable` /
58
58
  `disable` / `name-only` / `remove` / `doctor` / `exec` / `web`),`uv run cckit --help` 可用。(0.2.0因为打包失误,版本号被弃用)
59
59
 
60
60
  已落地模块(`src/cckit/`):
@@ -72,7 +72,7 @@
72
72
  - `projects` —— 关注项目清单 projects.json 原子读写
73
73
  - `web` —— Web 管理面板后端(FastAPI,惰性 import,`[web]` 可选依赖)
74
74
 
75
- 测试:`tests/` 下 126 项,`uv run pytest -q` 全绿;`Docs/examples/video-toolkit` 作为夹具。
75
+ 测试:`tests/` 下 132 项,`uv run pytest -q` 全绿;`Docs/examples/video-toolkit` 作为夹具。
76
76
 
77
77
  ## v0.1 范围
78
78
 
@@ -93,6 +93,15 @@
93
93
  由 Claude Code(kit-builder)改造 + 审计后复用现有安装流程;标准仓库不碰 LLM。
94
94
  详见 [cli-spec](cli-spec.md)、[02](02-architecture.md) 的 alt 节、[05](05-design-log.md) 的 D-16。
95
95
 
96
+ ## v0.3.1 范围
97
+
98
+ - **Web 面板子路径部署(`cckit web --base /cckit`)**:整站运行时挂到指定根路径前缀下,
99
+ 便于把面板 iframe 嵌入宿主 dashboard 而不与宿主自身的 `/api` 冲突;`--base` 是运行时
100
+ 配置,无需重建前端(见 [11](11-web-panel.md))。
101
+ - **kit-builder 随 wheel 分发**:修复 `uv tool install 'cckit[web,alt]'` 后,Web 端勾选
102
+ 「允许导入非标准仓库」检测不到 `skills/kit-builder`、无法给出安装选项的问题。
103
+ - **修复安装弹窗残留状态**:安装成功后关闭弹窗,再次打开不再显示上一次的「安装成功」。
104
+
96
105
  **推迟到 v0.4+**:`cckit.lock` 与 `sync`、`profile`、`update` 的 diff 展示、
97
106
  用量统计。
98
107
 
@@ -169,3 +169,24 @@ skill 脚本的统一入口。SKILL.md 里只写这一句,不写解释器路径
169
169
 
170
170
  要点:cwd **保持调用方 cwd**(让用户给的相对路径正常工作),脚本自身资源通过
171
171
  `CCKIT_SKILL_DIR` 定位。`scripts` 未在 manifest 声明的路径应拒绝执行。
172
+
173
+ ## `cckit web`
174
+
175
+ 启动 Web 管理面板(需 `[web]` extra,缺失时提示 `uv tool install 'cckit[web]'`)。
176
+ 前端静态产物随 wheel 打包,后端自动定位(包内 `static` → 源码 `web/dist`)。
177
+
178
+ ```bash
179
+ cckit web # 前端后端一起起,打开 http://127.0.0.1:8000 即用
180
+ cckit web --host 0.0.0.0 --port 8000 # 部署到服务器
181
+ cckit web --base /cckit # 挂到根路径前缀 /cckit 下
182
+ ```
183
+
184
+ | 选项 | 说明 |
185
+ |---|---|
186
+ | `--host` | 监听地址,默认 `127.0.0.1`;部署时 `0.0.0.0` |
187
+ | `--port` | 端口,默认 `8000` |
188
+ | `--static-dir` | 前端静态产物目录(缺省不托管,配合 vite dev 使用) |
189
+ | `--base` | 根路径前缀(如 `/cckit`)。运行时把整站(`/api/*`、`/assets/*`)挂到该前缀下,反向代理只需原样透传 `/base/*`;用于把面板以 iframe 嵌入宿主 dashboard 的子路径而不与宿主 `/api` 冲突 |
190
+
191
+ `--base` 是**运行时**配置:后端 serve 时把 `window.__CCKIT_BASE__` 注入 `index.html`,
192
+ 前端据此拼 `/api` 前缀;前端资源用相对路径(`Vite base: "./"`)自动跟随子路径,无需重新构建。
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cckit
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Claude Code 的 Skill 工具箱管理器
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -18,7 +18,7 @@ Description-Content-Type: text/markdown
18
18
 
19
19
  # CCKitKit
20
20
 
21
- [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue)](#) [![版本](https://img.shields.io/badge/version-0.3.0-lightgrey)](#) [![测试](https://img.shields.io/badge/tests-126%20passed-brightgreen)](#)
21
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue)](#) [![版本](https://img.shields.io/badge/version-0.3.1-lightgrey)](#) [![测试](https://img.shields.io/badge/tests-132%20passed-brightgreen)](#)
22
22
 
23
23
  </div>
24
24
 
@@ -125,6 +125,10 @@ cd web && npm run dev # 前端 dev server(:5173),代理 /api 到 :8000
125
125
  - 后端**默认只绑 `127.0.0.1`**。要作为服务器面板被浏览器访问时,用 `cckit web --host 0.0.0.0`。
126
126
  ⚠️ 放开即把「能装 kit(跑作者代码)、能改 skill 状态」的能力暴露给网段,请只在可信内网使用,必要时在反向代理层加鉴权。
127
127
  - 嵌入其他页面用 **iframe**:后端托管前端后,宿主页 `<iframe src="http://<server>:<port>/">` 即可(同源隔离,无 CORS 问题)。
128
+ - **子路径部署**:要把面板挂到宿主 dashboard 的某个子路径下(如 `/cckit/`),用 `cckit web --base /cckit`。
129
+ 此时整站(`/cckit/api/*`、`/cckit/assets/*`)都挂到该前缀下,反向代理只需把 `/cckit/*` 原样转发给 cckit,
130
+ 宿主页 `<iframe src="http://<server>:<port>/cckit/">` 即可,不会与 dashboard 自身的 `/api` 冲突。
131
+ `--base` 是**运行时**配置,无需重新构建前端。
128
132
 
129
133
  ## 特色
130
134
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  # CCKitKit
4
4
 
5
- [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue)](#) [![版本](https://img.shields.io/badge/version-0.3.0-lightgrey)](#) [![测试](https://img.shields.io/badge/tests-126%20passed-brightgreen)](#)
5
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue)](#) [![版本](https://img.shields.io/badge/version-0.3.1-lightgrey)](#) [![测试](https://img.shields.io/badge/tests-132%20passed-brightgreen)](#)
6
6
 
7
7
  </div>
8
8
 
@@ -109,6 +109,10 @@ cd web && npm run dev # 前端 dev server(:5173),代理 /api 到 :8000
109
109
  - 后端**默认只绑 `127.0.0.1`**。要作为服务器面板被浏览器访问时,用 `cckit web --host 0.0.0.0`。
110
110
  ⚠️ 放开即把「能装 kit(跑作者代码)、能改 skill 状态」的能力暴露给网段,请只在可信内网使用,必要时在反向代理层加鉴权。
111
111
  - 嵌入其他页面用 **iframe**:后端托管前端后,宿主页 `<iframe src="http://<server>:<port>/">` 即可(同源隔离,无 CORS 问题)。
112
+ - **子路径部署**:要把面板挂到宿主 dashboard 的某个子路径下(如 `/cckit/`),用 `cckit web --base /cckit`。
113
+ 此时整站(`/cckit/api/*`、`/cckit/assets/*`)都挂到该前缀下,反向代理只需把 `/cckit/*` 原样转发给 cckit,
114
+ 宿主页 `<iframe src="http://<server>:<port>/cckit/">` 即可,不会与 dashboard 自身的 `/api` 冲突。
115
+ `--base` 是**运行时**配置,无需重新构建前端。
112
116
 
113
117
  ## 特色
114
118
 
@@ -0,0 +1,34 @@
1
+ """hatch 构建钩子:把 skills/kit-builder 与前端产物 web/dist 作为 package data 打进 wheel。
2
+
3
+ - skills/kit-builder:alt 流程的自举工具可信来源,始终随包分发(见 alt.kit_builder_source_dir)。
4
+ - web/dist:若前端已构建,打进 wheel(完整构建);未构建则不含前端(纯本体构建)。
5
+
6
+ 要点:wheel 是从 sdist 解压目录构建的(uv build),所以 sdist 阶段要把 web/dist
7
+ 以**原始路径**带进 sdist,否则 wheel 阶段在解压目录里找不到它。skills/kit-builder
8
+ 是 git 跟踪文件,sdist 默认已含,无需在 sdist 阶段 force_include(见 Docs/11-web-panel.md)。
9
+ """
10
+ from __future__ import annotations
11
+
12
+ from pathlib import Path
13
+
14
+ from hatchling.builders.hooks.plugin.interface import BuildHookInterface
15
+
16
+
17
+ class CustomBuildHook(BuildHookInterface):
18
+ def initialize(self, version, build_data):
19
+ force = build_data.setdefault("force_include", {})
20
+
21
+ # kit-builder 自举工具:alt 流程的可信来源,始终随包分发。
22
+ kb = Path(self.root) / "skills" / "kit-builder"
23
+ if self.target_name == "wheel" and (kb / "cckit.yaml").is_file():
24
+ force[str(kb)] = "cckit/skills/kit-builder"
25
+
26
+ dist = Path(self.root) / "web" / "dist"
27
+ if not (dist / "index.html").is_file():
28
+ return
29
+ if self.target_name == "sdist":
30
+ # sdist 保留原始路径,供 wheel 阶段从解压目录读取
31
+ force[str(dist)] = "web/dist"
32
+ else: # wheel
33
+ # wheel 阶段把解压目录里的 web/dist 放到最终位置 cckit/web/static
34
+ force[str(dist)] = "cckit/web/static"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cckit"
3
- version = "0.3.0"
3
+ version = "0.3.1"
4
4
  description = "Claude Code 的 Skill 工具箱管理器"
5
5
  requires-python = ">=3.12"
6
6
  dependencies = [
@@ -5,4 +5,4 @@
5
5
  """
6
6
  from __future__ import annotations
7
7
 
8
- __version__ = "0.3.0"
8
+ __version__ = "0.3.1"
@@ -74,13 +74,23 @@ class AgentResult:
74
74
  # ---- kit-builder 前置条件 ----
75
75
 
76
76
  def kit_builder_source_dir() -> Path | None:
77
- """当前 cckit 源码树的 ./skills/kit-builder 绝对路径;找不到返回 None。
77
+ """当前 cckit 的 ./skills/kit-builder 绝对路径;找不到返回 None。
78
78
 
79
- kit-builder 唯一可信来源是当前源码树里的 ./skills/kit-builder,不能从网络
80
- 搜索同名 Skill。这里从本模块位置向上定位源码树,不依赖 cwd 恰好是仓库根。
79
+ kit-builder 唯一可信来源是随 cckit 分发的 ./skills/kit-builder,不能从网络
80
+ 搜索同名 Skill。这里按「源码树 → 随包分发」的顺序定位,不依赖 cwd 恰好是仓库根:
81
+
82
+ - 源码树:仓库根的 skills/kit-builder(开发/editable 安装);
83
+ - 随包:cckit/skills/kit-builder(hatch_build.py 打进 wheel 的位置)。
81
84
  """
82
- p = Path(__file__).resolve().parents[2] / "skills" / "kit-builder"
83
- return p if (p / "cckit.yaml").is_file() else None
85
+ here = Path(__file__).resolve()
86
+ candidates = (
87
+ here.parents[2] / "skills" / "kit-builder", # 源码树 ./skills/kit-builder
88
+ here.parent / "skills" / "kit-builder", # 随包 cckit/skills/kit-builder
89
+ )
90
+ for p in candidates:
91
+ if (p / "cckit.yaml").is_file():
92
+ return p
93
+ return None
84
94
 
85
95
 
86
96
  def _kit_builder_registry_conflicts() -> list[str]:
@@ -238,7 +238,7 @@ def cmd_web(args) -> int:
238
238
  except ImportError:
239
239
  print("web 端未安装,请运行: uv tool install 'cckit[web]'", file=sys.stderr)
240
240
  return 1
241
- serve(args.host, args.port, args.static_dir)
241
+ serve(args.host, args.port, args.static_dir, args.base)
242
242
  return 0
243
243
 
244
244
 
@@ -307,6 +307,8 @@ def build_parser() -> argparse.ArgumentParser:
307
307
  w.add_argument("--port", type=int, default=8000, help="端口(默认 8000)")
308
308
  w.add_argument("--static-dir", default=None,
309
309
  help="前端静态产物目录(缺省不托管,配合 vite dev 使用)")
310
+ w.add_argument("--base", default="",
311
+ help="根路径前缀(如 /cckit);经反向代理挂到子路径、iframe 嵌入时用")
310
312
  w.set_defaults(func=cmd_web)
311
313
 
312
314
  return p
@@ -18,7 +18,7 @@ from pathlib import Path
18
18
 
19
19
  from fastapi import FastAPI, HTTPException, Request
20
20
  from fastapi.exceptions import RequestValidationError
21
- from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
21
+ from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, StreamingResponse
22
22
  from fastapi.staticfiles import StaticFiles
23
23
  from pydantic import BaseModel
24
24
 
@@ -26,7 +26,9 @@ from .. import alt, config, doctor, installer, projects, registry, state
26
26
  from ..errors import CckitError
27
27
  from ..manifest import MissingManifestError
28
28
 
29
- app = FastAPI(title="cckit web", version="0.3.0")
29
+ CCKIT_VERSION = "0.3.1"
30
+
31
+ app = FastAPI(title="cckit web", version=CCKIT_VERSION)
30
32
 
31
33
 
32
34
  # ---- 错误契约 ----
@@ -490,8 +492,34 @@ def run_doctor(body: DoctorBody):
490
492
 
491
493
  # ---- 静态托管(前端 dist 存在时挂载) ----
492
494
 
493
- def _mount_static(static_dir: str) -> None:
494
- """挂载前端静态产物 + SPA fallback(不遮挡 /api)。"""
495
+ def _normalize_base(base: str) -> str:
496
+ """规范化根路径前缀:根部署(""或"/")返回 "",其余折叠重复斜杠并确保以 / 开头、无尾斜杠。"""
497
+ b = (base or "").strip()
498
+ if not b or b == "/":
499
+ return ""
500
+ parts = [seg for seg in b.split("/") if seg]
501
+ if not parts:
502
+ return ""
503
+ return "/" + "/".join(parts)
504
+
505
+
506
+ def _inject_base(html: str, base: str) -> str:
507
+ """把 window.__CCKIT_BASE__ 注入 index.html 的 <head>,前端运行时据此拼 /api 前缀。
508
+
509
+ 对注入值做 < > & 转义,防止 --base 携带的字符跳出 <script> 标签(防御性,非用户输入)。
510
+ """
511
+ if not base:
512
+ return html
513
+ value = (json.dumps(base)
514
+ .replace("<", "\\u003c")
515
+ .replace(">", "\\u003e")
516
+ .replace("&", "\\u0026"))
517
+ script = f"<script>window.__CCKIT_BASE__={value};</script>"
518
+ return html.replace("<head>", "<head>" + script, 1)
519
+
520
+
521
+ def _mount_static(static_dir: str, base: str = "") -> None:
522
+ """挂载前端静态产物 + SPA fallback(不遮挡 /api)。base 非空时注入 __CCKIT_BASE__。"""
495
523
  sd = Path(static_dir).resolve()
496
524
  if not (sd / "index.html").is_file():
497
525
  return
@@ -499,6 +527,8 @@ def _mount_static(static_dir: str) -> None:
499
527
  if assets.is_dir():
500
528
  app.mount("/assets", StaticFiles(directory=str(assets)), name="assets")
501
529
 
530
+ index_html = _inject_base((sd / "index.html").read_text(encoding="utf-8"), base)
531
+
502
532
  @app.get("/{full_path:path}", include_in_schema=False)
503
533
  async def _spa(full_path: str):
504
534
  if full_path.startswith("api/"):
@@ -506,7 +536,7 @@ def _mount_static(static_dir: str) -> None:
506
536
  candidate = sd / full_path
507
537
  if full_path and candidate.is_file():
508
538
  return FileResponse(str(candidate))
509
- return FileResponse(str(sd / "index.html"))
539
+ return HTMLResponse(index_html)
510
540
 
511
541
 
512
542
  def _default_static_dir() -> Path | None:
@@ -521,14 +551,25 @@ def _default_static_dir() -> Path | None:
521
551
 
522
552
 
523
553
  def serve(host: str = "127.0.0.1", port: int = 8000,
524
- static_dir: str | None = None) -> None:
554
+ static_dir: str | None = None, base: str = "") -> None:
525
555
  """启动 uvicorn。host 默认 127.0.0.1;部署时显式传 0.0.0.0(见 Docs/09 安全要求)。
526
556
 
557
+ base 非空时把整站挂到该根路径前缀下(如 --base /cckit → /cckit/api、/cckit/assets),
558
+ 便于经反向代理挂到 dashboard 子路径、用 iframe 嵌入而不与宿主页 /api 冲突。
559
+
527
560
  static_dir 缺省时自动定位前端产物(包内 static → 源码 web/dist);找不到则只跑 API。
528
561
  """
529
562
  import uvicorn
530
563
 
564
+ base = _normalize_base(base)
531
565
  target = Path(static_dir) if static_dir else _default_static_dir()
532
566
  if target is not None:
533
- _mount_static(str(target))
534
- uvicorn.run(app, host=host, port=port)
567
+ _mount_static(str(target), base=base)
568
+
569
+ root_app = app
570
+ if base:
571
+ # 把现有 app(所有 /api/* 与 /assets)整体挂到前缀下,代理只需原样透传 /base/*。
572
+ parent = FastAPI(title="cckit web", version=CCKIT_VERSION)
573
+ parent.mount(base, app, name="cckit")
574
+ root_app = parent
575
+ uvicorn.run(root_app, host=host, port=port)
@@ -0,0 +1,51 @@
1
+ """Web 根路径前缀(--base)的纯函数测试:规范化与 index.html 注入。
2
+
3
+ 直接调模块函数,不经过 ASGI 测试客户端(仓库未依赖 httpx)。
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import importlib
8
+
9
+ # 与 test_web_alt.py 同理:cckit.web.__init__ 把 app 覆盖到了 cckit.web.app 属性上。
10
+ web = importlib.import_module("cckit.web.app")
11
+
12
+
13
+ def test_normalize_base_root():
14
+ assert web._normalize_base("") == ""
15
+ assert web._normalize_base("/") == ""
16
+ assert web._normalize_base(" ") == ""
17
+
18
+
19
+ def test_normalize_base_subpath():
20
+ assert web._normalize_base("/cckit") == "/cckit"
21
+ assert web._normalize_base("/cckit/") == "/cckit"
22
+ assert web._normalize_base("cckit") == "/cckit"
23
+ assert web._normalize_base("cckit/") == "/cckit"
24
+ assert web._normalize_base("/cckit/nested/") == "/cckit/nested"
25
+
26
+
27
+ def test_normalize_base_collapses_slashes():
28
+ assert web._normalize_base("//cckit//") == "/cckit"
29
+ assert web._normalize_base("/cckit//nested/") == "/cckit/nested"
30
+ assert web._normalize_base("///") == ""
31
+
32
+
33
+ def test_inject_base_adds_script():
34
+ html = "<!doctype html><html><head><title>x</title></head><body></body></html>"
35
+ out = web._inject_base(html, "/cckit")
36
+ assert '<script>window.__CCKIT_BASE__="/cckit";</script>' in out
37
+ assert out.startswith("<!doctype html><html><head>")
38
+
39
+
40
+ def test_inject_base_escapes_angle_brackets():
41
+ html = "<head></head>"
42
+ out = web._inject_base(html, "</script><script>alert(1)</script>")
43
+ # 注入值里的 < > 被转义成 \u003c / \u003e,不会跳出 <script> 标签
44
+ assert "\\u003c/script\\u003e" in out
45
+ assert out.count("<script>") == 1
46
+ assert out.count("</script>") == 1
47
+
48
+
49
+ def test_inject_base_root_is_noop():
50
+ html = "<head></head>"
51
+ assert web._inject_base(html, "") == html
@@ -48,7 +48,7 @@ wheels = [
48
48
 
49
49
  [[package]]
50
50
  name = "cckit"
51
- version = "0.3.0"
51
+ version = "0.3.1"
52
52
  source = { editable = "." }
53
53
  dependencies = [
54
54
  { name = "jsonschema" },