@surething/cockpit 1.0.259 → 1.0.261

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 (249) hide show
  1. package/.next-prod/BUILD_ID +1 -1
  2. package/.next-prod/app-path-routes-manifest.json +1 -1
  3. package/.next-prod/build-manifest.json +2 -2
  4. package/.next-prod/cockpit-build.json +1 -1
  5. package/.next-prod/prerender-manifest.json +4 -4
  6. package/.next-prod/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next-prod/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next-prod/server/app/_global-error.html +1 -1
  9. package/.next-prod/server/app/_global-error.rsc +1 -1
  10. package/.next-prod/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/.next-prod/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  12. package/.next-prod/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next-prod/server/app/_not-found/page.js +1 -1
  14. package/.next-prod/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next-prod/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next-prod/server/app/_not-found.html +2 -2
  17. package/.next-prod/server/app/_not-found.rsc +3 -3
  18. package/.next-prod/server/app/_not-found.segments/_full.segment.rsc +3 -3
  19. package/.next-prod/server/app/_not-found.segments/_index.segment.rsc +3 -3
  20. package/.next-prod/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  21. package/.next-prod/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  22. package/.next-prod/server/app/api/agent/test/route.js.nft.json +1 -1
  23. package/.next-prod/server/app/api/bash/route.js.nft.json +1 -1
  24. package/.next-prod/server/app/api/chat/codex/route.js +1 -1
  25. package/.next-prod/server/app/api/chat/codex/route.js.nft.json +1 -1
  26. package/.next-prod/server/app/api/chat/deepseek/route.js +1 -1
  27. package/.next-prod/server/app/api/chat/deepseek/route.js.nft.json +1 -1
  28. package/.next-prod/server/app/api/chat/glm/route.js +1 -1
  29. package/.next-prod/server/app/api/chat/glm/route.js.nft.json +1 -1
  30. package/.next-prod/server/app/api/chat/kimi/route.js +1 -1
  31. package/.next-prod/server/app/api/chat/kimi/route.js.nft.json +1 -1
  32. package/.next-prod/server/app/api/chat/ollama/route.js.nft.json +1 -1
  33. package/.next-prod/server/app/api/chat/route.js +1 -1
  34. package/.next-prod/server/app/api/chat/route.js.nft.json +1 -1
  35. package/.next-prod/server/app/api/chat/stop/route.js +1 -1
  36. package/.next-prod/server/app/api/chat/stop/route.js.nft.json +1 -1
  37. package/.next-prod/server/app/api/claude-stats/route.js.nft.json +1 -1
  38. package/.next-prod/server/app/api/commands/route.js.nft.json +1 -1
  39. package/.next-prod/server/app/api/comments/route.js.nft.json +1 -1
  40. package/.next-prod/server/app/api/db/columns/route.js.nft.json +1 -1
  41. package/.next-prod/server/app/api/db/connect/route.js.nft.json +1 -1
  42. package/.next-prod/server/app/api/db/disconnect/route.js.nft.json +1 -1
  43. package/.next-prod/server/app/api/db/export/route.js.nft.json +1 -1
  44. package/.next-prod/server/app/api/db/query/route.js.nft.json +1 -1
  45. package/.next-prod/server/app/api/db/schemas/route.js.nft.json +1 -1
  46. package/.next-prod/server/app/api/deepseek/balance/route.js.nft.json +1 -1
  47. package/.next-prod/server/app/api/deepseek/credentials/route.js.nft.json +1 -1
  48. package/.next-prod/server/app/api/deepseek/models/route.js.nft.json +1 -1
  49. package/.next-prod/server/app/api/dev/spans/route.js.nft.json +1 -1
  50. package/.next-prod/server/app/api/extension/version/route.js.nft.json +1 -1
  51. package/.next-prod/server/app/api/file/route.js.nft.json +1 -1
  52. package/.next-prod/server/app/api/files/blame/route.js.nft.json +1 -1
  53. package/.next-prod/server/app/api/files/clipboard/route.js.nft.json +1 -1
  54. package/.next-prod/server/app/api/files/copy/route.js.nft.json +1 -1
  55. package/.next-prod/server/app/api/files/delete/route.js.nft.json +1 -1
  56. package/.next-prod/server/app/api/files/expanded/route.js.nft.json +1 -1
  57. package/.next-prod/server/app/api/files/index/route.js.nft.json +1 -1
  58. package/.next-prod/server/app/api/files/init/route.js.nft.json +1 -1
  59. package/.next-prod/server/app/api/files/paste/route.js.nft.json +1 -1
  60. package/.next-prod/server/app/api/files/read/route.js.nft.json +1 -1
  61. package/.next-prod/server/app/api/files/readdir/route.js.nft.json +1 -1
  62. package/.next-prod/server/app/api/files/recent/route.js.nft.json +1 -1
  63. package/.next-prod/server/app/api/files/save/route.js.nft.json +1 -1
  64. package/.next-prod/server/app/api/files/search/route.js.nft.json +1 -1
  65. package/.next-prod/server/app/api/files/stat/route.js.nft.json +1 -1
  66. package/.next-prod/server/app/api/files/text/route.js.nft.json +1 -1
  67. package/.next-prod/server/app/api/git/branch-diff/route.js.nft.json +1 -1
  68. package/.next-prod/server/app/api/git/branches/route.js.nft.json +1 -1
  69. package/.next-prod/server/app/api/git/commit-diff/route.js.nft.json +1 -1
  70. package/.next-prod/server/app/api/git/commits/route.js.nft.json +1 -1
  71. package/.next-prod/server/app/api/git/current-branch/route.js.nft.json +1 -1
  72. package/.next-prod/server/app/api/git/diff/route.js.nft.json +1 -1
  73. package/.next-prod/server/app/api/git/discard/route.js.nft.json +1 -1
  74. package/.next-prod/server/app/api/git/stage/route.js.nft.json +1 -1
  75. package/.next-prod/server/app/api/git/status/route.js.nft.json +1 -1
  76. package/.next-prod/server/app/api/git/unstage/route.js.nft.json +1 -1
  77. package/.next-prod/server/app/api/git/worktree/route.js.nft.json +1 -1
  78. package/.next-prod/server/app/api/glm/credentials/route.js.nft.json +1 -1
  79. package/.next-prod/server/app/api/glm/models/route.js +1 -1
  80. package/.next-prod/server/app/api/glm/models/route.js.nft.json +1 -1
  81. package/.next-prod/server/app/api/glm/usage/route.js +1 -1
  82. package/.next-prod/server/app/api/glm/usage/route.js.nft.json +1 -1
  83. package/.next-prod/server/app/api/global-state/route.js.nft.json +1 -1
  84. package/.next-prod/server/app/api/health/route.js.nft.json +1 -1
  85. package/.next-prod/server/app/api/html-apps/[id]/route.js +1 -1
  86. package/.next-prod/server/app/api/html-apps/[id]/route.js.nft.json +1 -1
  87. package/.next-prod/server/app/api/html-apps/route.js +1 -1
  88. package/.next-prod/server/app/api/html-apps/route.js.nft.json +1 -1
  89. package/.next-prod/server/app/api/jupyter/load/route.js.nft.json +1 -1
  90. package/.next-prod/server/app/api/jupyter/save/route.js.nft.json +1 -1
  91. package/.next-prod/server/app/api/jupyter/shutdown/route.js.nft.json +1 -1
  92. package/.next-prod/server/app/api/kimi/credentials/route.js.nft.json +1 -1
  93. package/.next-prod/server/app/api/kimi/models/route.js +1 -1
  94. package/.next-prod/server/app/api/kimi/models/route.js.nft.json +1 -1
  95. package/.next-prod/server/app/api/kimi/usage/route.js +1 -1
  96. package/.next-prod/server/app/api/kimi/usage/route.js.nft.json +1 -1
  97. package/.next-prod/server/app/api/lsp/definition/route.js.nft.json +1 -1
  98. package/.next-prod/server/app/api/lsp/hover/route.js.nft.json +1 -1
  99. package/.next-prod/server/app/api/lsp/references/route.js.nft.json +1 -1
  100. package/.next-prod/server/app/api/lsp/status/route.js.nft.json +1 -1
  101. package/.next-prod/server/app/api/lsp/warmup/route.js.nft.json +1 -1
  102. package/.next-prod/server/app/api/mysql/columns/route.js.nft.json +1 -1
  103. package/.next-prod/server/app/api/mysql/connect/route.js.nft.json +1 -1
  104. package/.next-prod/server/app/api/mysql/disconnect/route.js.nft.json +1 -1
  105. package/.next-prod/server/app/api/mysql/export/route.js.nft.json +1 -1
  106. package/.next-prod/server/app/api/mysql/query/route.js.nft.json +1 -1
  107. package/.next-prod/server/app/api/mysql/schemas/route.js.nft.json +1 -1
  108. package/.next-prod/server/app/api/neo4j/connect/route.js.nft.json +1 -1
  109. package/.next-prod/server/app/api/neo4j/disconnect/route.js.nft.json +1 -1
  110. package/.next-prod/server/app/api/neo4j/query/route.js.nft.json +1 -1
  111. package/.next-prod/server/app/api/neo4j/schema/route.js.nft.json +1 -1
  112. package/.next-prod/server/app/api/note/route.js.nft.json +1 -1
  113. package/.next-prod/server/app/api/ollama/config/route.js.nft.json +1 -1
  114. package/.next-prod/server/app/api/ollama/models/route.js.nft.json +1 -1
  115. package/.next-prod/server/app/api/ollama/start/route.js.nft.json +1 -1
  116. package/.next-prod/server/app/api/open-cursor/route.js.nft.json +1 -1
  117. package/.next-prod/server/app/api/open-vscode/route.js.nft.json +1 -1
  118. package/.next-prod/server/app/api/pick-folder/route.js.nft.json +1 -1
  119. package/.next-prod/server/app/api/pinned-sessions/route.js.nft.json +1 -1
  120. package/.next-prod/server/app/api/project-settings/route.js.nft.json +1 -1
  121. package/.next-prod/server/app/api/project-state/route.js +1 -1
  122. package/.next-prod/server/app/api/project-state/route.js.nft.json +1 -1
  123. package/.next-prod/server/app/api/projectGraph/affected/route.js.nft.json +1 -1
  124. package/.next-prod/server/app/api/projectGraph/callees/route.js.nft.json +1 -1
  125. package/.next-prod/server/app/api/projectGraph/callers/route.js.nft.json +1 -1
  126. package/.next-prod/server/app/api/projectGraph/coedit/route.js.nft.json +1 -1
  127. package/.next-prod/server/app/api/projectGraph/context/route.js.nft.json +1 -1
  128. package/.next-prod/server/app/api/projectGraph/file/route.js.nft.json +1 -1
  129. package/.next-prod/server/app/api/projectGraph/file-functions/route.js.nft.json +1 -1
  130. package/.next-prod/server/app/api/projectGraph/impact/route.js.nft.json +1 -1
  131. package/.next-prod/server/app/api/projectGraph/related/route.js.nft.json +1 -1
  132. package/.next-prod/server/app/api/projectGraph/risk/route.js.nft.json +1 -1
  133. package/.next-prod/server/app/api/projectGraph/search/route.js.nft.json +1 -1
  134. package/.next-prod/server/app/api/projects/route.js.nft.json +1 -1
  135. package/.next-prod/server/app/api/prompts/config/route.js.nft.json +1 -1
  136. package/.next-prod/server/app/api/push/public-key/route.js.nft.json +1 -1
  137. package/.next-prod/server/app/api/push/subscribe/route.js.nft.json +1 -1
  138. package/.next-prod/server/app/api/push/unsubscribe/route.js.nft.json +1 -1
  139. package/.next-prod/server/app/api/redis/command/route.js.nft.json +1 -1
  140. package/.next-prod/server/app/api/redis/connect/route.js.nft.json +1 -1
  141. package/.next-prod/server/app/api/redis/delete/route.js.nft.json +1 -1
  142. package/.next-prod/server/app/api/redis/disconnect/route.js.nft.json +1 -1
  143. package/.next-prod/server/app/api/redis/get/route.js.nft.json +1 -1
  144. package/.next-prod/server/app/api/redis/keys/route.js.nft.json +1 -1
  145. package/.next-prod/server/app/api/redis/set/route.js.nft.json +1 -1
  146. package/.next-prod/server/app/api/review/[id]/comments/route.js.nft.json +1 -1
  147. package/.next-prod/server/app/api/review/[id]/replies/route.js.nft.json +1 -1
  148. package/.next-prod/server/app/api/review/[id]/route.js.nft.json +1 -1
  149. package/.next-prod/server/app/api/review/identify/route.js.nft.json +1 -1
  150. package/.next-prod/server/app/api/review/order/route.js.nft.json +1 -1
  151. package/.next-prod/server/app/api/review/route.js.nft.json +1 -1
  152. package/.next-prod/server/app/api/review/share-info/route.js.nft.json +1 -1
  153. package/.next-prod/server/app/api/review/users/route.js.nft.json +1 -1
  154. package/.next-prod/server/app/api/scheduled-tasks/route.js +2 -2
  155. package/.next-prod/server/app/api/scheduled-tasks/route.js.nft.json +1 -1
  156. package/.next-prod/server/app/api/services/config/route.js.nft.json +1 -1
  157. package/.next-prod/server/app/api/services/scripts/route.js.nft.json +1 -1
  158. package/.next-prod/server/app/api/session/[sessionId]/fork/route.js +1 -1
  159. package/.next-prod/server/app/api/session/[sessionId]/fork/route.js.nft.json +1 -1
  160. package/.next-prod/server/app/api/session/[sessionId]/history/route.js.nft.json +1 -1
  161. package/.next-prod/server/app/api/session-by-path/route.js +1 -1
  162. package/.next-prod/server/app/api/session-by-path/route.js.nft.json +1 -1
  163. package/.next-prod/server/app/api/sessions/projects/[encodedPath]/route.js +1 -1
  164. package/.next-prod/server/app/api/sessions/projects/[encodedPath]/route.js.nft.json +1 -1
  165. package/.next-prod/server/app/api/sessions/projects/route.js +1 -1
  166. package/.next-prod/server/app/api/sessions/projects/route.js.nft.json +1 -1
  167. package/.next-prod/server/app/api/sessions/route.js.nft.json +1 -1
  168. package/.next-prod/server/app/api/settings/route.js.nft.json +1 -1
  169. package/.next-prod/server/app/api/skills/[id]/route.js.nft.json +1 -1
  170. package/.next-prod/server/app/api/skills/content/route.js.nft.json +1 -1
  171. package/.next-prod/server/app/api/skills/route.js.nft.json +1 -1
  172. package/.next-prod/server/app/api/snapshots/diff/route.js.nft.json +1 -1
  173. package/.next-prod/server/app/api/snapshots/route.js.nft.json +1 -1
  174. package/.next-prod/server/app/api/terminal/aliases/route.js.nft.json +1 -1
  175. package/.next-prod/server/app/api/terminal/autocomplete/route.js.nft.json +1 -1
  176. package/.next-prod/server/app/api/terminal/bubble-order/route.js.nft.json +1 -1
  177. package/.next-prod/server/app/api/terminal/env/route.js.nft.json +1 -1
  178. package/.next-prod/server/app/api/terminal/history/route.js.nft.json +1 -1
  179. package/.next-prod/server/app/api/version/route.js.nft.json +1 -1
  180. package/.next-prod/server/app/apps/[...path]/route.js +1 -1
  181. package/.next-prod/server/app/apps/[...path]/route.js.nft.json +1 -1
  182. package/.next-prod/server/app/favicon.ico/route.js.nft.json +1 -1
  183. package/.next-prod/server/app/m/page.js +1 -1
  184. package/.next-prod/server/app/m/page.js.nft.json +1 -1
  185. package/.next-prod/server/app/m/page_client-reference-manifest.js +1 -1
  186. package/.next-prod/server/app/manifest.webmanifest/route.js.nft.json +1 -1
  187. package/.next-prod/server/app/page.js +1 -1
  188. package/.next-prod/server/app/page.js.nft.json +1 -1
  189. package/.next-prod/server/app/page_client-reference-manifest.js +1 -1
  190. package/.next-prod/server/app/project/page.js +1 -1
  191. package/.next-prod/server/app/project/page.js.nft.json +1 -1
  192. package/.next-prod/server/app/project/page_client-reference-manifest.js +1 -1
  193. package/.next-prod/server/app/review/[id]/page.js +1 -1
  194. package/.next-prod/server/app/review/[id]/page.js.nft.json +1 -1
  195. package/.next-prod/server/app/review/[id]/page_client-reference-manifest.js +1 -1
  196. package/.next-prod/server/app-paths-manifest.json +1 -1
  197. package/.next-prod/server/chunks/3286.js +406 -270
  198. package/.next-prod/server/chunks/{9004.js → 3641.js} +2 -2
  199. package/.next-prod/server/chunks/516.js +1 -0
  200. package/.next-prod/server/chunks/5750.js +16 -10
  201. package/.next-prod/server/chunks/6495.js +1 -1
  202. package/.next-prod/server/chunks/6548.js +1 -1
  203. package/.next-prod/server/chunks/816.js +2 -0
  204. package/.next-prod/server/chunks/841.js +12 -10
  205. package/.next-prod/server/chunks/9352.js +4 -4
  206. package/.next-prod/server/chunks/9830.js +1 -1
  207. package/.next-prod/server/middleware-build-manifest.js +1 -1
  208. package/.next-prod/server/pages/404.html +2 -2
  209. package/.next-prod/server/pages/500.html +1 -1
  210. package/.next-prod/server/server-reference-manifest.json +1 -1
  211. package/.next-prod/static/chunks/1157-164bcf046a0a9b15.js +32 -0
  212. package/.next-prod/static/chunks/1372-d16d68b1b03a345a.js +15 -0
  213. package/.next-prod/static/chunks/{2653-3f8bfa49727caa72.js → 4333-9e6709e96d3ec973.js} +7 -7
  214. package/.next-prod/static/chunks/{225-75b2e22f496b56a1.js → 6160-958532439d324185.js} +2 -2
  215. package/.next-prod/static/chunks/6784-09dffdb0e8ca7fb3.js +381 -0
  216. package/.next-prod/static/chunks/app/{layout-fef0d44c94fcb415.js → layout-ab52fc1a83af968b.js} +1 -1
  217. package/.next-prod/static/chunks/app/m/page-7dcf56a65528216f.js +1 -0
  218. package/.next-prod/static/chunks/app/{page-824be00a023f34d7.js → page-5de8b6584e328a6f.js} +1 -1
  219. package/.next-prod/static/chunks/app/project/{page-824be00a023f34d7.js → page-5de8b6584e328a6f.js} +1 -1
  220. package/.next-prod/static/css/83c134c954630942.css +1 -0
  221. package/.next-prod/trace +14 -14
  222. package/.next-prod/trace-build +1 -1
  223. package/README.md +1 -1
  224. package/README.zh.md +1 -1
  225. package/apps/devkit/app.jsx +730 -0
  226. package/apps/devkit/index.html +178 -0
  227. package/apps/devkit/md5.js +98 -0
  228. package/dist/{chunk-L5TWVHYB.mjs → chunk-B5LXMESP.mjs} +19 -9
  229. package/dist/{chunk-YEZ3U3F2.mjs → chunk-CLPJYIFC.mjs} +1 -1
  230. package/dist/{chunk-RSRPFWNL.mjs → chunk-TU3GL2HK.mjs} +1 -27
  231. package/dist/httpApi.mjs +2 -2
  232. package/dist/scheduledTasks.mjs +685 -462
  233. package/dist/{server-UZPDELUK.mjs → server-2UJDVZ32.mjs} +2 -2
  234. package/dist/wsServer.mjs +5 -4
  235. package/package.json +1 -1
  236. package/public/agent-icons/claude.svg +1 -0
  237. package/public/agent-icons/codex.svg +1 -0
  238. package/public/agent-icons/deepseek.svg +1 -0
  239. package/public/agent-icons/glm.svg +1 -0
  240. package/public/agent-icons/kimi.svg +1 -0
  241. package/public/agent-icons/ollama.svg +1 -0
  242. package/.next-prod/server/chunks/151.js +0 -1
  243. package/.next-prod/static/chunks/1372-d94b8f9ed5cd2713.js +0 -15
  244. package/.next-prod/static/chunks/6784-ec12c61b77c64dc0.js +0 -381
  245. package/.next-prod/static/chunks/7482-3ffe7da02d3e7f45.js +0 -26
  246. package/.next-prod/static/chunks/app/m/page-aff83d3f5f4047c5.js +0 -1
  247. package/.next-prod/static/css/a82580b2376d7cae.css +0 -1
  248. /package/.next-prod/static/{F9dxCcbBY_bNPY4fxWaWe → pgteRwI6FQcp683smeSNO}/_buildManifest.js +0 -0
  249. /package/.next-prod/static/{F9dxCcbBY_bNPY4fxWaWe → pgteRwI6FQcp683smeSNO}/_ssgManifest.js +0 -0
@@ -459,348 +459,484 @@ Finally emit [Overall recap: end-to-end interaction verification + total deliver
459
459
  - **Recap over interruption**: one final review > mid-flow breaks
460
460
  `},html:{zh:`---
461
461
  name: html
462
- description: "生成可交互的 HTML 应用:内置 cockpit bash SDK,执行命令取/改数据"
463
- argument-hint: "描述你想要的 HTML 应用"
462
+ description: "生成可交互的本地 React 应用:内置 cockpit bash SDK,执行命令取/改数据"
463
+ argument-hint: "描述你想要的应用"
464
464
  ---
465
465
 
466
- # 生成 Cockpit HTML 应用
466
+ # 生成 Cockpit 应用(本地 React 页面)
467
467
 
468
- 一个能取/改数据的 HTML 应用:预览时注入了全局 \`window.cockpit\` SDK —— 本质就是 Bash
469
- 工具暴露给页面,页面按钮能 \`curl\`、读写文件、跑脚本。
468
+ 一个**本地应用**,不是一次性页面:预览时注入了全局 \`window.cockpit\` SDK —— 本质就是 Bash 工具
469
+ 暴露给页面,按钮能 \`curl\`、读写文件、跑脚本;数据落盘在 app 目录里,重开即用。
470
470
 
471
- **默认用 React 写**(下面的本地零构建栈,productized、AI 最熟,适合有状态 / 多视图 /
472
- 表单);只有**极简单的单视图、或纯静态一次性页**才退回单文件内联 HTML。两种模式共用下面
473
- 这套契约。
471
+ 零构建:React / Babel 从同源的 \`/html-lib\` 加载,不打包、不装依赖、离线可用。
474
472
 
475
- ## 流程:先对齐,确认后再写(重要)
473
+ ## 一、流程:先对齐,确认后再写(重要)
476
474
 
477
475
  **别一上来就写文件**,分三步:
478
476
 
479
- 1. **对齐需求** —— 先复述你对这个 HTML 应用的理解(做什么、用 React 还是单文件、数据从哪来、
480
- 关键功能与交互)。仅当需求有歧义或存在多种合理解读时才回问;已经清楚就直接给一段简短
481
- 的理解摘要,不必逐条追问。
482
- 2. **告知存放目录** —— 按下面「存放位置」规则定出 \`<目录>/<name>/\`,明确告诉用户准备存哪、
483
- 叫什么名;用户可否决或改目录 / 改名。
484
- 3. **确认后才写** —— 等用户明确同意(确认 / 开始 / 写吧 / go 等任意肯定表示)再 \`Write\`
485
- 落盘。确认之前只讨论、不写文件。
477
+ 1. **对齐需求** —— 先复述你对这个应用的理解(做什么、数据从哪来、关键功能与交互)。仅当需求有
478
+ 歧义或存在多种合理解读时才回问;已经清楚就直接给一段简短的理解摘要,不必逐条追问。
479
+ 2. **告知存放目录** —— 定出 \`<目录>/<name>/\`,明确告诉用户准备存哪、叫什么名;用户可否决或改:
480
+ - \`<name>\` **一律由你按需求起名**(短横线小写,与 \`cockpit-name\` 一致),用户只描述需求、
481
+ 不负责起名。
482
+ - 用户在需求里给了目录 \`<用户给的目录>/<name>/\`;没给 **当前聊天工作目录**(本次会话的
483
+ cwd)下的 \`<name>/\`。
484
+ - 一个 app 的所有文件都在这个目录里,数据按第四节分到 \`cache/\` \`state/\` \`out/\`。别把文件
485
+ 散落到别处,也别自造 \`.cockpit-apps\` 之类目录。
486
+ 3. **确认后才写** —— 等用户明确同意(确认 / 开始 / 写吧 / go 等任意肯定表示)再 \`Write\` 落盘。
487
+ 确认之前只讨论、不写文件。
486
488
 
487
- 即便用户在 \`/html\` 后已经把需求和目录都写清楚了,也要先停一轮,给出「理解 + 目录 + 一句
488
- 『确认就开始写?』」等待确认——但这一轮要**轻量**,别反复追问。
489
+ 即便用户在 \`/html\` 后已经把需求和目录都写清楚了,也要先停一轮,给出「理解 + 目录 + 一句『确认
490
+ 就开始写?』」等待确认——但这一轮要**轻量**,别反复追问。
489
491
 
490
- ## 契约:window.cockpit(两模式共用,只此一处)
492
+ ## 二、骨架:三个文件,照抄
491
493
 
492
- 页面加载时 SDK 已就绪,无需引入任何库:
493
-
494
- - \`cockpit.cwd: string\` —— 当前文件所在目录,相对路径命令默认在此执行。
495
- - \`cockpit.bash(command, opts?)\` —— 执行一条 bash 命令(\`command\` 是整条 shell 串):
496
- - **前台**(默认,短/离散命令)→ \`Promise<{ stdout, stderr, exitCode }>\`,\`await\` 后一次性拿全部输出。
497
- - **后台**(\`opts.background: true\`,长/实时命令)→ \`{ kill() }\`,经回调流式输出:
498
- \`opts = { background:true, cwd?, onOutput?, onStderr?, onExit?, onError? }\`。大/持续输出必须用后台(前台全缓存在内存)。
499
-
500
- 写这类页面必须守的几条:
501
-
502
- 1. **数据只走 \`cockpit.bash\`**(\`curl\` 取、\`curl -X POST\` 改、或读写文件),**绝不用
503
- \`fetch(外部URL)\`** —— 同源沙箱会 CORS 拦截。
504
- 2. **两类失败都要处理**(前台):\`try/catch\` 抓到的**抛异常 = spawn/基建失败**(坏路径、
505
- 连接断);没抛但 **\`exitCode !== 0\` = 命令跑了但失败**,展示 \`stderr\` 或 stdout 里的错误
506
- 体,别只写"失败"。后台对应 \`onError\`(基建)+ \`onExit\` 非零 \`code\`(命令)。
507
- 3. **\`command\` 是 shell 串**:动态值/用户输入拼进去前必须校验或转义(shell 元字符会注入);
508
- 能用固定命令就别裸拼。
509
- 4. **资源引用**:同级相对(\`./app.jsx\`、\`./style.css\`、图片)和 CDN 绝对 URL 都能加载;根
510
- 相对(\`/assets/x.css\`)会 404。
511
-
512
- meta 头(务必写上,供 HTML 面板做卡片、console \`/name\` 打开):
494
+ \`index.html\` —— 外壳固定这样写,别自由发挥:
513
495
 
514
496
  \`\`\`html
515
- <title>页面标题</title>
516
- <meta name="cockpit-name" content="short-name"> <!-- /name 短名,唯一、只用字母数字-_ -->
517
- <meta name="description" content="一句话说明">
518
- <meta name="cockpit-icon" content="🔍"> <!-- emoji 或图标 url,可选 -->
519
- <meta name="cockpit-theme" content="auto"> <!-- 启用可拖动的亮/暗切换按钮;auto=首次跟随系统,可填 light/dark -->
497
+ <!doctype html>
498
+ <html>
499
+ <head>
500
+ <meta charset="utf-8">
501
+ <title>页面标题</title>
502
+ <meta name="cockpit-name" content="short-name"> <!-- /name 短名,唯一、只用字母数字-_ -->
503
+ <meta name="description" content="一句话说明">
504
+ <meta name="cockpit-icon" content="🔍"> <!-- emoji 或图标 url,可选 -->
505
+ <meta name="cockpit-theme" content="auto"> <!-- 亮/暗切换,见第五节 -->
506
+ <link rel="stylesheet" href="/html-lib/theme.css">
507
+ </head>
508
+ <body>
509
+ <div id="root"></div>
510
+ <script src="/html-lib/react.production.min.js"></script>
511
+ <script src="/html-lib/react-dom.production.min.js"></script>
512
+ <script src="/html-lib/babel.min.js"></script>
513
+ <script>
514
+ (async () => {
515
+ const src = await (await fetch('./app.jsx')).text();
516
+ // 必须 classic runtime:默认 automatic 会注入 import react/jsx-runtime,
517
+ // 作为普通脚本注入会报 "Cannot use import statement outside a module"
518
+ const { code } = Babel.transform(src, { presets: [['react', { runtime: 'classic' }]] });
519
+ const s = document.createElement('script'); s.textContent = code; document.body.appendChild(s);
520
+ })();
521
+ </script>
522
+ </body>
523
+ </html>
520
524
  \`\`\`
521
525
 
522
- ## 默认:React(本地零构建栈)
526
+ meta 头四行都要写:HTML 面板靠它做卡片,console 靠 \`cockpit-name\` 用 \`/name\` 打开。
523
527
 
524
- 外壳固定这样写,逻辑拆到同级 \`./app.jsx\`:
525
-
526
- \`\`\`html
527
- <div id="root"></div>
528
- <script src="/html-lib/react.production.min.js"></script>
529
- <script src="/html-lib/react-dom.production.min.js"></script>
530
- <script src="/html-lib/babel.min.js"></script>
531
- <script>
532
- (async () => {
533
- const src = await (await fetch('./app.jsx')).text();
534
- // 必须 classic runtime:默认 automatic 会注入 import react/jsx-runtime,
535
- // 作为普通脚本注入会报 "Cannot use import statement outside a module"
536
- const { code } = Babel.transform(src, { presets: [['react', { runtime: 'classic' }]] });
537
- const s = document.createElement('script'); s.textContent = code; document.body.appendChild(s);
538
- })();
539
- </script>
540
- \`\`\`
541
-
542
- - \`/html-lib\` 是 Cockpit 本地托管(同源、离线可用,无需 CDN)。
543
- - \`app.jsx\` 用全局 \`React\`(\`const { useState } = React;\`),末尾
544
- \`ReactDOM.createRoot(document.getElementById('root')).render(<App/>)\`。
545
- - 样式默认套 **Cockpit 主题**:\`<link rel="stylesheet" href="/html-lib/theme.css">\`,直接用
546
- 语义变量(\`var(--background)\` / \`--foreground\` / \`--card\` / \`--brand\` / \`--muted-foreground\` /
547
- \`--border\` / \`--destructive\`;半透明用 \`hsl(var(--green-9) / .12)\`)。上面 meta 头里的
548
- \`cockpit-theme\` 让预览带上一个浮动亮/暗切换按钮(默认右上角,用户可拖动吸附到任意一角,
549
- 所以不必为它在右上角留空位);**默认跟随 Cockpit 宿主主题**(Cockpit 外打开时按
550
- meta 的 auto/light/dark),手动切换后**按 app 记住选择(跨刷新,优先于跟随)**,也可调
551
- \`cockpit.toggleTheme()\`。**别自拍调色板、别上 Tailwind**;app 特有细节再补一小段 \`<style>\`。
552
-
553
- React 里取数同样走 \`cockpit.bash\`:
528
+ \`app.jsx\` —— 全局 \`React\`,末尾 render。**打开只读缓存,点刷新才回源**:
554
529
 
555
530
  \`\`\`jsx
531
+ const { useState, useEffect, useCallback } = React;
532
+
556
533
  function App() {
557
- const [stars, setStars] = React.useState(null);
558
- const load = async () => {
559
- const { stdout, exitCode } = await cockpit.bash("curl -s https://api.github.com/repos/Surething-io/cockpit");
560
- setStars(exitCode === 0 ? JSON.parse(stdout).stargazers_count : '出错');
561
- };
562
- return <button onClick={load}>⭐ {stars ?? '加载'}</button>;
534
+ const [data, setData] = useState(null);
535
+ const [busy, setBusy] = useState(false);
536
+ const [err, setErr] = useState(null);
537
+
538
+ const load = useCallback(async (refresh) => {
539
+ setBusy(true); setErr(null);
540
+ try {
541
+ const arg = btoa(JSON.stringify({ refresh: !!refresh })); // 结构化入参走 base64
542
+ const { stdout, stderr, exitCode } = await cockpit.bash(\`node ./api.mjs \${arg}\`);
543
+ if (exitCode !== 0) throw new Error(stderr || '脚本失败'); // 命令跑了但失败
544
+ const d = JSON.parse(stdout);
545
+ if (d.error && !d.stale) throw new Error(d.error);
546
+ setData(d);
547
+ if (d.stale) setErr(d.error); // 有旧数据可显示
548
+ } catch (e) {
549
+ setErr(e.message); // 抛异常 = spawn/基建失败。已有 data 就留着,别清成错误页
550
+ } finally { setBusy(false); }
551
+ }, []);
552
+
553
+ useEffect(() => { load(false); }, [load]); // 打开:只读缓存,0 次回源
554
+
555
+ if (!data) return <div>{err ?? '加载中…'}</div>;
556
+ return (
557
+ <div>
558
+ <span>{data.cached ? '缓存' : '已更新'} \xb7 {new Date(data.fetchedAt).toLocaleString('zh-CN')}</span>
559
+ <button disabled={busy} onClick={() => load(true)}>{busy ? '刷新中…' : '刷新'}</button>
560
+ {err && <em>刷新失败,下面是旧数据:{err}</em>}
561
+ {/* 渲染 data */}
562
+ </div>
563
+ );
563
564
  }
564
- \`\`\`
565
565
 
566
- ## 后端:复杂逻辑写成脚本(CGI 风格,两模式通用)
566
+ ReactDOM.createRoot(document.getElementById('root')).render(<App />);
567
+ \`\`\`
567
568
 
568
- 命令很短就内联在 \`cockpit.bash("...")\`;逻辑复杂(多步、解析、循环、要库、写库)时,把后端
569
- 写成同目录**脚本文件**,\`cockpit.bash\` 调它——HTML/JSX 管展示,脚本当后端处理器。
569
+ \`api.mjs\` —— 后端处理器:取数、解析、落盘都在这里,stdout 吐一个 JSON:
570
570
 
571
- - **显式解释器**:\`cockpit.bash("node ./api.mjs")\` / \`python3 ./api.py\` / \`bash ./api.sh\`
572
- (免 \`chmod +x\`);\`node\`/\`python3\` 拿不准就先 \`command -v\` 探测,退回 shell。
573
- - **传参防注入**:结构化入参用 **base64** 传(前端 \`btoa(...)\`、脚本
574
- \`Buffer.from(arg,'base64')\`),别拼字符串;写库用**参数化**语句。
575
- - 脚本用 stdout 返回 JSON,前端 \`JSON.parse\`。先 \`Write\` 出脚本,再在页面调它。
571
+ \`\`\`js
572
+ import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
573
+ import { join, dirname } from 'node:path';
574
+ import { fileURLToPath } from 'node:url';
576
575
 
577
- ## 退回:单文件 HTML(极简 / 纯静态一次性页)
576
+ const DIR = dirname(fileURLToPath(import.meta.url));
577
+ const CACHE = join(DIR, 'cache/data.json');
578
+ const VERSION = 1; // payload 结构一改就 bump,否则老缓存喂给新前端
578
579
 
579
- 不需要状态就别上 React,直接内联;取数与错误处理同上。
580
+ const args = JSON.parse(Buffer.from(process.argv[2] || '', 'base64').toString() || '{}');
580
581
 
581
- 前台(点按钮拉数据):
582
+ function readCache() {
583
+ if (!existsSync(CACHE)) return null;
584
+ const c = JSON.parse(readFileSync(CACHE, 'utf8'));
585
+ return c.v === VERSION ? c : null; // 版本不符 = 作废,当没有
586
+ }
587
+ function writeCache(payload) {
588
+ mkdirSync(dirname(CACHE), { recursive: true });
589
+ const tmp = \`\${CACHE}.\${process.pid}.tmp\`; // 原子写:中途被杀不会留下半个 JSON
590
+ writeFileSync(tmp, JSON.stringify({ v: VERSION, fetchedAt: new Date().toISOString(), payload }));
591
+ renameSync(tmp, CACHE);
592
+ }
582
593
 
583
- \`\`\`html
584
- <button id="load">加载</button>
585
- <pre id="out"></pre>
586
- <script>
587
- document.getElementById('load').onclick = async () => {
588
- try {
589
- const { stdout, stderr, exitCode } = await cockpit.bash("curl -s https://api.github.com/repos/Surething-io/cockpit");
590
- if (exitCode !== 0) { out.textContent = '命令失败: ' + stderr; return; }
591
- out.textContent = '⭐ ' + JSON.parse(stdout).stargazers_count;
592
- } catch (e) {
593
- out.textContent = '执行出错: ' + e.message; // spawn / 基建失败
594
- }
595
- };
596
- </script>
594
+ try {
595
+ const hit = args.refresh ? null : readCache();
596
+ if (hit) {
597
+ process.stdout.write(JSON.stringify({ ...hit.payload, cached: true, fetchedAt: hit.fetchedAt }));
598
+ } else {
599
+ const payload = await fetchFresh(args); // ← 你的取数逻辑(curl / 查库 / 跑命令)
600
+ writeCache(payload);
601
+ process.stdout.write(JSON.stringify({ ...payload, cached: false, fetchedAt: new Date().toISOString() }));
602
+ }
603
+ } catch (e) {
604
+ const hit = readCache(); // 刷新失败:回落旧缓存并标注,别让页面空掉
605
+ process.stdout.write(JSON.stringify(hit
606
+ ? { ...hit.payload, cached: true, stale: true, error: e.message, fetchedAt: hit.fetchedAt }
607
+ : { error: e.message }));
608
+ }
597
609
  \`\`\`
598
610
 
599
- 后台(实时日志):
611
+ - 一条命令就够、数据也不值得留(纯查看当前状态)时,\`api.mjs\` 可以省掉,直接
612
+ \`cockpit.bash("curl -s …")\`;但只要数据要留下来,就用这个骨架,别自己发明一套。
613
+ - 换语言同理:\`python3 ./api.py\` / \`bash ./api.sh\`(显式解释器,免 \`chmod +x\`);\`node\` /
614
+ \`python3\` 拿不准就先 \`command -v\` 探测,退回 shell。
615
+ - 缓存 key 要含**影响结果的参数**(时间范围、抓取深度…):把 \`cache/data.json\` 换成
616
+ \`cache/<param>.json\` 或在文件里按 key 分桶,否则会拿 7 天的缓存去渲染 30 天的视图。
600
617
 
601
- \`\`\`html
602
- <pre id="log"></pre>
603
- <script>
604
- const box = document.getElementById('log');
605
- const h = cockpit.bash("tail -f ./build.log", {
606
- background: true,
607
- onOutput: c => { box.textContent += c; },
608
- onExit: code => { box.textContent += '\\n[退出 ' + code + ']'; },
609
- onError: msg => { box.textContent += '\\n[错误 ' + msg + ']'; },
610
- });
611
- // 需要时 h.kill()
612
- </script>
613
- \`\`\`
618
+ ## 三、契约:window.cockpit
614
619
 
615
- ## 存放位置
620
+ 页面加载时 SDK 已就绪,无需引入任何库:
616
621
 
617
- 一个 app 的所有文件(\`index.html\` + \`app.jsx\` + \`api.mjs\` …)放进同一个 \`<name>/\`
618
- 子目录。\`<name>\` **一律由你按需求起名**(短横线小写,与 \`cockpit-name\` 一致),用户只
619
- 描述需求、不负责起名。目标目录按下面规则确定:
622
+ - \`cockpit.cwd: string\` —— 当前文件所在目录,相对路径命令默认在此执行。
623
+ - \`cockpit.bash(command, opts?)\` —— 执行一条 bash 命令(\`command\` 是整条 shell 串):
624
+ - **前台**(默认,短/离散命令)→ \`Promise<{ stdout, stderr, exitCode }>\`,\`await\` 后一次拿全部输出。
625
+ - **后台**(\`opts.background: true\`,长/实时命令)→ \`{ kill() }\`,经回调流式输出。
626
+ 大/持续输出**必须**用后台——前台会把输出全缓存在内存里。
627
+
628
+ \`\`\`js
629
+ const h = cockpit.bash("npm run build", {
630
+ background: true,
631
+ onOutput: c => append(c),
632
+ onStderr: c => append(c),
633
+ onExit: code => append('\\n[退出 ' + code + ']'), // 命令跑完了,code 非零 = 失败
634
+ onError: msg => append('\\n[错误 ' + msg + ']'), // spawn / 基建失败
635
+ });
636
+ // 组件卸载或用户中止时 h.kill()
637
+ \`\`\`
620
638
 
621
- - 用户在需求里给了目录 → 存到 \`<用户给的目录>/<name>/\`
622
- - 用户没给 → 存到**当前聊天工作目录**(本次会话的 cwd)下的 \`<name>/\`
639
+ 必守的几条:
623
640
 
624
- 别把文件散落到别处或自造 \`.cockpit-apps\` 之类目录。这个目录就是上面「流程」第 2 步要告知用户的;**用户确认后**再用 \`Write\` 写出,用户点击
625
- 即可预览并交互。`,en:`---
626
- name: html
627
- description: "Generate an interactive small app with the built-in cockpit bash SDK to fetch/update data"
628
- argument-hint: "describe the small app you want"
629
- ---
641
+ 1. **数据只走 \`cockpit.bash\`**(\`curl\` 取、\`curl -X POST\` 改、或读写文件),**绝不用
642
+ \`fetch(外部URL)\`** —— 同源沙箱会 CORS 拦截。(\`fetch('./app.jsx')\` 这种同目录相对请求没问题。)
643
+ 2. **两类失败都要处理**:\`try/catch\` 抓到的**抛异常 = spawn/基建失败**(坏路径、连接断);没抛但
644
+ **\`exitCode !== 0\` = 命令跑了但失败**,要展示 \`stderr\` stdout 里的错误体,别只写"失败"。
645
+ 后台对应 \`onError\`(基建)+ \`onExit\` 非零 \`code\`(命令)。
646
+ 3. **\`command\` 是 shell 串**:动态值/用户输入拼进去前必须校验或转义(shell 元字符会注入);结构化
647
+ 入参一律走 base64(见骨架),别拼字符串;写库用**参数化**语句。
648
+ 4. **资源引用**:同级相对(\`./app.jsx\`、\`./style.css\`、图片)和 CDN 绝对 URL 都能加载;根相对
649
+ (\`/assets/x.css\`)会 404 —— 服务端只托管 \`/html-lib/*\` 和 \`/apps/*\`。
630
650
 
631
- # Build a Cockpit small app
651
+ ## 四、本地数据:三类目录
632
652
 
633
- A small app that can fetch/update data: the preview injects a global
634
- \`window.cockpit\` SDK — essentially the Bash tool exposed to the page, so buttons can
635
- \`curl\`, read/write files, and run scripts.
653
+ 数据要落盘、要重开即用——**别每次打开都重新拉一遍**。所有持久化走 \`api.mjs\`,app 目录下按
654
+ 「能不能重建」分三类,别平铺在一起:
636
655
 
637
- **Default to React** (the local zero-build stack below productized, most AI-familiar,
638
- ideal for stateful / multi-view / form apps); fall back to single-file inline HTML only
639
- for a **trivial single view or a purely static one-off**. Both modes share the one
640
- contract below.
656
+ - \`cache/\` —— 能重建的(接口返回、原始 dump、派生索引)。删了只是慢。**整目录写进 \`.gitignore\`**。
657
+ - \`state/\` —— 重建不出来的(每日快照、用户手工维护的清单)。源接口给不了第二次,删了永久丢。**入库**。
658
+ - \`out/\` —— 给人看的产物(报告、导出),按日期命名,留档。
641
659
 
642
- ## Flow: align first, write only after confirmation (important)
660
+ 分类的意义全在 git 策略上:目录分了但 \`cache/\` ignore、\`state/\` 没入库,等于没分。
643
661
 
644
- **Don't write files up front.** Three steps:
662
+ 骨架里已经做掉的(版本号、原子写、\`refresh\` 参数、取数时间戳、失败回落旧数据)不再重复,剩下这些
663
+ 要你自己守:
645
664
 
646
- 1. **Align on the requirement** restate your understanding of the app (what it does,
647
- React vs single-file, where data comes from, key features/interactions). Ask back only
648
- when the requirement is ambiguous or has several reasonable readings; if it's already
649
- clear, just give a short understanding summary — don't interrogate.
650
- 2. **Tell the storage directory** resolve \`<dir>/<name>/\` per the "Where to store" rules
651
- below and tell the user exactly where it'll go and what it's named; the user may reject
652
- or change the directory / name.
653
- 3. **Write only after confirmation** — \`Write\` the files only once the user clearly agrees
654
- (confirm / start / go / "write it" — any affirmative). Until then, only discuss — write
655
- nothing to disk.
665
+ 1. **会增长的目录必须有上限,并在脚本里执行**(留 N / X MB 就清)。只写在注释里的上限等于
666
+ 没有,没人看管的 dump 目录涨到 GB 级是常态。
667
+ 2. **落盘文件里不写绝对路径**(存相对 app 目录的路径),否则目录一挪索引全废;含邮箱等隐私的落盘
668
+ 一律 ignore。
669
+ 3. **留一个清空入口**(\`api.mjs\` 加个 \`clear_cache\` 模式 + 界面按钮),否则用户只能去文件管理器里
670
+ 猜哪个文件能删。
671
+ 4. 要用别的 app 的凭据 / 工具脚本时走 \`../<app>/\` 相对引用,并**先探测存在再用**;失败给一句照着做
672
+ 就能修的提示(「去 xxx 重新登录」),别把原始报错甩给用户。
656
673
 
657
- Even if the user already spelled out the requirement and directory in the \`/html\` call,
658
- still stop once to present "understanding + directory + a 'shall I start writing?'" and
659
- wait — but keep this round **lightweight**, don't re-interrogate.
674
+ ## 五、主题
660
675
 
661
- ## Contract: window.cockpit (shared by both modes, stated once)
676
+ 样式默认套 **Cockpit 主题**(\`index.html\` 里已经 link \`/html-lib/theme.css\`)。**配色动手前先拉
677
+ 一次这份文件**,它是变量的唯一真源,别凭记忆猜变量名:
662
678
 
663
- The SDK is ready on load — no library to import:
679
+ \`\`\`bash
680
+ curl -fsS "{{BASE_URL}}/html-lib/theme.css"
681
+ \`\`\`
664
682
 
665
- - \`cockpit.cwd: string\` directory of the current file; relative-path commands run here.
666
- - \`cockpit.bash(command, opts?)\` run one bash command (\`command\` is a raw shell string):
667
- - **Foreground** (default, short/discrete) → \`Promise<{ stdout, stderr, exitCode }>\`; \`await\` for the full output at once.
668
- - **Background** (\`opts.background: true\`, long/live) → \`{ kill() }\`, streaming via callbacks:
669
- \`opts = { background:true, cwd?, onOutput?, onStderr?, onExit?, onError? }\`. Use background for large/continuous output (foreground buffers it all in memory).
683
+ - \`:root\` 段是亮色取值、\`.dark\` 段是暗色。原始色阶(\`--slate-*\` / \`--teal-*\` / \`--red-*\` …)存的是
684
+ HSL 分量,所以半透明直接写 \`hsl(var(--green-9) / .12)\`。
685
+ - 前景色成对取(\`--card\`/\`--card-foreground\` …),**只有 \`--brand\` 是坑**:\`--brand-foreground\`
686
+ 同色系色调,压在 \`--brand\` 填充上仅 1.5:1;**brand 填充上的文字改用 \`var(--background)\`**。
687
+ - \`cockpit-theme\` meta 让预览带一个浮动亮/暗切换按钮(默认右上角,用户可拖动吸附到任意一角,所以
688
+ 不必为它在右上角留空位);**默认跟随 Cockpit 宿主主题**(Cockpit 外打开时按 meta 的
689
+ auto/light/dark),手动切换后**按 app 记住选择(跨刷新,优先于跟随)**,也可调
690
+ \`cockpit.toggleTheme()\`。
691
+ - **别自拍调色板、别上 Tailwind**;app 特有细节再补一小段 \`<style>\`。`,en:`---
692
+ name: html
693
+ description: "Generate an interactive local React app with the built-in cockpit bash SDK to fetch/update data"
694
+ argument-hint: "describe the app you want"
695
+ ---
670
696
 
671
- Rules every such page must follow:
697
+ # Build a Cockpit app (a local React page)
672
698
 
673
- 1. **Data only through \`cockpit.bash\`** (\`curl\` to read, \`curl -X POST\` to write, or
674
- read/write files) **never \`fetch(externalURL)\`**: the same-origin sandbox blocks it via CORS.
675
- 2. **Handle both failure modes** (foreground): a **throw** caught by \`try/catch\` = spawn/infra
676
- failure (bad path, dropped connection); no throw but **\`exitCode !== 0\` = the command ran
677
- and failed** — show \`stderr\` or the error body in stdout, don't just say "failed". Background:
678
- \`onError\` (infra) + a non-zero \`code\` in \`onExit\` (command).
679
- 3. **\`command\` is a shell string**: validate or escape any dynamic/user input before
680
- interpolating it (shell metacharacters inject); prefer a fixed command.
681
- 4. **Resource refs**: relative siblings (\`./app.jsx\`, \`./style.css\`, images) and absolute
682
- CDN URLs load; root-relative (\`/assets/x.css\`) 404s.
699
+ A **local application**, not a one-off page: the preview injects a global \`window.cockpit\`
700
+ SDKessentially the Bash tool exposed to the page, so buttons can \`curl\`, read/write files
701
+ and run scripts; data lands on disk inside the app directory and is there on reopen.
683
702
 
684
- Meta head (always include the HTML panel renders cards from it, the console opens it via \`/name\`):
703
+ Zero build: React / Babel load from the same-origin \`/html-lib\` no bundler, no deps, works offline.
685
704
 
686
- \`\`\`html
687
- <title>Page title</title>
688
- <meta name="cockpit-name" content="short-name"> <!-- unique short name for /name; letters/digits/-_ only -->
689
- <meta name="description" content="one line about the page">
690
- <meta name="cockpit-icon" content="🔍"> <!-- emoji or icon url, optional -->
691
- <meta name="cockpit-theme" content="auto"> <!-- enable the draggable light/dark toggle; auto = first load follows the OS, or light/dark -->
692
- \`\`\`
705
+ ## 1. Flow: align first, write only after confirmation (important)
693
706
 
694
- ## Default: React (local zero-build stack)
707
+ **Don't write files up front.** Three steps:
695
708
 
696
- Use this fixed shell, with the logic in a sibling \`./app.jsx\`:
709
+ 1. **Align on the requirement** — restate your understanding of the app (what it does, where
710
+ data comes from, key features/interactions). Ask back only when the requirement is ambiguous
711
+ or has several reasonable readings; if it's already clear, just give a short summary — don't
712
+ interrogate.
713
+ 2. **Tell the storage directory** — resolve \`<dir>/<name>/\` and tell the user exactly where it
714
+ goes and what it's called; they may reject or change it:
715
+ - **You always pick \`<name>\`** from the request (lowercase kebab-case, matching
716
+ \`cockpit-name\`) — the user only describes the app, they don't name it.
717
+ - The user gave a directory → \`<the given directory>/<name>/\`; none given → \`<name>/\` under
718
+ the **current chat working directory** (this session's cwd).
719
+ - Every file of the app lives in that one directory, with data split into \`cache/\`
720
+ \`state/\` \`out/\` per section 4. Don't scatter files elsewhere or invent a \`.cockpit-apps\`.
721
+ 3. **Write only after confirmation** — \`Write\` the files once the user clearly agrees (confirm /
722
+ start / go / "write it" — any affirmative). Until then, only discuss.
723
+
724
+ Even if the user already spelled out requirement and directory in the \`/html\` call, still stop
725
+ once to present "understanding + directory + a 'shall I start writing?'" and wait — but keep that
726
+ round **lightweight**, don't re-interrogate.
727
+
728
+ ## 2. Skeleton: three files, copy them
729
+
730
+ \`index.html\` — use this fixed shell, don't improvise:
697
731
 
698
732
  \`\`\`html
699
- <div id="root"></div>
700
- <script src="/html-lib/react.production.min.js"></script>
701
- <script src="/html-lib/react-dom.production.min.js"></script>
702
- <script src="/html-lib/babel.min.js"></script>
703
- <script>
704
- (async () => {
705
- const src = await (await fetch('./app.jsx')).text();
706
- // MUST use classic runtime: the default automatic runtime injects an import of
707
- // react/jsx-runtime, which appended as a plain script — throws
708
- // "Cannot use import statement outside a module".
709
- const { code } = Babel.transform(src, { presets: [['react', { runtime: 'classic' }]] });
710
- const s = document.createElement('script'); s.textContent = code; document.body.appendChild(s);
711
- })();
712
- </script>
733
+ <!doctype html>
734
+ <html>
735
+ <head>
736
+ <meta charset="utf-8">
737
+ <title>Page title</title>
738
+ <meta name="cockpit-name" content="short-name"> <!-- unique short name for /name; letters/digits/-_ -->
739
+ <meta name="description" content="one line about the page">
740
+ <meta name="cockpit-icon" content="🔍"> <!-- emoji or icon url, optional -->
741
+ <meta name="cockpit-theme" content="auto"> <!-- light/dark toggle, see section 5 -->
742
+ <link rel="stylesheet" href="/html-lib/theme.css">
743
+ </head>
744
+ <body>
745
+ <div id="root"></div>
746
+ <script src="/html-lib/react.production.min.js"></script>
747
+ <script src="/html-lib/react-dom.production.min.js"></script>
748
+ <script src="/html-lib/babel.min.js"></script>
749
+ <script>
750
+ (async () => {
751
+ const src = await (await fetch('./app.jsx')).text();
752
+ // MUST use classic runtime: the default automatic runtime injects an import of
753
+ // react/jsx-runtime, which — appended as a plain script — throws
754
+ // "Cannot use import statement outside a module".
755
+ const { code } = Babel.transform(src, { presets: [['react', { runtime: 'classic' }]] });
756
+ const s = document.createElement('script'); s.textContent = code; document.body.appendChild(s);
757
+ })();
758
+ </script>
759
+ </body>
760
+ </html>
713
761
  \`\`\`
714
762
 
715
- - \`/html-lib\` is hosted locally by Cockpit (same-origin, offline, no CDN).
716
- - \`app.jsx\` uses the global \`React\` (\`const { useState } = React;\`) and ends with
717
- \`ReactDOM.createRoot(document.getElementById('root')).render(<App/>)\`.
718
- - Style with the **Cockpit theme** by default: \`<link rel="stylesheet" href="/html-lib/theme.css">\`,
719
- then use the semantic vars (\`var(--background)\` / \`--foreground\` / \`--card\` / \`--brand\` /
720
- \`--muted-foreground\` / \`--border\` / \`--destructive\`; translucency via \`hsl(var(--green-9) / .12)\`).
721
- The \`cockpit-theme\` meta above gives the preview a floating light/dark toggle (top-right by
722
- default, draggable to any corner — so don't reserve top-right space for it); it **follows the
723
- Cockpit host theme by default** (outside Cockpit, the meta's auto/light/dark decides), and a manual
724
- toggle is **remembered per app across reloads and overrides the host**, or call \`cockpit.toggleTheme()\`
725
- from your own UI. Don't invent a palette or add Tailwind; add a small \`<style>\` for app-specific bits.
763
+ All four meta lines matter: the HTML panel renders cards from them, and the console opens the
764
+ app via \`cockpit-name\` (\`/name\`).
726
765
 
727
- Fetch data from React the same way, via \`cockpit.bash\`:
766
+ \`app.jsx\` global \`React\`, render at the end. **Opening reads the cache; only Refresh goes out**:
728
767
 
729
768
  \`\`\`jsx
769
+ const { useState, useEffect, useCallback } = React;
770
+
730
771
  function App() {
731
- const [stars, setStars] = React.useState(null);
732
- const load = async () => {
733
- const { stdout, exitCode } = await cockpit.bash("curl -s https://api.github.com/repos/Surething-io/cockpit");
734
- setStars(exitCode === 0 ? JSON.parse(stdout).stargazers_count : 'error');
735
- };
736
- return <button onClick={load}>⭐ {stars ?? 'load'}</button>;
772
+ const [data, setData] = useState(null);
773
+ const [busy, setBusy] = useState(false);
774
+ const [err, setErr] = useState(null);
775
+
776
+ const load = useCallback(async (refresh) => {
777
+ setBusy(true); setErr(null);
778
+ try {
779
+ const arg = btoa(JSON.stringify({ refresh: !!refresh })); // structured args as base64
780
+ const { stdout, stderr, exitCode } = await cockpit.bash(\`node ./api.mjs \${arg}\`);
781
+ if (exitCode !== 0) throw new Error(stderr || 'script failed'); // ran, but failed
782
+ const d = JSON.parse(stdout);
783
+ if (d.error && !d.stale) throw new Error(d.error);
784
+ setData(d);
785
+ if (d.stale) setErr(d.error); // old data is still shown
786
+ } catch (e) {
787
+ setErr(e.message); // a throw = spawn/infra failure. Keep existing data on screen
788
+ } finally { setBusy(false); }
789
+ }, []);
790
+
791
+ useEffect(() => { load(false); }, [load]); // on open: cache only, zero requests
792
+
793
+ if (!data) return <div>{err ?? 'Loading…'}</div>;
794
+ return (
795
+ <div>
796
+ <span>{data.cached ? 'cached' : 'updated'} \xb7 {new Date(data.fetchedAt).toLocaleString()}</span>
797
+ <button disabled={busy} onClick={() => load(true)}>{busy ? 'Refreshing…' : 'Refresh'}</button>
798
+ {err && <em>Refresh failed, showing older data: {err}</em>}
799
+ {/* render data */}
800
+ </div>
801
+ );
737
802
  }
738
- \`\`\`
739
803
 
740
- ## Backend: write complex logic as a script (CGI-style, both modes)
804
+ ReactDOM.createRoot(document.getElementById('root')).render(<App />);
805
+ \`\`\`
741
806
 
742
- Inline short commands in \`cockpit.bash("...")\`. For complex logic (multi-step, parsing,
743
- loops, a library, DB writes), write the backend as a **script file** next to the page and
744
- call it — the HTML/JSX is the frontend, the script is the backend handler.
807
+ \`api.mjs\` the backend handler: fetching, parsing and persistence live here; one JSON on stdout:
745
808
 
746
- - **Explicit interpreter**: \`cockpit.bash("node ./api.mjs")\` / \`python3 ./api.py\` /
747
- \`bash ./api.sh\` (avoids \`chmod +x\`); probe \`node\`/\`python3\` with \`command -v\` if unsure,
748
- and fall back to shell.
749
- - **Pass args safely**: send structured input as **base64** (\`btoa(...)\` on the page,
750
- \`Buffer.from(arg,'base64')\` in the script) rather than string-building; use **parameterized**
751
- statements for DB writes.
752
- - The script returns JSON on stdout; the frontend does \`JSON.parse\`. \`Write\` the script first, then call it.
809
+ \`\`\`js
810
+ import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'node:fs';
811
+ import { join, dirname } from 'node:path';
812
+ import { fileURLToPath } from 'node:url';
753
813
 
754
- ## Fallback: single-file HTML (trivial / static one-off)
814
+ const DIR = dirname(fileURLToPath(import.meta.url));
815
+ const CACHE = join(DIR, 'cache/data.json');
816
+ const VERSION = 1; // bump whenever the payload shape changes, or old cache feeds a new UI
755
817
 
756
- No state? Skip React and inline it. Same data/error handling as above.
818
+ const args = JSON.parse(Buffer.from(process.argv[2] || '', 'base64').toString() || '{}');
757
819
 
758
- Foreground (button fetches data):
820
+ function readCache() {
821
+ if (!existsSync(CACHE)) return null;
822
+ const c = JSON.parse(readFileSync(CACHE, 'utf8'));
823
+ return c.v === VERSION ? c : null; // version mismatch = void it, act as if empty
824
+ }
825
+ function writeCache(payload) {
826
+ mkdirSync(dirname(CACHE), { recursive: true });
827
+ const tmp = \`\${CACHE}.\${process.pid}.tmp\`; // atomic: a kill mid-write leaves no half JSON
828
+ writeFileSync(tmp, JSON.stringify({ v: VERSION, fetchedAt: new Date().toISOString(), payload }));
829
+ renameSync(tmp, CACHE);
830
+ }
759
831
 
760
- \`\`\`html
761
- <button id="load">Load</button>
762
- <pre id="out"></pre>
763
- <script>
764
- document.getElementById('load').onclick = async () => {
765
- try {
766
- const { stdout, stderr, exitCode } = await cockpit.bash("curl -s https://api.github.com/repos/Surething-io/cockpit");
767
- if (exitCode !== 0) { out.textContent = 'command failed: ' + stderr; return; }
768
- out.textContent = '⭐ ' + JSON.parse(stdout).stargazers_count;
769
- } catch (e) {
770
- out.textContent = 'exec error: ' + e.message; // spawn / infra failure
771
- }
772
- };
773
- </script>
832
+ try {
833
+ const hit = args.refresh ? null : readCache();
834
+ if (hit) {
835
+ process.stdout.write(JSON.stringify({ ...hit.payload, cached: true, fetchedAt: hit.fetchedAt }));
836
+ } else {
837
+ const payload = await fetchFresh(args); // ← your fetching logic (curl / DB / commands)
838
+ writeCache(payload);
839
+ process.stdout.write(JSON.stringify({ ...payload, cached: false, fetchedAt: new Date().toISOString() }));
840
+ }
841
+ } catch (e) {
842
+ const hit = readCache(); // failed refresh: fall back to the old cache, labelled
843
+ process.stdout.write(JSON.stringify(hit
844
+ ? { ...hit.payload, cached: true, stale: true, error: e.message, fetchedAt: hit.fetchedAt }
845
+ : { error: e.message }));
846
+ }
774
847
  \`\`\`
775
848
 
776
- Background (live log):
849
+ - When one command is enough and the data isn't worth keeping (just viewing current state), skip
850
+ \`api.mjs\` and call \`cockpit.bash("curl -s …")\` directly. The moment data should survive a
851
+ reopen, use this skeleton rather than inventing your own.
852
+ - Same for other languages: \`python3 ./api.py\` / \`bash ./api.sh\` (explicit interpreter avoids
853
+ \`chmod +x\`); probe \`node\` / \`python3\` with \`command -v\` if unsure and fall back to shell.
854
+ - The cache key must include **every parameter that changes the result** (time range, fetch depth
855
+ …): use \`cache/<param>.json\` or bucket by key inside the file — otherwise a 7-day cache renders
856
+ in the 30-day view.
777
857
 
778
- \`\`\`html
779
- <pre id="log"></pre>
780
- <script>
781
- const box = document.getElementById('log');
782
- const h = cockpit.bash("tail -f ./build.log", {
783
- background: true,
784
- onOutput: c => { box.textContent += c; },
785
- onExit: code => { box.textContent += '\\n[exit ' + code + ']'; },
786
- onError: msg => { box.textContent += '\\n[error ' + msg + ']'; },
787
- });
788
- // h.kill() when done
789
- </script>
790
- \`\`\`
858
+ ## 3. Contract: window.cockpit
791
859
 
792
- ## Where to store it
860
+ The SDK is ready on load — no library to import:
861
+
862
+ - \`cockpit.cwd: string\` — directory of the current file; relative-path commands run here.
863
+ - \`cockpit.bash(command, opts?)\` — run one bash command (\`command\` is a raw shell string):
864
+ - **Foreground** (default, short/discrete) → \`Promise<{ stdout, stderr, exitCode }>\`; \`await\` for the full output at once.
865
+ - **Background** (\`opts.background: true\`, long/live) → \`{ kill() }\`, streaming via callbacks.
866
+ Large/continuous output **must** use background — foreground buffers it all in memory.
867
+
868
+ \`\`\`js
869
+ const h = cockpit.bash("npm run build", {
870
+ background: true,
871
+ onOutput: c => append(c),
872
+ onStderr: c => append(c),
873
+ onExit: code => append('\\n[exit ' + code + ']'), // command finished; non-zero = failed
874
+ onError: msg => append('\\n[error ' + msg + ']'), // spawn / infra failure
875
+ });
876
+ // h.kill() on unmount or when the user aborts
877
+ \`\`\`
793
878
 
794
- Put all of an app's files (\`index.html\` + \`app.jsx\` + \`api.mjs\` …) in a single
795
- \`<name>/\` subdirectory. **You always pick \`<name>\`** from the request (lowercase
796
- kebab-case, matching \`cockpit-name\`) the user only describes the app, they don't
797
- name it. Resolve the target directory as follows:
879
+ Rules every page must follow:
880
+
881
+ 1. **Data only through \`cockpit.bash\`** (\`curl\` to read, \`curl -X POST\` to write, or read/write
882
+ files) **never \`fetch(externalURL)\`**: the same-origin sandbox blocks it via CORS.
883
+ (\`fetch('./app.jsx')\` and other same-directory relative requests are fine.)
884
+ 2. **Handle both failure modes**: a **throw** caught by \`try/catch\` = spawn/infra failure (bad
885
+ path, dropped connection); no throw but **\`exitCode !== 0\` = the command ran and failed** — show
886
+ \`stderr\` or the error body in stdout, don't just say "failed". Background: \`onError\` (infra) +
887
+ a non-zero \`code\` in \`onExit\` (command).
888
+ 3. **\`command\` is a shell string**: validate or escape any dynamic/user input before interpolating
889
+ it (shell metacharacters inject); pass structured input as base64 (see the skeleton) instead of
890
+ string-building; use **parameterized** statements for DB writes.
891
+ 4. **Resource refs**: relative siblings (\`./app.jsx\`, \`./style.css\`, images) and absolute CDN URLs
892
+ load; root-relative (\`/assets/x.css\`) 404s — the server only hosts \`/html-lib/*\` and \`/apps/*\`.
893
+
894
+ ## 4. Local data: three directories
895
+
896
+ Data must land on disk and be there on reopen — **don't refetch everything on every open**. All
897
+ persistence goes through \`api.mjs\`; inside the app directory, split it by "can this be rebuilt?"
898
+ into three directories instead of one flat pile:
899
+
900
+ - \`cache/\` — rebuildable (API responses, raw dumps, derived indexes). Deleting it only costs time. **Gitignore the whole directory.**
901
+ - \`state/\` — NOT rebuildable (daily snapshots, hand-maintained lists). The source can't hand it to you twice; deleted means gone. **Commit it.**
902
+ - \`out/\` — artifacts for humans (reports, exports), date-named, kept.
903
+
904
+ The split only pays off through the git policy: directories separated but \`cache/\` not ignored and
905
+ \`state/\` not committed means nothing was separated.
906
+
907
+ What the skeleton already handles (versioning, atomic writes, the \`refresh\` flag, the fetch
908
+ timestamp, stale fallback) isn't repeated here. What's left is on you:
909
+
910
+ 1. **Any growing directory needs a cap enforced in the script** (keep N entries / prune past X MB).
911
+ A cap that lives only in a comment is not a cap — unattended dump directories reach GB scale.
912
+ 2. **Never write absolute paths into a file on disk** (store paths relative to the app directory),
913
+ or moving the directory voids the index; anything holding private data (emails …) is gitignored.
914
+ 3. **Provide a clear/reset entry point** (a \`clear_cache\` mode in \`api.mjs\` + a button), or the
915
+ user's only option is guessing which file is safe to delete in a file manager.
916
+ 4. To use another app's credentials / helper scripts, reference them as \`../<app>/\` and **probe
917
+ that they exist first**; on failure give one actionable line ("log in again at xxx") rather than
918
+ dumping the raw error.
919
+
920
+ ## 5. Theme
921
+
922
+ Style with the **Cockpit theme** by default (\`index.html\` already links \`/html-lib/theme.css\`).
923
+ **Fetch that file once before you pick any colour** — it is the single source of truth for the
924
+ variables, so don't guess names from memory:
798
925
 
799
- - The user gave a directory in the request → store under \`<the given directory>/<name>/\`.
800
- - The user gave none → store under the **current chat working directory** (this session's cwd), i.e. \`<cwd>/<name>/\`.
926
+ \`\`\`bash
927
+ curl -fsS "{{BASE_URL}}/html-lib/theme.css"
928
+ \`\`\`
801
929
 
802
- Don't scatter files elsewhere or invent a directory like \`.cockpit-apps\`. This directory is what step 2 of the flow above tells the user; **after the user confirms**,
803
- \`Write\` the files; the user clicks to preview and interact.`},cg:{zh:`---
930
+ - \`:root\` holds the light values, \`.dark\` the dark ones. The raw scales (\`--slate-*\` / \`--teal-*\` /
931
+ \`--red-*\` …) store HSL components, so translucency is just \`hsl(var(--green-9) / .12)\`.
932
+ - Take foregrounds in pairs (\`--card\`/\`--card-foreground\` …), **except \`--brand\`**:
933
+ \`--brand-foreground\` is a tint of the same hue and hits only 1.5:1 on a \`--brand\` fill — **use
934
+ \`var(--background)\` for text on a brand fill**.
935
+ - The \`cockpit-theme\` meta gives the preview a floating light/dark toggle (top-right by default,
936
+ draggable to any corner — so don't reserve top-right space for it); it **follows the Cockpit host
937
+ theme by default** (outside Cockpit, the meta's auto/light/dark decides), and a manual toggle is
938
+ **remembered per app across reloads and overrides the host**, or call \`cockpit.toggleTheme()\`.
939
+ - **Don't invent a palette or add Tailwind**; add a small \`<style>\` for app-specific bits.`},cg:{zh:`---
804
940
  name: cg
805
941
  description: "进入项目图谱(CodeGraph)探索模式:用预建的符号 + 调用图 + git 协同视图(search/callers/callees/impact/file/coedit + context/related/risk/affected)追代码、评估影响范围,返回坐标而非源码。"
806
942
  ---
@@ -1779,4 +1915,4 @@ When done, briefly report: skill slug, source path (\`<skills-dir>/<slug>/SKILL.
1779
1915
  `,"utf-8"),f}catch{return null}}(a,j);return k?{name:a,path:k,content:null}:{name:a,path:null,content:j}}(a.cmd,m,r,n);s.set(a.i,function(a,b,c,d,e){if(b.path){let f=e?`[${k(a,d)}\xb7${b.name}]`:`[${b.name}]`;return c?`${f} ${c}`:f}let f=e?`[${k(a,d)}] `:"",g=c?"zh"===d?`,然后:${c}`:`, then: ${c}`:"";return`${f}${b.content??""}${g}`}(a.marker,i,f.join("\n"),m,q)),i.path&&!v.has(i.name)&&(v.add(i.name),u.push({name:i.name,path:i.path}))});let w=[];for(let a=0;a<o.length;a++)t.has(a)||w.push(s.get(a)??o[a]);let x=w.join("\n");if(u.length>0){let a="zh"===m?"请先读取以下 skill 文件,再据此执行:":"Read these skill files first, then act accordingly:",b="zh"===m?":":": ",c=u.map(a=>`- ${a.name}${b}${a.path}`).join("\n");x=`${x}
1780
1916
 
1781
1917
  ${a}
1782
- ${c}`}return x}function k(a,b){return"@"===a?"subagent":"zh"===b?"主会话":"main session"}},38561:(a,b,c)=>{c.d(b,{W:()=>d});let d=new Set(["Read","Grep","Glob","LS","WebSearch","WebFetch","NotebookRead","TodoRead","AskUserQuestion","ExitPlanMode","TodoWrite","Skill"])},82940:(a,b,c)=>{c.d(b,{AD:()=>j,GS:()=>l,HS:()=>m,Si:()=>n,Y6:()=>r,ah:()=>k,e5:()=>q,j$:()=>p,yQ:()=>o});let d=globalThis,e=d.__cockpitRunRegistry??(d.__cockpitRunRegistry=new Map),f=d.__cockpitRunListeners??(d.__cockpitRunListeners=new Map),g=d.__cockpitRunSeqByKey??(d.__cockpitRunSeqByKey=new Map);function h(a){for(let b of(a.seq+=1,a.keys))g.set(b,a.seq);return a.seq}function i(a,b){for(let c of a.keys){let a=f.get(c);if(a)for(let c of a)try{c(b)}catch{}}}function j(a,b,c,d){let f=e.get(a);if(f?.status==="running")return!1;let h=f?.seq??g.get(a)??0;if(f)for(let a of(f.evictTimer&&clearTimeout(f.evictTimer),f.keys))e.delete(a);let i=Date.now();return e.set(a,{keys:new Set([a]),cwd:b,status:"running",seq:h,events:[],startedAt:i,updatedAt:i}),c&&k(a,{type:"user",_human:!0,...d?{_turnId:d}:{},_ts:i,message:{role:"user",content:c}}),!0}function k(a,b){let c=e.get(a);c&&"running"===c.status&&(h(c),c.events.push(b),c.updatedAt=Date.now(),i(c,{seq:c.seq,message:b}))}function l(a,b){let c=e.get(a);c&&(c.sessionId=b,a!==b&&(c.keys.add(b),e.set(b,c)))}function m(a,b="idle"){let c=e.get(a);if(c){if("running"!==c.status){"idle"===c.status&&"error"===b&&(c.status="error");return}c.status=b,c.updatedAt=Date.now(),h(c),i(c,{seq:c.seq,message:{type:"run-ended",status:b}}),c.evictTimer&&clearTimeout(c.evictTimer),c.evictTimer=setTimeout(()=>{for(let a of c.keys)e.delete(a)},6e4)}}function n(a){let b=e.get(a);return b?{status:b.status,seq:b.seq,events:b.events.slice(),startedAt:b.startedAt}:null}function o(a){return e.get(a)?.status==="running"}function p(a){return e.get(a)?.sessionId??null}function q(a,b){let c=e.get(a);c&&(c.abort=b)}function r(a){let b=e.get(a);if(!b)return!1;try{b.abort?.()}catch{}return k(a,{type:"result",subtype:"stopped"}),m(a,"idle"),!0}}};
1918
+ ${c}`}return x}function k(a,b){return"@"===a?"subagent":"zh"===b?"主会话":"main session"}},38561:(a,b,c)=>{c.d(b,{W:()=>d});let d=new Set(["Read","Grep","Glob","LS","WebSearch","WebFetch","NotebookRead","TodoRead","AskUserQuestion","ExitPlanMode","TodoWrite","Skill"])},82940:(a,b,c)=>{c.d(b,{AD:()=>j,GS:()=>l,HS:()=>m,Si:()=>n,Y6:()=>r,ah:()=>k,e5:()=>q,j$:()=>p,yQ:()=>o});let d=globalThis,e=d.__cockpitRunRegistry??(d.__cockpitRunRegistry=new Map),f=d.__cockpitRunListeners??(d.__cockpitRunListeners=new Map),g=d.__cockpitRunSeqByKey??(d.__cockpitRunSeqByKey=new Map);function h(a){for(let b of(a.seq+=1,a.keys))g.set(b,a.seq);return a.seq}function i(a,b){for(let c of a.keys){let a=f.get(c);if(a)for(let c of a)try{c(b)}catch{}}}function j(a,b,c,d){let f=e.get(a);if(f?.status==="running")return!1;let h=f?.seq??g.get(a)??0;if(f)for(let a of(f.evictTimer&&clearTimeout(f.evictTimer),f.keys))e.delete(a);let i=Date.now();return e.set(a,{keys:new Set([a]),cwd:b,status:"running",seq:h,events:[],startedAt:i,updatedAt:i}),c&&k(a,{type:"user",_human:!0,...d?{_turnId:d}:{},_ts:i,message:{role:"user",content:c}}),!0}function k(a,b){let c=e.get(a);if(!c||"running"!==c.status)return;h(c);let d=function(a){if("object"!=typeof a||null===a||"usage_update"!==a.type)return null;let b=a.output_tokens;return"number"==typeof b&&Number.isFinite(b)?Math.max(0,Math.round(b)):null}(b);null!==d&&(c.outputTokens=Math.max(c.outputTokens??0,d)),("object"!=typeof b||null===b||"usage_update"!==b.type)&&c.events.push(b),c.updatedAt=Date.now(),i(c,{seq:c.seq,message:b})}function l(a,b){let c=e.get(a);c&&(c.sessionId=b,a!==b&&(c.keys.add(b),e.set(b,c)))}function m(a,b="idle"){let c=e.get(a);if(c){if("running"!==c.status){"idle"===c.status&&"error"===b&&(c.status="error");return}c.status=b,c.updatedAt=Date.now(),h(c),i(c,{seq:c.seq,message:{type:"run-ended",status:b}}),c.evictTimer&&clearTimeout(c.evictTimer),c.evictTimer=setTimeout(()=>{for(let a of c.keys)e.delete(a)},6e4)}}function n(a){let b=e.get(a);return b?{status:b.status,seq:b.seq,events:b.events.slice(),startedAt:b.startedAt,outputTokens:b.outputTokens}:null}function o(a){return e.get(a)?.status==="running"}function p(a){return e.get(a)?.sessionId??null}function q(a,b){let c=e.get(a);c&&(c.abort=b)}function r(a){let b=e.get(a);if(!b)return!1;try{b.abort?.()}catch{}return k(a,{type:"result",subtype:"stopped"}),m(a,"idle"),!0}}};