cckit 0.3.1__tar.gz → 0.3.3__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 (168) hide show
  1. {cckit-0.3.1 → cckit-0.3.3}/Docs/01-overview.md +8 -0
  2. {cckit-0.3.1 → cckit-0.3.3}/Docs/02-architecture.md +8 -3
  3. {cckit-0.3.1 → cckit-0.3.3}/Docs/05-design-log.md +103 -0
  4. {cckit-0.3.1 → cckit-0.3.3}/Docs/06-platform-findings.md +22 -0
  5. {cckit-0.3.1 → cckit-0.3.3}/Docs/07-security.md +42 -0
  6. {cckit-0.3.1 → cckit-0.3.3}/Docs/09-state-api.md +60 -2
  7. {cckit-0.3.1 → cckit-0.3.3}/Docs/11-web-panel.md +256 -226
  8. {cckit-0.3.1 → cckit-0.3.3}/Docs/README.md +152 -113
  9. {cckit-0.3.1 → cckit-0.3.3}/Docs/cli-spec.md +112 -4
  10. {cckit-0.3.1 → cckit-0.3.3}/Docs/examples/video-toolkit/cckit.yaml +8 -1
  11. {cckit-0.3.1 → cckit-0.3.3}/Docs/pics/architecture.md +4 -1
  12. {cckit-0.3.1 → cckit-0.3.3}/Docs/pics/directory-layout.md +1 -0
  13. {cckit-0.3.1 → cckit-0.3.3}/Docs/schema/cckit.schema.json +17 -2
  14. cckit-0.3.3/Docs/test/README.md +169 -0
  15. cckit-0.3.3/Docs/test/test-skill-dependency/.gitignore +5 -0
  16. cckit-0.3.3/Docs/test/test-skill-dependency/README.md +70 -0
  17. cckit-0.3.3/Docs/test/test-skill-dependency/json-pretty/SKILL.md +39 -0
  18. cckit-0.3.3/Docs/test/test-skill-dependency/json-pretty/pretty.config.json +5 -0
  19. cckit-0.3.3/Docs/test/test-skill-dependency/json-pretty/scripts/pretty.js +54 -0
  20. cckit-0.3.3/Docs/test/test-skill-dependency/package.json +9 -0
  21. cckit-0.3.3/Docs/test/test-skill-dependency/plain-advisor/SKILL.md +19 -0
  22. cckit-0.3.3/Docs/test/test-skill-dependency/requirements.txt +1 -0
  23. cckit-0.3.3/Docs/test/test-skill-dependency/test-skill-dependency/SKILL.md +43 -0
  24. cckit-0.3.3/Docs/test/test-skill-dependency/test-skill-dependency/config.json +5 -0
  25. cckit-0.3.3/Docs/test/test-skill-dependency/test-skill-dependency/scripts/test.py +99 -0
  26. cckit-0.3.3/Docs/test/test-skill-dependency-converted/README.md +86 -0
  27. cckit-0.3.3/Docs/test/test-skill-dependency-converted/cckit.yaml +50 -0
  28. cckit-0.3.3/Docs/test/test-skill-dependency-converted/json-pretty/SKILL.md +39 -0
  29. cckit-0.3.3/Docs/test/test-skill-dependency-converted/json-pretty/pretty.config.json +5 -0
  30. cckit-0.3.3/Docs/test/test-skill-dependency-converted/json-pretty/scripts/pretty.js +54 -0
  31. cckit-0.3.3/Docs/test/test-skill-dependency-converted/plain-advisor/SKILL.md +19 -0
  32. cckit-0.3.3/Docs/test/test-skill-dependency-converted/test-skill-dependency/SKILL.md +43 -0
  33. cckit-0.3.3/Docs/test/test-skill-dependency-converted/test-skill-dependency/config.json +5 -0
  34. cckit-0.3.3/Docs/test/test-skill-dependency-converted/test-skill-dependency/scripts/test.py +99 -0
  35. cckit-0.3.3/Docs/test/test_notice.txt +5 -0
  36. {cckit-0.3.1 → cckit-0.3.3}/PKG-INFO +29 -6
  37. {cckit-0.3.1 → cckit-0.3.3}/README.md +28 -5
  38. {cckit-0.3.1 → cckit-0.3.3}/pyproject.toml +45 -45
  39. cckit-0.3.3/skills/kit-builder/kit-builder/SKILL.md +182 -0
  40. {cckit-0.3.1 → cckit-0.3.3}/skills/kit-builder/kit-builder/kit-authoring.md +57 -12
  41. {cckit-0.3.1 → cckit-0.3.3}/skills/kit-builder/kit-builder/manifest-spec.md +40 -3
  42. {cckit-0.3.1 → cckit-0.3.3}/skills/kit-builder/kit-builder/templates/cckit.yaml +11 -4
  43. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/__init__.py +8 -8
  44. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/alt.py +685 -656
  45. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/cckit.schema.json +17 -2
  46. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/cli.py +142 -5
  47. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/config.py +10 -0
  48. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/doctor.py +38 -0
  49. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/exec.py +13 -11
  50. cckit-0.3.3/src/cckit/fsutil.py +39 -0
  51. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/installer.py +156 -13
  52. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/manifest.py +39 -2
  53. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/state.py +382 -12
  54. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/web/app.py +712 -575
  55. {cckit-0.3.1 → cckit-0.3.3}/tests/test_alt.py +22 -1
  56. cckit-0.3.3/tests/test_exec.py +234 -0
  57. cckit-0.3.3/tests/test_fsutil.py +60 -0
  58. cckit-0.3.3/tests/test_installer.py +499 -0
  59. {cckit-0.3.1 → cckit-0.3.3}/tests/test_manifest.py +81 -5
  60. {cckit-0.3.1 → cckit-0.3.3}/tests/test_state.py +93 -0
  61. {cckit-0.3.1 → cckit-0.3.3}/tests/test_web_alt.py +18 -4
  62. cckit-0.3.3/tests/test_web_config.py +184 -0
  63. cckit-0.3.3/tests/test_web_notice.py +111 -0
  64. {cckit-0.3.1 → cckit-0.3.3}/uv.lock +1 -1
  65. cckit-0.3.3/web/dist/assets/index-BHbLqx9G.js +68 -0
  66. cckit-0.3.3/web/dist/assets/index-DyKMbG6Y.css +1 -0
  67. {cckit-0.3.1 → cckit-0.3.3}/web/dist/index.html +2 -2
  68. {cckit-0.3.1 → cckit-0.3.3}/web/package-lock.json +2 -2
  69. {cckit-0.3.1 → cckit-0.3.3}/web/package.json +1 -1
  70. {cckit-0.3.1 → cckit-0.3.3}/web/src/App.tsx +7 -0
  71. {cckit-0.3.1 → cckit-0.3.3}/web/src/components/AppSidebar.tsx +222 -222
  72. cckit-0.3.3/web/src/components/KitList.tsx +824 -0
  73. cckit-0.3.3/web/src/components/NoticeMenu.tsx +86 -0
  74. cckit-0.3.3/web/src/components/ui/textarea.tsx +17 -0
  75. {cckit-0.3.1 → cckit-0.3.3}/web/src/lib/api.ts +109 -7
  76. cckit-0.3.3/web/tsconfig.tsbuildinfo +1 -0
  77. cckit-0.3.1/Docs/test/test-skill-dependency/README.md +0 -63
  78. cckit-0.3.1/Docs/test/test-skill-dependency/json-pretty/SKILL.md +0 -18
  79. cckit-0.3.1/Docs/test/test-skill-dependency/json-pretty/scripts/pretty.js +0 -12
  80. cckit-0.3.1/Docs/test/test-skill-dependency/plain-advisor/SKILL.md +0 -12
  81. cckit-0.3.1/Docs/test/test-skill-dependency/test-skill-dependency/SKILL.md +0 -22
  82. cckit-0.3.1/Docs/test/test-skill-dependency/test-skill-dependency/scripts/test.py +0 -48
  83. cckit-0.3.1/skills/kit-builder/kit-builder/SKILL.md +0 -102
  84. cckit-0.3.1/tests/test_exec.py +0 -111
  85. cckit-0.3.1/tests/test_installer.py +0 -211
  86. cckit-0.3.1/web/dist/assets/index-CSuOYCbo.js +0 -68
  87. cckit-0.3.1/web/dist/assets/index-Cp1Xoa9J.css +0 -1
  88. cckit-0.3.1/web/src/components/KitList.tsx +0 -422
  89. cckit-0.3.1/web/tsconfig.tsbuildinfo +0 -1
  90. {cckit-0.3.1 → cckit-0.3.3}/.gitignore +0 -0
  91. {cckit-0.3.1 → cckit-0.3.3}/Docs/.gitignore +0 -0
  92. {cckit-0.3.1 → cckit-0.3.3}/Docs/03-manifest-spec.md +0 -0
  93. {cckit-0.3.1 → cckit-0.3.3}/Docs/04-cli-spec.md +0 -0
  94. {cckit-0.3.1 → cckit-0.3.3}/Docs/08-installation.md +0 -0
  95. {cckit-0.3.1 → cckit-0.3.3}/Docs/10-kit-authoring.md +0 -0
  96. {cckit-0.3.1 → cckit-0.3.3}/Docs/kit-development.md +0 -0
  97. {cckit-0.3.1 → cckit-0.3.3}/Docs/pics/four-state-model.md +0 -0
  98. {cckit-0.3.1 → cckit-0.3.3}/Docs/pics/install-flow.md +0 -0
  99. {cckit-0.3.1 → cckit-0.3.3}/Docs/pics/pain-points-solution.md +0 -0
  100. {cckit-0.3.1 → cckit-0.3.3}/Docs/pics/state-transitions.md +0 -0
  101. {cckit-0.3.1 → cckit-0.3.3}/Docs/test/hello-skill/SKILL.md +0 -0
  102. {cckit-0.3.1/Docs/test/test-skill-dependency → cckit-0.3.3/Docs/test/test-skill-dependency-converted}/.gitignore +0 -0
  103. {cckit-0.3.1/Docs/test/test-skill-dependency → cckit-0.3.3/Docs/test/test-skill-dependency-converted}/package.json +0 -0
  104. {cckit-0.3.1/Docs/test/test-skill-dependency → cckit-0.3.3/Docs/test/test-skill-dependency-converted}/requirements.txt +0 -0
  105. {cckit-0.3.1 → cckit-0.3.3}/LICENSE +0 -0
  106. {cckit-0.3.1 → cckit-0.3.3}/hatch_build.py +0 -0
  107. {cckit-0.3.1 → cckit-0.3.3}/skills/kit-builder/cckit.yaml +0 -0
  108. {cckit-0.3.1 → cckit-0.3.3}/skills/kit-builder/kit-builder/templates/SKILL.md +0 -0
  109. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/__main__.py +0 -0
  110. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/env.py +0 -0
  111. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/errors.py +0 -0
  112. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/link.py +0 -0
  113. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/lint.py +0 -0
  114. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/projects.py +0 -0
  115. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/registry.py +0 -0
  116. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/schema.py +0 -0
  117. {cckit-0.3.1 → cckit-0.3.3}/src/cckit/web/__init__.py +0 -0
  118. {cckit-0.3.1 → cckit-0.3.3}/tests/conftest.py +0 -0
  119. {cckit-0.3.1 → cckit-0.3.3}/tests/helpers.py +0 -0
  120. {cckit-0.3.1 → cckit-0.3.3}/tests/test_budget.py +0 -0
  121. {cckit-0.3.1 → cckit-0.3.3}/tests/test_env.py +0 -0
  122. {cckit-0.3.1 → cckit-0.3.3}/tests/test_link.py +0 -0
  123. {cckit-0.3.1 → cckit-0.3.3}/tests/test_lint.py +0 -0
  124. {cckit-0.3.1 → cckit-0.3.3}/tests/test_registry.py +0 -0
  125. {cckit-0.3.1 → cckit-0.3.3}/tests/test_web_base.py +0 -0
  126. {cckit-0.3.1 → cckit-0.3.3}/web/.gitignore +0 -0
  127. {cckit-0.3.1 → cckit-0.3.3}/web/components.json +0 -0
  128. {cckit-0.3.1 → cckit-0.3.3}/web/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  129. {cckit-0.3.1 → cckit-0.3.3}/web/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  130. {cckit-0.3.1 → cckit-0.3.3}/web/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  131. {cckit-0.3.1 → cckit-0.3.3}/web/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  132. {cckit-0.3.1 → cckit-0.3.3}/web/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  133. {cckit-0.3.1 → cckit-0.3.3}/web/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  134. {cckit-0.3.1 → cckit-0.3.3}/web/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  135. {cckit-0.3.1 → cckit-0.3.3}/web/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  136. {cckit-0.3.1 → cckit-0.3.3}/web/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  137. {cckit-0.3.1 → cckit-0.3.3}/web/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  138. {cckit-0.3.1 → cckit-0.3.3}/web/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  139. {cckit-0.3.1 → cckit-0.3.3}/web/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  140. {cckit-0.3.1 → cckit-0.3.3}/web/index.html +0 -0
  141. {cckit-0.3.1 → cckit-0.3.3}/web/src/components/AddKitDialog.tsx +0 -0
  142. {cckit-0.3.1 → cckit-0.3.3}/web/src/components/AddScopeDialog.tsx +0 -0
  143. {cckit-0.3.1 → cckit-0.3.3}/web/src/components/DoctorDialog.tsx +0 -0
  144. {cckit-0.3.1 → cckit-0.3.3}/web/src/components/ui/badge.tsx +0 -0
  145. {cckit-0.3.1 → cckit-0.3.3}/web/src/components/ui/button.tsx +0 -0
  146. {cckit-0.3.1 → cckit-0.3.3}/web/src/components/ui/card.tsx +0 -0
  147. {cckit-0.3.1 → cckit-0.3.3}/web/src/components/ui/dialog.tsx +0 -0
  148. {cckit-0.3.1 → cckit-0.3.3}/web/src/components/ui/dropdown-menu.tsx +0 -0
  149. {cckit-0.3.1 → cckit-0.3.3}/web/src/components/ui/input.tsx +0 -0
  150. {cckit-0.3.1 → cckit-0.3.3}/web/src/components/ui/popover.tsx +0 -0
  151. {cckit-0.3.1 → cckit-0.3.3}/web/src/components/ui/progress.tsx +0 -0
  152. {cckit-0.3.1 → cckit-0.3.3}/web/src/components/ui/separator.tsx +0 -0
  153. {cckit-0.3.1 → cckit-0.3.3}/web/src/components/ui/sheet.tsx +0 -0
  154. {cckit-0.3.1 → cckit-0.3.3}/web/src/components/ui/sidebar.tsx +0 -0
  155. {cckit-0.3.1 → cckit-0.3.3}/web/src/components/ui/skeleton.tsx +0 -0
  156. {cckit-0.3.1 → cckit-0.3.3}/web/src/components/ui/sonner.tsx +0 -0
  157. {cckit-0.3.1 → cckit-0.3.3}/web/src/components/ui/tooltip.tsx +0 -0
  158. {cckit-0.3.1 → cckit-0.3.3}/web/src/hooks/use-mobile.ts +0 -0
  159. {cckit-0.3.1 → cckit-0.3.3}/web/src/hooks/useSSE.ts +0 -0
  160. {cckit-0.3.1 → cckit-0.3.3}/web/src/index.css +0 -0
  161. {cckit-0.3.1 → cckit-0.3.3}/web/src/lib/utils.ts +0 -0
  162. {cckit-0.3.1 → cckit-0.3.3}/web/src/main.tsx +0 -0
  163. {cckit-0.3.1 → cckit-0.3.3}/web/tsconfig.json +0 -0
  164. {cckit-0.3.1 → cckit-0.3.3}/web/tsconfig.node.json +0 -0
  165. {cckit-0.3.1 → cckit-0.3.3}/web/tsconfig.node.tsbuildinfo +0 -0
  166. {cckit-0.3.1 → cckit-0.3.3}/web/vite.config.d.ts +0 -0
  167. {cckit-0.3.1 → cckit-0.3.3}/web/vite.config.js +0 -0
  168. {cckit-0.3.1 → cckit-0.3.3}/web/vite.config.ts +0 -0
@@ -29,6 +29,14 @@ Claude Code 2.1.245 原生已有 plugin + marketplace 系统,并且已经解决
29
29
  后果:Python 依赖、原生编译、系统级二进制,官方全部无解。作者只能自己写
30
30
  `SessionStart` hook 比对 `package.json` 哈希再手动装——只覆盖 Node,且很笨。
31
31
 
32
+ 同一个缺口还有另一半:**skill 需要的用户配置**(API key、字体目录、一份要改的
33
+ `config.json`)官方同样没有位置可放。作者只能在 SKILL.md 里写"请先 export XXX",
34
+ 用户装完看不出缺什么,唯一发现时机是脚本跑崩那一刻。cckit 把这两半一起补上:
35
+
36
+ - 依赖由 `requires` 声明,`cckit add` 建隔离环境(D-01 / D-02);
37
+ - 用户配置由 `kit_env` / `env` / `conf_files` 声明,用户在 CLI 与 Web 面板里填,
38
+ `cckit exec` 时注入(见 [03-manifest-spec.md](03-manifest-spec.md))。
39
+
32
40
  > **CCKitKit 的核心功能,正是官方明确拒绝实现的那件事。**
33
41
  > 这不代表不能做——我们是本机工具、用户自己装的,信任模型不同。但安全责任是官方
34
42
  > 推给我们的,不是顺手接的。详见 [07-security.md](07-security.md)。
@@ -17,7 +17,7 @@
17
17
 
18
18
  ```
19
19
  ~/.cckit/ # 可被 CCKIT_HOME 覆盖
20
- store/<kit>/ # git clone 的真实内容,含 cckit.yaml
20
+ store/<kit>/ # kit 的真实文件树,含 cckit.yaml(不含 .git)
21
21
  <skill>/SKILL.md
22
22
  envs/<kit>__<skill>/ # 每 skill 一个独立环境
23
23
  registry.json # 装了什么、装在哪、什么版本
@@ -154,11 +154,16 @@ cckit 负责:
154
154
  - `CCKIT_SKILL_DIR` — skill 自己的目录。**必需**,因为 CC 跑 Bash 时 cwd 是项目根,
155
155
  脚本引用自己的资源文件需要这个
156
156
  - `CCKIT_KIT_DIR`、`CCKIT_ENV_DIR`
157
- - manifest `env:` 段声明的变量
157
+ - `kit_env`(kit 级)与 skill 的 `env`(skill 级)声明的变量。同名时 skill 级优先;
158
+ **值先取用户在 CLI/Web 填的**(`~/.cckit/envs.json`),没有再回落调用方的进程环境
158
159
  4. 记录用量(用于预算优化建议)
159
160
  5. cwd **保持调用方的 cwd**,让用户给的相对路径正常工作
160
161
 
161
- env 缺失时不静默失败,报错并提示跑 `cckit doctor`。
162
+ 必需的变量却哪儿都取不到时不静默失败,报错并给出填写的命令。
163
+
164
+ > 值**不写 Claude Code 的 `settings.json`**。往那里写会把用户的 `model`/`env`/
165
+ > `permissions` 一起卷进写入路径,而且"哪些变量是 cckit 写的"无从追踪、卸载时不敢删。
166
+ > 改由 cckit 自己存(`envs.json`,按 `{作用域}|{kit}[|{skill}]` 分桶),只在 exec 时注入。
162
167
 
163
168
  > 这个入口顺带给了我们一个真实的拦截点——用户最初设想的"总控 skill 拦截调用"在这里
164
169
  > 才真正拿得到。但它只覆盖带脚本的 skill;纯 prompt skill 的开关仍靠可见性。详见 D-04。
@@ -457,6 +457,108 @@ Web 把全局 skill 单独折叠一组、用独立覆盖选择器(不复用四
457
457
  kit 作者诱导的操作。因此审计这一步聚焦 prompt injection / 敏感读取 / 外传 /
458
458
  越界,并要求输出明确的 PASS/FAIL 结论,结论不明确一律视为不通过。
459
459
 
460
+ ## D-17 用户配置:声明在 manifest,值由用户填,只在 exec 注入
461
+
462
+ **提出的问题**:skill 常常需要用户提供参数——一个 API key、一个字体目录、一份要改的
463
+ `config.json`。cckit 原本只有一份 **kit 级**的 `env:` 声明,而且值必须由用户自己在
464
+ shell 里 export。结果是:装完看不出缺什么(唯一发现时机是脚本跑崩那一刻),CLI 与
465
+ Web 上都没有填写的入口,粒度还和 `needs`(skill 级)不一致。
466
+
467
+ **最终方案**:分两层声明 + 一个独立的值存储 + 只在 exec 注入。
468
+
469
+ - **manifest 三层字段**:顶层 `env` 改名 `kit_env`(kit 共用);skill 新增 `env`
470
+ (skill 专属,同名时覆盖 kit 级)与 `conf_files`(相对 skill 目录的可改文件)。
471
+ 三者都**只声明"要什么"**,没有 `value` / `default`。
472
+ - **值**:用户在 `cckit env <skill> <NAME> <VALUE>` 或 Web 面板里填,存
473
+ `~/.cckit/envs.json`(按 `{作用域}|{kit}[|{skill}]` 分桶,带锁 + 原子写)。
474
+ 取值优先级:`envs.json` > 调用方的进程环境。`cckit exec` 时注入。
475
+ - **声明快照进 registry**:与 `needs` 同样处理,`list` 与 Web 不必每次现场解析 manifest。
476
+ - **缺配置要看得见**:`list_skills` 顺带算出每个 skill "声明了 `required` 却还没值"的变量名
477
+ (`missing_env`),CLI 在 `cckit list` 对应行末尾提醒、Web 在 skill 名字旁挂一个琥珀色钥匙
478
+ 图标。**判定口径与 `cckit exec` 注入完全一致**(kit 级+skill 级、同名 skill 覆盖、值先看
479
+ `envs.json` 再看进程环境),否则会出现"列表说没事、一跑就报错"这种最费解的不一致。
480
+
481
+ **为什么这样定**:
482
+
483
+ - **不写 Claude Code 的 `settings.json`。** 最初的设计是把值写进 CC `settings.json`
484
+ 的 `env` 键,并维护"基线快照 + 变量引用计数"。放弃它的理由有两条:一是那个键里
485
+ 装着用户的核心配置(`ANTHROPIC_BASE_URL`、`ANTHROPIC_AUTH_TOKEN` 等),写坏等于
486
+ Claude Code 直接不可用;二是**基线快照抓不住快照之后发生的事**——用户事后手改
487
+ 会被误判成 cckit 写的进而删除,基线文件丢了又会把 cckit 自己写的变量当成用户原有的
488
+ 从而永远删不掉。改成自己存、exec 时注入之后,这些问题连同"误删用户凭据"的风险
489
+ 一起消失(见 [07-security.md](07-security.md) 第 10 条)。
490
+ - **值不由作者提供。** manifest 里没有放值的地方,从根上堵死"作者把密钥提交进仓库、
491
+ 随 kit 分发给所有用户"。作者能做的只有声明"我需要这个变量",值一律由用户填。
492
+ - **`envs.json` 不并进 `registry.json`。** registry 是安装元数据(`remove` 即清),
493
+ 用户填的值是用户数据。混在一起会让"重装即恢复"的语义变乱。
494
+ - **分桶键带 (kit, skill),因此不需要引用计数。** 删一个 skill 只删它自己那份,
495
+ 不会波及同 kit 的其他 skill。key 必须带作用域:同名 skill 在全局与项目各装一份时,
496
+ 各自的配置是独立的。
497
+ - **`conf_files` 就放在 skill 目录里**,承认它会被 `remove`/重装一起删掉。要长期保留的
498
+ 数据应由脚本写到用户自己的目录——把用户数据搬出 store 会引入"配置目录 + 注入路径 +
499
+ 升级时合并用户改动"一整套设计,而那是作者该决定的事,不该由 cckit 规定。
500
+ - **`envs` 这个名字留给虚拟环境。** 代码里 `envs` 早已指 `{runtime: env_dir}` 与
501
+ `~/.cckit/envs/`(venv 目录),`env_ok`、doctor 的 env 项、`CCKIT_ENV_DIR` 也都是这个
502
+ 意思。所以"环境变量"一律用 `env`(单数):`kit_env` / skill 的 `env` / `envs.json`。
503
+ 同一份 registry 记录里 `envs` 与 `env` 并存,这是刻意区分的,不是笔误。
504
+ - **版本号不变(`cckit: 1`)。** 顶层 schema 是 `additionalProperties: false`,所以改名后
505
+ 仍写旧 `env:` 的 kit 会被新 schema **明确拒绝**安装,而不是静默忽略——可以接受的
506
+ 失败方式。代价是"1"从此不再能表达兼容性边界(见下)。
507
+
508
+ **代价(必须记录)**:
509
+
510
+ - **`env` → `kit_env` 是破坏性改名。** 任何已发布的、用了顶层 `env:` 的 kit,在新版
511
+ cckit 下会被 schema 拒绝安装,作者需要改字段名。因为 cckit 尚在 0.x 且生态未铺开,
512
+ 判断为可接受;官方 kit-builder 的规范文档、模板与自检清单已同步更新。
513
+ - **`conf_files` 里的用户改动会随卸载/重装丢失。** 这是明确接受的限制,已在
514
+ manifest-spec 与 kit-authoring 里以警告形式写给作者。
515
+ - **`installed` 态(无 link)与 `managed=False` 的 skill 不允许改配置。** 前者尚未启用,
516
+ 后者不是 cckit 管的。UI 禁用之外,服务端也重新校验一遍——UI 禁用了不代表可以信任
517
+ 客户端传来的请求。
518
+
519
+ ## D-18 卸载:删除必须可验证,store 不含 `.git`
520
+
521
+ **提出的问题**(用户报告):从远程仓库装好后,远程更新了,再次 `cckit add` 被提示
522
+ "已安装(store 已存在),请先 `cckit remove`";`remove` 跑完看似成功,store 里却留下
523
+ 一个 `.git`,于是 `add` 永远装不上,而 `remove` 这时又说"未安装"——两头堵死。
524
+
525
+ **根因**:两处叠加,单独任一处都不会致命。
526
+
527
+ 1. `installer._clone` 把 clone 的 `.git` 一起搬进了 store。
528
+ 2. 删 store 用 `shutil.rmtree(ignore_errors=True)`,而 git for Windows 把
529
+ `.git/objects/**` 标为只读,`os.unlink` 抛 PermissionError 被这个 flag 吞掉:
530
+ **报告成功,目录还在**(实测见 [06](06-platform-findings.md) 4.1)。
531
+ registry 已先被清掉,于是这个残留再没有任何人能定位到它。
532
+
533
+ **最终方案**:四条一起上,少一条这个问题都会换个形式回来。
534
+
535
+ 1. **store 不含 `.git`**:`_clone` 落盘前剥离。来源与 sha 已记进 registry,git 历史
536
+ 对安装/卸载都没有用处。alt 流程一直如此(cli-spec 已写"总是剥离 `.git`"),
537
+ 标准流程现在对齐,`_clone` 与 `alt.materialize` 行为一致。
538
+ 2. **删目录统一走 `fsutil.rmtree`**:失败回调里清只读位后重试;`ignore_errors` 只
539
+ 决定"重试还失败时是否抛出",再也没有"静默跳过只读文件"这条路径。
540
+ 3. **remove 不许谎报成功**:删不掉就抛受检错误并**保留 registry**,让用户排掉占用
541
+ 后重跑;并且当 registry 无记录、store 却有同名目录时,`remove <kit>` 直接清掉它
542
+ (连同指向它的 link、按 `<kit>__*` 命名的 env),给出唯一出路。`doctor` 增加一条
543
+ 同名残留检查,但**只报告不自动修**——registry.json 若被人为删掉,自动清等于清空
544
+ store。
545
+ 4. **删 store 前按目标路径反查 link**(`state.links_into`)。registry 的
546
+ `known_scopes` + skill 名只管得住记录在案的 link;记录丢失或用户在别的项目根手工
547
+ 建的 link 会留成悬空 link —— 而悬空 link 同样占住 skill 名,下一次 `add` 照样被挡。
548
+
549
+ **为什么这样定**:
550
+
551
+ - **store 是 cckit 的私有缓存,不是工作副本。** "远程更新后重装" = remove + add,
552
+ 用户不需要在 store 里 `git pull`;保留 `.git` 只买到只读文件与磁盘占用两个成本。
553
+ - **静默失败比失败更贵。** 这次的代价不是"少删一个目录",而是状态自相矛盾:一个
554
+ 说装了、一个说没装,用户没有任何可自证的路径。宁可当场报错。
555
+ - **不做"删除前先备份"之类的补偿。** store 里的内容随时可由 registry 记录的
556
+ URL+sha 重新拉取,为它做备份机制是把简单问题复杂化。
557
+
558
+ **代价(必须记录)**:store 里不再能 `git log`(查装了什么版本看 `cckit list` 或
559
+ registry);`remove` 现在可能因文件被占用而失败并留下半清理状态,需要重跑一次——
560
+ 这是刻意的取舍,失败会明确说出来,而不是留在那里等人踩。
561
+
460
562
  ## 我的判断失误汇总
461
563
 
462
564
  集中列出,便于后续开发者校准对本文档其余部分的信任度:
@@ -470,6 +572,7 @@ kit 作者诱导的操作。因此审计这一步聚焦 prompt injection / 敏
470
572
  | 5 | 建议 subprocess 调 `mklink` | 多余(标准库已有) | D-10 |
471
573
  | 6 | 未抽出状态读写层 | 架构层遗漏,被 Web 需求问出来 | D-13 |
472
574
  | 7 | 未考虑并发写 | 遗漏,且是 Web 下最难定位的一类 bug | D-14 |
575
+ | 8 | 用 `shutil.rmtree(ignore_errors=True)` 删 store,并把 `.git` 一起搬进 store | 静默失败:命令报成功、目录还在,用户无路可走 | D-18 |
473
576
 
474
577
  前三条的共同点:**"我验证过"和"我搜过但没找到"都会伪装成确定性。**
475
578
  对策是[06](06-platform-findings.md)只记录实跑结论,并保留"待验证清单"。
@@ -203,6 +203,28 @@ os.open(O_CREAT|O_EXCL) 加锁 + os.replace → 8 条完整保留
203
203
  - **Windows 保留名**:`con` `prn` `aux` `nul` `com1..9` `lpt1..9` 不能作 skill 名。
204
204
  - **可执行位**:POSIX 下脚本需要 exec bit;若 kit 作者在 Windows 开发可能未设置。
205
205
 
206
+ ### 4.1 ⚠️ 删 `.git` 会静默失败(已实测,Windows)
207
+
208
+ git for Windows 把 `.git/objects/**` 的对象文件标成**只读**,`os.unlink` 抛
209
+ PermissionError;`shutil.rmtree(ignore_errors=True)` 会把这些异常吞掉 ——
210
+ **命令报告成功,目录却还在**。
211
+
212
+ 实测(`git init` → `git clone` → `shutil.rmtree(clone, ignore_errors=True)`):
213
+ 工作树文件全被删掉,只剩 `.git/` 骨架,里面是那批只读对象文件。
214
+ 即"看起来删干净了、其实留了一个 `.git`"。
215
+
216
+ 这就是 `cckit remove` 后 store 里留一个 `.git`、之后 `add` 永远报
217
+ "store 已存在"的成因 —— 而 registry 已经清掉,`remove` 又回一句"未安装",
218
+ 用户两头堵死。
219
+
220
+ 对策(三层,见 `fsutil.rmtree` / `installer.remove_kit`):
221
+
222
+ 1. 删目录统一走 `fsutil.rmtree`:失败回调里 `os.chmod(path, 0o700)` 后重试一次;
223
+ 2. `installer._clone` 落盘前剥离 `.git`(只读对象文件根本不进 store);
224
+ 3. 确实删不掉时**抛错并保留 registry**,绝不"报告成功却留下残留"。
225
+
226
+ `shutil.rmtree(..., ignore_errors=True)` 在本项目里已不再直接使用。
227
+
206
228
  ---
207
229
 
208
230
  ## 5. 待验证清单(实现前必须补)
@@ -106,6 +106,48 @@ Web 管理界面能改 CC 行为、能触发安装(而安装会执行仓库作
106
106
  **绝不能监听 `0.0.0.0`。** 确需远程访问必须加鉴权。这条要写在用户文档显眼处,
107
107
  不能默认放开让用户自己发现。
108
108
 
109
+ ### 10. 用户配置:注入点是 `exec`,写入面只有 skill 目录
110
+
111
+ skill 需要的用户配置(环境变量的值、可改的配置文件)有两个新面:
112
+
113
+ **a) 环境变量的值不写 `settings.json`。** 最初的设想是把值写进 CC `settings.json`
114
+ 的 `env` 键,被否决:那个键是用户的核心配置,写进去就要处理"哪些变量是 cckit 写的"
115
+ (基线快照 + 引用计数),而快照抓不住用户事后手改、基线丢了又会把 cckit 自己写的变量
116
+ 误判成用户原有的。改为 cckit 自己存 (`envs.json`) 并在 `cckit exec` 时注入,
117
+ 上述问题连同"误删用户凭据"的风险一起消失。
118
+
119
+ **b) 保存配置文件必须服务端校验路径。** Web 面板能改 skill 目录里的文件,这是一个
120
+ **任意文件写入**的潜在入口。两道闸缺一不可:
121
+
122
+ - 路径必须**逐字命中** registry 快照里的 `conf_files` 声明 —— 客户端传来的一律不可信,
123
+ "装的时候声明过"不等于"这次传的就是那一条";
124
+ - 解析(`resolve`)之后仍要落在 skill 目录内 —— 防 `..` 与符号链接逃逸。
125
+
126
+ 安装时 `semantic_check` 会先拦一遍(拒绝绝对路径与 `..`),但那只是作者侧的第一道;
127
+ 文件系统在安装之后仍可能变化,所以运行期必须重新校验。
128
+
129
+ **c) 值不在终端回显。** `cckit list --envs` 与 `cckit env <skill>` 只显示
130
+ "变量名 + 是否必需 + 已设/未设",不回显明文 —— 否则密钥会进终端 scrollback 与日志。
131
+ 面板里的值绑定在 127.0.0.1 上传输(见第 9 条)。
132
+
133
+ **d) manifest 里没有放值的地方。** `kit_env` / `env` 只有 `name` / `required` /
134
+ `description`,没有 `value` / `default`。这从根上堵死了"作者把自己的密钥提交进仓库、
135
+ 随 kit 分发给所有用户"。作者能做的只有声明"我需要这个变量"。
136
+
137
+ ### 11. 管理员通知的内容等同公开
138
+
139
+ `cckit web --notice TITLE FILE` 让运维在面板顶部挂一条公告。两点必须写进用户文档:
140
+
141
+ - **能访问面板的人都看得到 `FILE` 的内容。** 默认只绑 `127.0.0.1` 时还好,但文档是
142
+ 明确支持 `--host 0.0.0.0` 部署的 —— 那时公告就是公开的。**不要往公告文件里放密钥。**
143
+ - **正文按纯文本渲染,不走 HTML 也不走 Markdown。** 文件内容原样进 `<pre>`,既不能
144
+ 执行脚本,也不会因为解析器的某个 bug 变成注入点。每多支持一种富文本格式就多一个
145
+ XSS 面,而这个功能并不需要它。
146
+
147
+ 实现上另有两道防御:`--notice` 的 TITLE 会被注入 `index.html`,所以对 `<` / `>` / `&`
148
+ 做了转义(与 `--base` 同一套 `_js_string`),防止标题里的 `</script>` 跳出去;正文读取
149
+ 有 256 KiB 上限、用 `errors="replace"` 解码,免得一个非 UTF-8 文件把面板打挂。
150
+
109
151
  ## alt 流程的安全边界(非标准仓库导入)
110
152
 
111
153
  `--alt` 会在安装前调用 Claude Code 去改造并审计一个**临时目录里**的仓库。它没有
@@ -42,10 +42,15 @@ CC 实际 不加载
42
42
  |---|---|---|
43
43
  | 装了哪些 kit、版本、sha、来源 | `registry.json` | 观测不出来,必须存 |
44
44
  | env 路径映射(runtime → env_dir)| `registry.json` | 同上 |
45
+ | 声明快照(`kit_env` / `env` / `conf_files` / `needs`)| `registry.json` | 从 manifest 抄一份,免得 list/Web 每次现场解析 |
45
46
  | 用量统计 | `registry.json` | 同上 |
47
+ | **用户填的环境变量值** | `envs.json` | 用户的输入,观测不出来;与安装元数据分开存 |
46
48
  | 团队期望装什么 | `cckit.lock` | 这是声明,不是现状 |
47
49
  | **是否启用 / name-only / off** | **文件系统 + settings.json** | 观测得到,存了会漂移 |
48
50
 
51
+ > `envs.json` 单独一个文件、不并进 `registry.json`:registry 是**安装元数据**
52
+ > (`remove` 即清),用户填的值是**用户数据**,混在一起会让"重装即恢复"的语义变乱。
53
+
49
54
  ## API 契约
50
55
 
51
56
  ```python
@@ -64,13 +69,33 @@ class SkillState:
64
69
  is_global_skill: bool = False # project 作用域下,此条目是「全局 skill 的项目覆盖/跟随」
65
70
  global_state: str | None = None # 全局 skill 在全局作用域的状态
66
71
  override: bool = False # 全局 skill 是否带项目级覆盖(off/name-only)
72
+ envs: list[dict] = [] # skill 级环境变量声明(name/required/description)
73
+ conf_files: list[str] = [] # 可修改的配置文件,相对 skill 根
74
+ missing_env: list[str] = [] # 声明 required 却还没值的变量名(CLI/Web 的提醒就靠它)
67
75
 
68
76
  def list_skills(scope=None, root=None) -> list[SkillState]: ...
69
77
  def get_state(name, scope, root=None) -> str: ...
70
78
  def set_state(name, state, scope, root=None) -> None: ... # 带锁 + 原子写
71
79
  def budget(scope=None, root=None) -> tuple[int, int]: ... # (已用字符, 上限)
80
+
81
+ # 用户填的环境变量值(envs.json,带锁 + 原子写)
82
+ def set_user_env(name, value, *, kit, skill=None, scope='global', root=None) -> None: ...
83
+ def remove_user_envs(kit, scope='global', *, skill=None, root=None) -> None: ...
84
+ def stored_env(scope, kit, skill=None, root=None) -> dict[str, str]: ...
85
+ def env_requirements(kit, skill, scope='global', root=None) -> list[EnvRequirement]: ...
86
+ def missing_required_env(kit_info, skill_rec, values) -> list[str]: ...
87
+ def env_scope_of(s) -> str: ... # 该条目的值存在哪个作用域(全局 skill 的项目视图仍是 global)
88
+
89
+ # 可修改的配置文件(conf_files)
90
+ def ensure_config_editable(scope, root, kit, skill) -> None: ...
91
+ def conf_files(kit, skill) -> list[str]: ...
92
+ def read_conf_file(kit, skill, rel) -> str: ...
93
+ def write_conf_file(kit, skill, rel, content) -> None: ... # 原子替换
72
94
  ```
73
95
 
96
+ ⚠️ `envs` 与 `env` 是**两个不同的东西**,只差一个字母:前者是 `{runtime: env_dir}`
97
+ (虚拟环境目录),后者是环境变量声明。同一份 registry 记录里两者并存,别混。
98
+
74
99
  `list_skills()` 必须同时列出**非 cckit 管理**的 skill,标记 `managed=False`。
75
100
  用户看到的是完整工具箱视图,但只有 cckit 装的能被开关。
76
101
 
@@ -106,11 +131,17 @@ def budget(scope=None, root=None) -> tuple[int, int]: ... # (已用字符, 上
106
131
  "version": "1.2.0",
107
132
  "installed_at": "2026-08-27T02:10:00Z",
108
133
  "store": "~/.cckit/store/video-toolkit",
134
+ "kit_env": [
135
+ { "name": "OPENAI_API_KEY", "required": false, "description": "字幕润色" }
136
+ ],
109
137
  "skills": [
110
138
  { "name": "burn-subtitles",
111
139
  "envs": { "python": "~/.cckit/envs/video-toolkit__burn-subtitles__python" },
112
- "needs": ["ffmpeg", "python"] },
113
- { "name": "describe-video", "envs": {}, "needs": [] }
140
+ "needs": ["ffmpeg", "python"],
141
+ "env": [{ "name": "FONT_DIR", "required": true, "description": "字体目录" }],
142
+ "conf_files": ["config.json"] },
143
+ { "name": "describe-video", "envs": {}, "needs": [],
144
+ "env": [], "conf_files": [] }
114
145
  ],
115
146
  "known_scopes": ["global", "C:/work/proj-a"],
116
147
  "override_scopes": []
@@ -119,6 +150,33 @@ def budget(scope=None, root=None) -> tuple[int, int]: ... # (已用字符, 上
119
150
  }
120
151
  ```
121
152
 
153
+ 注意 `envs`(虚拟环境目录)与 `env`(环境变量声明)并存 —— 前者由 `installer.build_envs`
154
+ 建好后写入,后者是从 manifest 抄来的声明快照。
155
+
156
+ ## `envs.json` 结构
157
+
158
+ 用户在 CLI(`cckit env`)或 Web 面板里填的环境变量值。按
159
+ `{作用域}|{kit}` 与 `{作用域}|{kit}|{skill}` 分桶;作用域是 `"global"` 或项目根绝对路径
160
+ (与 `known_scopes` 的表示一致)。
161
+
162
+ ```json
163
+ {
164
+ "version": 1,
165
+ "kit_env": {
166
+ "global|video-toolkit": { "OPENAI_API_KEY": "sk-..." }
167
+ },
168
+ "skill_env": {
169
+ "global|video-toolkit|burn-subtitles": { "FONT_DIR": "D:/fonts" }
170
+ }
171
+ }
172
+ ```
173
+
174
+ - **按 (kit, skill) 分桶意味着不需要引用计数**:删一个 skill 只删它自己那份,
175
+ 不会波及同 kit 的其他 skill。`remove_kit` 会把这个 kit 的 kit 桶与全部 skill 桶一起清掉
176
+ (除非 `--keep-env`)。
177
+ - 读取顺序:`stored_env()` 先取 kit 桶打底、再叠加 skill 桶(同名以 skill 级为准)。
178
+ - 与写 `settings.json` 同一套纪律:**文件锁 + 原子替换**。空桶会被顺手删掉。
179
+
122
180
  `skills[].envs` 是 `runtime → env_dir` 的映射。一个 skill 可同时有 `python` 与
123
181
  `node` 两个 env(各自独立目录);纯 prompt skill 为 `{}`。
124
182