@surething/cockpit 1.0.230 → 1.0.232

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 (205) 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/prerender-manifest.json +3 -3
  5. package/.next-prod/server/app/_global-error/page.js.nft.json +1 -1
  6. package/.next-prod/server/app/_global-error/page_client-reference-manifest.js +1 -1
  7. package/.next-prod/server/app/_global-error.html +1 -1
  8. package/.next-prod/server/app/_global-error.rsc +1 -1
  9. package/.next-prod/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next-prod/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  11. package/.next-prod/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  12. package/.next-prod/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next-prod/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next-prod/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next-prod/server/app/_not-found/page.js.nft.json +1 -1
  16. package/.next-prod/server/app/_not-found/page_client-reference-manifest.js +1 -1
  17. package/.next-prod/server/app/_not-found.html +2 -2
  18. package/.next-prod/server/app/_not-found.rsc +3 -3
  19. package/.next-prod/server/app/_not-found.segments/_full.segment.rsc +3 -3
  20. package/.next-prod/server/app/_not-found.segments/_head.segment.rsc +1 -1
  21. package/.next-prod/server/app/_not-found.segments/_index.segment.rsc +3 -3
  22. package/.next-prod/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  23. package/.next-prod/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  24. package/.next-prod/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  25. package/.next-prod/server/app/api/agent/test/route.js.nft.json +1 -1
  26. package/.next-prod/server/app/api/bash/route.js.nft.json +1 -1
  27. package/.next-prod/server/app/api/chat/codex/route.js +1 -1
  28. package/.next-prod/server/app/api/chat/codex/route.js.nft.json +1 -1
  29. package/.next-prod/server/app/api/chat/deepseek/route.js +1 -1
  30. package/.next-prod/server/app/api/chat/deepseek/route.js.nft.json +1 -1
  31. package/.next-prod/server/app/api/chat/kimi/route.js +1 -1
  32. package/.next-prod/server/app/api/chat/kimi/route.js.nft.json +1 -1
  33. package/.next-prod/server/app/api/chat/ollama/route.js +1 -1
  34. package/.next-prod/server/app/api/chat/ollama/route.js.nft.json +1 -1
  35. package/.next-prod/server/app/api/chat/pty-input/route.js.nft.json +1 -1
  36. package/.next-prod/server/app/api/chat/route.js +1 -1
  37. package/.next-prod/server/app/api/chat/route.js.nft.json +1 -1
  38. package/.next-prod/server/app/api/chat/stop/route.js.nft.json +1 -1
  39. package/.next-prod/server/app/api/claude-stats/route.js.nft.json +1 -1
  40. package/.next-prod/server/app/api/commands/route.js.nft.json +1 -1
  41. package/.next-prod/server/app/api/comments/route.js.nft.json +1 -1
  42. package/.next-prod/server/app/api/db/columns/route.js.nft.json +1 -1
  43. package/.next-prod/server/app/api/db/connect/route.js.nft.json +1 -1
  44. package/.next-prod/server/app/api/db/disconnect/route.js.nft.json +1 -1
  45. package/.next-prod/server/app/api/db/export/route.js.nft.json +1 -1
  46. package/.next-prod/server/app/api/db/query/route.js.nft.json +1 -1
  47. package/.next-prod/server/app/api/db/schemas/route.js.nft.json +1 -1
  48. package/.next-prod/server/app/api/dev/spans/route.js.nft.json +1 -1
  49. package/.next-prod/server/app/api/extension/version/route.js.nft.json +1 -1
  50. package/.next-prod/server/app/api/file/route.js.nft.json +1 -1
  51. package/.next-prod/server/app/api/files/blame/route.js.nft.json +1 -1
  52. package/.next-prod/server/app/api/files/clipboard/route.js.nft.json +1 -1
  53. package/.next-prod/server/app/api/files/copy/route.js.nft.json +1 -1
  54. package/.next-prod/server/app/api/files/delete/route.js.nft.json +1 -1
  55. package/.next-prod/server/app/api/files/expanded/route.js.nft.json +1 -1
  56. package/.next-prod/server/app/api/files/index/route.js.nft.json +1 -1
  57. package/.next-prod/server/app/api/files/init/route.js.nft.json +1 -1
  58. package/.next-prod/server/app/api/files/paste/route.js.nft.json +1 -1
  59. package/.next-prod/server/app/api/files/read/route.js.nft.json +1 -1
  60. package/.next-prod/server/app/api/files/readdir/route.js.nft.json +1 -1
  61. package/.next-prod/server/app/api/files/recent/route.js.nft.json +1 -1
  62. package/.next-prod/server/app/api/files/save/route.js.nft.json +1 -1
  63. package/.next-prod/server/app/api/files/search/route.js.nft.json +1 -1
  64. package/.next-prod/server/app/api/files/stat/route.js.nft.json +1 -1
  65. package/.next-prod/server/app/api/files/text/route.js.nft.json +1 -1
  66. package/.next-prod/server/app/api/git/branch-diff/route.js.nft.json +1 -1
  67. package/.next-prod/server/app/api/git/branches/route.js.nft.json +1 -1
  68. package/.next-prod/server/app/api/git/commit-diff/route.js.nft.json +1 -1
  69. package/.next-prod/server/app/api/git/commits/route.js.nft.json +1 -1
  70. package/.next-prod/server/app/api/git/current-branch/route.js.nft.json +1 -1
  71. package/.next-prod/server/app/api/git/diff/route.js.nft.json +1 -1
  72. package/.next-prod/server/app/api/git/discard/route.js.nft.json +1 -1
  73. package/.next-prod/server/app/api/git/stage/route.js.nft.json +1 -1
  74. package/.next-prod/server/app/api/git/status/route.js.nft.json +1 -1
  75. package/.next-prod/server/app/api/git/unstage/route.js.nft.json +1 -1
  76. package/.next-prod/server/app/api/git/worktree/route.js.nft.json +1 -1
  77. package/.next-prod/server/app/api/global-state/route.js +3 -1
  78. package/.next-prod/server/app/api/global-state/route.js.nft.json +1 -1
  79. package/.next-prod/server/app/api/health/route.js.nft.json +1 -1
  80. package/.next-prod/server/app/api/jupyter/load/route.js.nft.json +1 -1
  81. package/.next-prod/server/app/api/jupyter/save/route.js.nft.json +1 -1
  82. package/.next-prod/server/app/api/jupyter/shutdown/route.js.nft.json +1 -1
  83. package/.next-prod/server/app/api/lsp/definition/route.js.nft.json +1 -1
  84. package/.next-prod/server/app/api/lsp/hover/route.js.nft.json +1 -1
  85. package/.next-prod/server/app/api/lsp/references/route.js.nft.json +1 -1
  86. package/.next-prod/server/app/api/lsp/status/route.js.nft.json +1 -1
  87. package/.next-prod/server/app/api/lsp/warmup/route.js.nft.json +1 -1
  88. package/.next-prod/server/app/api/mysql/columns/route.js.nft.json +1 -1
  89. package/.next-prod/server/app/api/mysql/connect/route.js.nft.json +1 -1
  90. package/.next-prod/server/app/api/mysql/disconnect/route.js.nft.json +1 -1
  91. package/.next-prod/server/app/api/mysql/export/route.js.nft.json +1 -1
  92. package/.next-prod/server/app/api/mysql/query/route.js.nft.json +1 -1
  93. package/.next-prod/server/app/api/mysql/schemas/route.js.nft.json +1 -1
  94. package/.next-prod/server/app/api/neo4j/connect/route.js.nft.json +1 -1
  95. package/.next-prod/server/app/api/neo4j/disconnect/route.js.nft.json +1 -1
  96. package/.next-prod/server/app/api/neo4j/query/route.js.nft.json +1 -1
  97. package/.next-prod/server/app/api/neo4j/schema/route.js.nft.json +1 -1
  98. package/.next-prod/server/app/api/note/route.js.nft.json +1 -1
  99. package/.next-prod/server/app/api/ollama/models/route.js.nft.json +1 -1
  100. package/.next-prod/server/app/api/ollama/start/route.js.nft.json +1 -1
  101. package/.next-prod/server/app/api/open-cursor/route.js.nft.json +1 -1
  102. package/.next-prod/server/app/api/open-vscode/route.js.nft.json +1 -1
  103. package/.next-prod/server/app/api/pick-folder/route.js.nft.json +1 -1
  104. package/.next-prod/server/app/api/pinned-sessions/route.js.nft.json +1 -1
  105. package/.next-prod/server/app/api/project-settings/route.js.nft.json +1 -1
  106. package/.next-prod/server/app/api/project-state/route.js +2 -2
  107. package/.next-prod/server/app/api/project-state/route.js.nft.json +1 -1
  108. package/.next-prod/server/app/api/projectGraph/affected/route.js.nft.json +1 -1
  109. package/.next-prod/server/app/api/projectGraph/callees/route.js.nft.json +1 -1
  110. package/.next-prod/server/app/api/projectGraph/callers/route.js.nft.json +1 -1
  111. package/.next-prod/server/app/api/projectGraph/coedit/route.js.nft.json +1 -1
  112. package/.next-prod/server/app/api/projectGraph/context/route.js.nft.json +1 -1
  113. package/.next-prod/server/app/api/projectGraph/file/route.js.nft.json +1 -1
  114. package/.next-prod/server/app/api/projectGraph/file-functions/route.js.nft.json +1 -1
  115. package/.next-prod/server/app/api/projectGraph/impact/route.js.nft.json +1 -1
  116. package/.next-prod/server/app/api/projectGraph/related/route.js.nft.json +1 -1
  117. package/.next-prod/server/app/api/projectGraph/risk/route.js.nft.json +1 -1
  118. package/.next-prod/server/app/api/projectGraph/search/route.js.nft.json +1 -1
  119. package/.next-prod/server/app/api/projects/route.js.nft.json +1 -1
  120. package/.next-prod/server/app/api/push/public-key/route.js.nft.json +1 -1
  121. package/.next-prod/server/app/api/push/subscribe/route.js.nft.json +1 -1
  122. package/.next-prod/server/app/api/push/unsubscribe/route.js.nft.json +1 -1
  123. package/.next-prod/server/app/api/redis/command/route.js.nft.json +1 -1
  124. package/.next-prod/server/app/api/redis/connect/route.js.nft.json +1 -1
  125. package/.next-prod/server/app/api/redis/delete/route.js.nft.json +1 -1
  126. package/.next-prod/server/app/api/redis/disconnect/route.js.nft.json +1 -1
  127. package/.next-prod/server/app/api/redis/get/route.js.nft.json +1 -1
  128. package/.next-prod/server/app/api/redis/keys/route.js.nft.json +1 -1
  129. package/.next-prod/server/app/api/redis/set/route.js.nft.json +1 -1
  130. package/.next-prod/server/app/api/review/[id]/comments/route.js.nft.json +1 -1
  131. package/.next-prod/server/app/api/review/[id]/replies/route.js.nft.json +1 -1
  132. package/.next-prod/server/app/api/review/[id]/route.js.nft.json +1 -1
  133. package/.next-prod/server/app/api/review/identify/route.js.nft.json +1 -1
  134. package/.next-prod/server/app/api/review/order/route.js.nft.json +1 -1
  135. package/.next-prod/server/app/api/review/route.js.nft.json +1 -1
  136. package/.next-prod/server/app/api/review/share-info/route.js.nft.json +1 -1
  137. package/.next-prod/server/app/api/review/users/route.js.nft.json +1 -1
  138. package/.next-prod/server/app/api/scheduled-tasks/route.js +2 -2
  139. package/.next-prod/server/app/api/scheduled-tasks/route.js.nft.json +1 -1
  140. package/.next-prod/server/app/api/services/config/route.js.nft.json +1 -1
  141. package/.next-prod/server/app/api/services/scripts/route.js.nft.json +1 -1
  142. package/.next-prod/server/app/api/session/[sessionId]/fork/route.js.nft.json +1 -1
  143. package/.next-prod/server/app/api/session/[sessionId]/history/route.js +1 -1
  144. package/.next-prod/server/app/api/session/[sessionId]/history/route.js.nft.json +1 -1
  145. package/.next-prod/server/app/api/session-by-path/route.js +1 -1
  146. package/.next-prod/server/app/api/session-by-path/route.js.nft.json +1 -1
  147. package/.next-prod/server/app/api/sessions/projects/[encodedPath]/route.js +1 -1
  148. package/.next-prod/server/app/api/sessions/projects/[encodedPath]/route.js.nft.json +1 -1
  149. package/.next-prod/server/app/api/sessions/projects/route.js.nft.json +1 -1
  150. package/.next-prod/server/app/api/sessions/route.js.nft.json +1 -1
  151. package/.next-prod/server/app/api/settings/route.js.nft.json +1 -1
  152. package/.next-prod/server/app/api/skills/[id]/route.js.nft.json +1 -1
  153. package/.next-prod/server/app/api/skills/content/route.js.nft.json +1 -1
  154. package/.next-prod/server/app/api/skills/route.js.nft.json +1 -1
  155. package/.next-prod/server/app/api/terminal/aliases/route.js.nft.json +1 -1
  156. package/.next-prod/server/app/api/terminal/autocomplete/route.js.nft.json +1 -1
  157. package/.next-prod/server/app/api/terminal/bubble-order/route.js.nft.json +1 -1
  158. package/.next-prod/server/app/api/terminal/env/route.js.nft.json +1 -1
  159. package/.next-prod/server/app/api/terminal/history/route.js.nft.json +1 -1
  160. package/.next-prod/server/app/api/version/route.js.nft.json +1 -1
  161. package/.next-prod/server/app/favicon.ico/route.js.nft.json +1 -1
  162. package/.next-prod/server/app/m/page.js.nft.json +1 -1
  163. package/.next-prod/server/app/m/page_client-reference-manifest.js +1 -1
  164. package/.next-prod/server/app/manifest.webmanifest/route.js.nft.json +1 -1
  165. package/.next-prod/server/app/page.js.nft.json +1 -1
  166. package/.next-prod/server/app/page_client-reference-manifest.js +1 -1
  167. package/.next-prod/server/app/project/page.js.nft.json +1 -1
  168. package/.next-prod/server/app/project/page_client-reference-manifest.js +1 -1
  169. package/.next-prod/server/app/review/[id]/page.js.nft.json +1 -1
  170. package/.next-prod/server/app/review/[id]/page_client-reference-manifest.js +1 -1
  171. package/.next-prod/server/app-paths-manifest.json +1 -1
  172. package/.next-prod/server/chunks/3633.js +1 -1
  173. package/.next-prod/server/chunks/4257.js +3 -3
  174. package/.next-prod/server/chunks/8916.js +1 -1
  175. package/.next-prod/server/chunks/9658.js +15 -5
  176. package/.next-prod/server/chunks/9830.js +1 -0
  177. package/.next-prod/server/middleware-build-manifest.js +1 -1
  178. package/.next-prod/server/pages/404.html +2 -2
  179. package/.next-prod/server/pages/500.html +1 -1
  180. package/.next-prod/server/server-reference-manifest.json +1 -1
  181. package/.next-prod/static/chunks/217-4f8250a204080159.js +15 -0
  182. package/.next-prod/static/chunks/2576-6420f6bad0b8ffb3.js +25 -0
  183. package/.next-prod/static/chunks/6784-8a99d051f03bb100.js +0 -0
  184. package/.next-prod/static/chunks/{9428-5c287b255a9831e6.js → 9428-ffc16bc24c6e0da1.js} +7 -7
  185. package/.next-prod/static/chunks/app/{layout-03c738d41e660f72.js → layout-ac2f1f67df01cc89.js} +1 -1
  186. package/.next-prod/static/chunks/app/page-1f0a6f49656a4c52.js +1 -0
  187. package/.next-prod/static/chunks/app/project/page-1f0a6f49656a4c52.js +1 -0
  188. package/.next-prod/static/css/2a854655f4108c3a.css +1 -0
  189. package/.next-prod/trace +13 -13
  190. package/.next-prod/trace-build +1 -1
  191. package/bin/cock.mjs +10 -0
  192. package/dist/auth.mjs +92 -0
  193. package/dist/{chunk-XW5CKMXI.mjs → chunk-VHFWPI2X.mjs} +64 -3
  194. package/dist/scheduledTasks.mjs +47 -25
  195. package/dist/wsServer.mjs +1 -1
  196. package/package.json +1 -1
  197. package/server.mjs +48 -0
  198. package/.next-prod/static/chunks/217-7dafa608b7290bdc.js +0 -5
  199. package/.next-prod/static/chunks/2576-55f4835e4d7e27a8.js +0 -25
  200. package/.next-prod/static/chunks/6784-b644510a0e060aac.js +0 -0
  201. package/.next-prod/static/chunks/app/page-677439ad5fd63732.js +0 -1
  202. package/.next-prod/static/chunks/app/project/page-677439ad5fd63732.js +0 -1
  203. package/.next-prod/static/css/ee516fc73c03aa88.css +0 -1
  204. /package/.next-prod/static/{9qXUVBkdZdaWcpPpaLd3c → Z3Em1C_kM7x4RjcH7E7v2}/_buildManifest.js +0 -0
  205. /package/.next-prod/static/{9qXUVBkdZdaWcpPpaLd3c → Z3Em1C_kM7x4RjcH7E7v2}/_ssgManifest.js +0 -0
@@ -1 +1 @@
1
- [{"name":"run-webpack","duration":19529043,"timestamp":273243687,"id":14,"parentId":1,"tags":{},"startTime":1782530159452,"traceId":"f63e4386afa40357"},{"name":"run-typescript","duration":17848581,"timestamp":292776722,"id":1602,"parentId":1,"tags":{},"startTime":1782530178985,"traceId":"f63e4386afa40357"},{"name":"static-check","duration":1197212,"timestamp":310663305,"id":1605,"parentId":1,"tags":{},"startTime":1782530196872,"traceId":"f63e4386afa40357"},{"name":"static-generation","duration":6324628,"timestamp":311987963,"id":1877,"parentId":1,"tags":{},"startTime":1782530198196,"traceId":"f63e4386afa40357"},{"name":"collect-build-traces","duration":20344456,"timestamp":311861128,"id":1874,"parentId":1,"tags":{},"startTime":1782530198070,"traceId":"f63e4386afa40357"},{"name":"telemetry-flush","duration":72,"timestamp":332211532,"id":1886,"parentId":1,"tags":{},"startTime":1782530218420,"traceId":"f63e4386afa40357"},{"name":"next-build","duration":59143442,"timestamp":273068174,"id":1,"tags":{"buildMode":"default","version":"16.2.6","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1782530159277,"traceId":"f63e4386afa40357"}]
1
+ [{"name":"run-webpack","duration":21093023,"timestamp":158697893,"id":14,"parentId":1,"tags":{},"startTime":1782900947547,"traceId":"dcd58a4bd7b49062"},{"name":"run-typescript","duration":18807400,"timestamp":179794924,"id":1603,"parentId":1,"tags":{},"startTime":1782900968644,"traceId":"dcd58a4bd7b49062"},{"name":"static-check","duration":1213527,"timestamp":198644615,"id":1606,"parentId":1,"tags":{},"startTime":1782900987494,"traceId":"dcd58a4bd7b49062"},{"name":"static-generation","duration":6335613,"timestamp":200113230,"id":1878,"parentId":1,"tags":{},"startTime":1782900988963,"traceId":"dcd58a4bd7b49062"},{"name":"collect-build-traces","duration":21023731,"timestamp":199858733,"id":1875,"parentId":1,"tags":{},"startTime":1782900988708,"traceId":"dcd58a4bd7b49062"},{"name":"telemetry-flush","duration":92,"timestamp":220886988,"id":1887,"parentId":1,"tags":{},"startTime":1782901009736,"traceId":"dcd58a4bd7b49062"},{"name":"next-build","duration":62368455,"timestamp":158518639,"id":1,"tags":{"buildMode":"default","version":"16.2.6","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1782900947368,"traceId":"dcd58a4bd7b49062"}]
package/bin/cock.mjs CHANGED
@@ -27,6 +27,7 @@ Commands:
27
27
 
28
28
  Options:
29
29
  --port <port> Set server port (default: 3457)
30
+ --token <token> Require this token for remote access (local stays open)
30
31
  --no-open Don't open browser after start
31
32
  -v, --version Show version
32
33
  -h, --help Show this help
@@ -57,6 +58,15 @@ if (portIdx !== -1 && process.argv[portIdx + 1]) {
57
58
  process.argv.splice(portIdx, 2);
58
59
  }
59
60
 
61
+ // Parse --token argument → enable shared-token auth (server.mjs reads
62
+ // COCKPIT_TOKEN). Off entirely when omitted. Local (loopback) access stays
63
+ // exempt; remote access must present the token.
64
+ const tokenIdx = process.argv.indexOf('--token');
65
+ if (tokenIdx !== -1 && process.argv[tokenIdx + 1] && !process.argv[tokenIdx + 1].startsWith('-')) {
66
+ process.env.COCKPIT_TOKEN = process.argv[tokenIdx + 1];
67
+ process.argv.splice(tokenIdx, 2);
68
+ }
69
+
60
70
  // Default prod port
61
71
  if (!process.env.COCKPIT_PORT) {
62
72
  process.env.COCKPIT_PORT = '3457';
package/dist/auth.mjs ADDED
@@ -0,0 +1,92 @@
1
+ import "./chunk-7P6ASYW6.mjs";
2
+
3
+ // src/lib/auth.ts
4
+ import { timingSafeEqual } from "crypto";
5
+ var COOKIE_NAME = "cockpit_token";
6
+ var QUERY_KEY = "token";
7
+ var COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
8
+ function tokenEnabled() {
9
+ return Boolean(process.env.COCKPIT_TOKEN);
10
+ }
11
+ function configuredToken() {
12
+ return process.env.COCKPIT_TOKEN || "";
13
+ }
14
+ function safeEqual(a, b) {
15
+ const ba = Buffer.from(a);
16
+ const bb = Buffer.from(b);
17
+ if (ba.length !== bb.length) return false;
18
+ return timingSafeEqual(ba, bb);
19
+ }
20
+ function isLoopbackAddr(addr) {
21
+ if (!addr) return false;
22
+ return addr === "::1" || addr === "::ffff:127.0.0.1" || addr.startsWith("127.");
23
+ }
24
+ function parseCookies(header) {
25
+ const out = {};
26
+ if (!header) return out;
27
+ for (const part of header.split(";")) {
28
+ const i = part.indexOf("=");
29
+ if (i < 0) continue;
30
+ const k = part.slice(0, i).trim();
31
+ if (!k) continue;
32
+ out[k] = decodeURIComponent(part.slice(i + 1).trim());
33
+ }
34
+ return out;
35
+ }
36
+ function tokenFromQuery(url) {
37
+ try {
38
+ return new URL(url, "http://localhost").searchParams.get(QUERY_KEY) ?? void 0;
39
+ } catch {
40
+ return void 0;
41
+ }
42
+ }
43
+ function stripTokenParam(url) {
44
+ try {
45
+ const u = new URL(url, "http://localhost");
46
+ u.searchParams.delete(QUERY_KEY);
47
+ const qs = u.searchParams.toString();
48
+ return u.pathname + (qs ? `?${qs}` : "") + (u.hash || "");
49
+ } catch {
50
+ return "/";
51
+ }
52
+ }
53
+ function makeCookie(token, secure) {
54
+ const attrs = [
55
+ `${COOKIE_NAME}=${encodeURIComponent(token)}`,
56
+ "Path=/",
57
+ "HttpOnly",
58
+ "SameSite=Lax",
59
+ `Max-Age=${COOKIE_MAX_AGE}`
60
+ ];
61
+ if (secure) attrs.push("Secure");
62
+ return attrs.join("; ");
63
+ }
64
+ function checkAccess(input) {
65
+ if (!tokenEnabled()) return { action: "pass" };
66
+ if (!input.forwarded && isLoopbackAddr(input.remoteAddr)) {
67
+ return { action: "pass" };
68
+ }
69
+ const want = configuredToken();
70
+ const cookieTok = parseCookies(input.cookieHeader)[COOKIE_NAME];
71
+ if (cookieTok && safeEqual(cookieTok, want)) return { action: "pass" };
72
+ const auth = input.authHeader;
73
+ const bearer = auth && auth.startsWith("Bearer ") ? auth.slice(7).trim() : void 0;
74
+ if (bearer && safeEqual(bearer, want)) return { action: "pass" };
75
+ const queryTok = tokenFromQuery(input.url);
76
+ if (queryTok && safeEqual(queryTok, want)) {
77
+ if (input.isWs) return { action: "pass" };
78
+ return {
79
+ action: "redirect",
80
+ location: stripTokenParam(input.url),
81
+ setCookie: makeCookie(want, input.isHttps)
82
+ };
83
+ }
84
+ return { action: "deny" };
85
+ }
86
+ export {
87
+ COOKIE_NAME,
88
+ checkAccess,
89
+ isLoopbackAddr,
90
+ makeCookie,
91
+ tokenEnabled
92
+ };
@@ -217,16 +217,37 @@ async function getLastUserMessage(cwd, sessionId) {
217
217
  var SUMMARY_THRESHOLD = 10;
218
218
  var SUMMARY_HEAD = 5;
219
219
  var SUMMARY_TAIL = 5;
220
+ async function getSessionContent(cwd, sessionId) {
221
+ const claudePath = getClaudeSessionPath(cwd, sessionId);
222
+ if (existsSync(claudePath)) return getClaudeStyleContent(claudePath);
223
+ const claude2Path = getClaude2SessionPath(cwd, sessionId);
224
+ if (existsSync(claude2Path)) return getClaudeStyleContent(claude2Path);
225
+ const ollamaPath = getOllamaSessionPath(cwd, sessionId);
226
+ if (existsSync(ollamaPath)) return getClaudeStyleContent(ollamaPath);
227
+ const codexPath = findCodexSessionPath(sessionId);
228
+ if (codexPath && existsSync(codexPath)) {
229
+ return { summary: "", messages: await getCodexUserMessages(codexPath) };
230
+ }
231
+ const kimiPath = findKimiSessionPath(sessionId);
232
+ if (kimiPath && existsSync(kimiPath)) {
233
+ return { summary: "", messages: await getKimiUserMessages(kimiPath) };
234
+ }
235
+ return { summary: "", messages: [] };
236
+ }
220
237
  async function getSessionPreview(cwd, sessionId) {
221
- const messages = await collectUserMessages(cwd, sessionId);
238
+ const { summary, messages } = await getSessionContent(cwd, sessionId);
239
+ const title = generateTitle(summary, messages);
222
240
  const lastUserMessage = messages[messages.length - 1];
241
+ const searchText = [summary, ...messages].join("\n").toLowerCase();
223
242
  if (messages.length <= SUMMARY_THRESHOLD) {
224
- return { lastUserMessage, firstMessages: messages.map((m) => truncate(m)), lastMessages: [] };
243
+ return { title, lastUserMessage, firstMessages: messages.map((m) => truncate(m)), lastMessages: [], searchText };
225
244
  }
226
245
  return {
246
+ title,
227
247
  lastUserMessage,
228
248
  firstMessages: messages.slice(0, SUMMARY_HEAD).map((m) => truncate(m)),
229
- lastMessages: messages.slice(-SUMMARY_TAIL).map((m) => truncate(m))
249
+ lastMessages: messages.slice(-SUMMARY_TAIL).map((m) => truncate(m)),
250
+ searchText
230
251
  };
231
252
  }
232
253
  function filterCommandTags(text) {
@@ -308,6 +329,46 @@ async function getClaudeStyleUserMessages(filePath) {
308
329
  }
309
330
  return messages;
310
331
  }
332
+ async function getClaudeStyleContent(filePath) {
333
+ let summary = "";
334
+ const messages = [];
335
+ try {
336
+ const fileStream = createReadStream(filePath);
337
+ const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
338
+ for await (const line of rl) {
339
+ if (!line.trim()) continue;
340
+ try {
341
+ const entry = JSON.parse(line);
342
+ if (entry.type === "summary" && entry.summary) {
343
+ summary = entry.summary;
344
+ continue;
345
+ }
346
+ if (entry.type !== "user") continue;
347
+ const message = entry.message;
348
+ if (!message?.content) continue;
349
+ let text = "";
350
+ if (typeof message.content === "string") {
351
+ text = message.content;
352
+ } else if (Array.isArray(message.content)) {
353
+ for (const block of message.content) {
354
+ if (block.type === "text" && block.text) {
355
+ text = block.text;
356
+ break;
357
+ }
358
+ }
359
+ }
360
+ if (!text) continue;
361
+ const filtered = filterCommandTags(text);
362
+ if (filtered && isValidUserMessage(filtered)) {
363
+ messages.push(filtered);
364
+ }
365
+ } catch {
366
+ }
367
+ }
368
+ } catch {
369
+ }
370
+ return { summary, messages };
371
+ }
311
372
  async function getCodexUserMessages(filePath) {
312
373
  const messages = [];
313
374
  try {
@@ -10,7 +10,7 @@ import {
10
10
  setRunAbort,
11
11
  startRun,
12
12
  updateGlobalState
13
- } from "./chunk-XW5CKMXI.mjs";
13
+ } from "./chunk-VHFWPI2X.mjs";
14
14
  import {
15
15
  AgentError,
16
16
  AppRuntime,
@@ -1400,41 +1400,63 @@ async function runSdkLoop(ctx, buildOptions) {
1400
1400
  }
1401
1401
  }
1402
1402
  if (ctx.prompt) content.push({ type: "text", text: ctx.prompt });
1403
- const firstAbort = follow(ctx);
1404
- const baseOptions = buildOptions(firstAbort, ctx.sessionId);
1405
- let response;
1406
- if (hasImages) {
1407
- const messages = (async function* () {
1408
- yield {
1409
- type: "user",
1410
- message: { role: "user", content },
1411
- parent_tool_use_id: null,
1412
- session_id: ctx.sessionId || `session-${ctx.currentKey()}`
1413
- };
1414
- })();
1415
- response = query({ prompt: messages, options: baseOptions });
1416
- } else {
1417
- response = query({ prompt: ctx.prompt, options: baseOptions });
1403
+ const firstContent = hasImages ? content : ctx.prompt ?? "";
1404
+ const pendingTasks = /* @__PURE__ */ new Set();
1405
+ let closeInput = () => {
1406
+ };
1407
+ if (!ctx.signal.aborted) {
1408
+ ctx.signal.addEventListener("abort", () => closeInput(), { once: true });
1418
1409
  }
1419
- let currentResponse = response;
1420
1410
  for (let attempt = 0; attempt <= MAX_COMPACTION_RETRIES; attempt++) {
1411
+ let gateClosed = false;
1412
+ const gateOpen = new Promise((resolve) => {
1413
+ closeInput = () => {
1414
+ if (!gateClosed) {
1415
+ gateClosed = true;
1416
+ resolve();
1417
+ }
1418
+ };
1419
+ });
1420
+ if (ctx.signal.aborted) closeInput();
1421
+ const isRetry = attempt > 0;
1422
+ const firstYield = {
1423
+ type: "user",
1424
+ // A compaction retry resumes the same session with 'continue'; the first attempt sends the
1425
+ // user turn (text ∪ images).
1426
+ message: { role: "user", content: isRetry ? "continue" : firstContent },
1427
+ parent_tool_use_id: null,
1428
+ session_id: ctx.sessionId || `session-${ctx.currentKey()}`
1429
+ };
1430
+ const input = (async function* () {
1431
+ yield firstYield;
1432
+ await gateOpen;
1433
+ })();
1434
+ const attemptAbort = follow(ctx);
1435
+ const resume = isRetry ? ctx.currentKey() : ctx.sessionId;
1436
+ const options = isRetry ? { ...buildOptions(attemptAbort, resume), resume } : buildOptions(attemptAbort, ctx.sessionId);
1437
+ const response = query({ prompt: input, options });
1421
1438
  let receivedResult = false;
1422
- for await (const message of currentResponse) {
1423
- if (ctx.signal.aborted) break;
1439
+ for await (const message of response) {
1440
+ if (ctx.signal.aborted) {
1441
+ closeInput();
1442
+ break;
1443
+ }
1424
1444
  const msg = message;
1425
1445
  if (msg.type === "system" && msg.subtype === "init" && msg.session_id) {
1426
1446
  ctx.rekey(msg.session_id);
1427
1447
  }
1448
+ if (msg.type === "system" && msg.subtype === "task_started" && msg.task_id) {
1449
+ pendingTasks.add(msg.task_id);
1450
+ }
1451
+ if (msg.type === "system" && msg.subtype === "task_notification" && msg.task_id) {
1452
+ pendingTasks.delete(msg.task_id);
1453
+ }
1428
1454
  if (msg.type === "result") receivedResult = true;
1429
1455
  ctx.emit(message);
1456
+ if (msg.type === "result" && pendingTasks.size === 0) closeInput();
1430
1457
  }
1458
+ closeInput();
1431
1459
  if (receivedResult || ctx.signal.aborted) break;
1432
- const retryAbort = follow(ctx);
1433
- const resume = ctx.currentKey();
1434
- currentResponse = query({
1435
- prompt: "continue",
1436
- options: { ...buildOptions(retryAbort, resume), resume }
1437
- });
1438
1460
  }
1439
1461
  }
1440
1462
 
package/dist/wsServer.mjs CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  addRunListener,
3
3
  getRunSnapshot,
4
4
  getSessionPreview
5
- } from "./chunk-XW5CKMXI.mjs";
5
+ } from "./chunk-VHFWPI2X.mjs";
6
6
  import {
7
7
  addExitListener,
8
8
  addOutputListener,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@surething/cockpit",
3
- "version": "1.0.230",
3
+ "version": "1.0.232",
4
4
  "description": "Claude Code GUI for parallel AI coding — drive Claude (default), Codex, DeepSeek, Kimi, or local Ollama in tabs. Multi-project sessions, terminal, browser & DB bubbles, code review, slash modes. Local-first, MIT.",
5
5
  "author": "Robert",
6
6
  "license": "MIT",
package/server.mjs CHANGED
@@ -128,6 +128,7 @@ app.prepare().then(async () => {
128
128
  const upgradeHandler = app.getUpgradeHandler();
129
129
  // v2 P8: HTTP intercepts (handleTerminalApi / handleBrowserApi) moved to src/lib/httpApi.ts
130
130
  const { handleUpgrade, broadcastToGlobalState } = await import(dev ? './src/lib/wsServer.ts' : './dist/wsServer.mjs');
131
+ const auth = await import(dev ? './src/lib/auth.ts' : './dist/auth.mjs');
131
132
  const httpApi = await import(dev ? './src/lib/httpApi.ts' : './dist/httpApi.mjs');
132
133
  const { handleBrowserApi, handleTerminalApi, handleConnectionApi } = httpApi;
133
134
  flushRunningSync = httpApi.flushAllRunningSync || null;
@@ -139,7 +140,45 @@ app.prepare().then(async () => {
139
140
  });
140
141
  await scheduledTaskManager.init();
141
142
 
143
+ // ============================================
144
+ // Token gate — opt-in via `cockpit --token <value>` (COCKPIT_TOKEN).
145
+ // Off by default (open). Local callers (loopback peer + no forwarding header)
146
+ // are exempt, so the CLI / /cg curls / self-probe never need a token.
147
+ // ============================================
148
+ const gateInput = (req, isWs) => ({
149
+ url: req.url || '',
150
+ remoteAddr: req.socket?.remoteAddress,
151
+ cookieHeader: req.headers?.cookie,
152
+ authHeader: req.headers?.authorization,
153
+ forwarded:
154
+ req.headers?.['x-forwarded-for'] ||
155
+ req.headers?.['x-real-ip'] ||
156
+ req.headers?.['forwarded'],
157
+ isWs,
158
+ isHttps:
159
+ String(req.headers?.['x-forwarded-proto'] || '').split(',')[0].trim() === 'https',
160
+ });
161
+
162
+ // Apply the gate to an HTTP request. Returns true if it wrote a response
163
+ // (blocked / redirected) and the caller should stop.
164
+ const applyHttpGate = (req, res) => {
165
+ const decision = auth.checkAccess(gateInput(req, false));
166
+ if (decision.action === 'redirect') {
167
+ res.writeHead(302, { Location: decision.location, 'Set-Cookie': decision.setCookie });
168
+ res.end();
169
+ return true;
170
+ }
171
+ if (decision.action === 'deny') {
172
+ res.writeHead(401, { 'Content-Type': 'text/plain; charset=utf-8' });
173
+ res.end('401 Unauthorized - append ?token=<token> to the URL to authenticate\n');
174
+ return true;
175
+ }
176
+ return false;
177
+ };
178
+
142
179
  const server = createServer(async (req, res) => {
180
+ if (applyHttpGate(req, res)) return;
181
+
143
182
  // /api/browser/* 必须在自定义 server 中处理(与 WS 共享 BrowserBridge 内存)
144
183
  if (req.url?.startsWith('/api/browser/') && req.method === 'POST') {
145
184
  const handled = await handleBrowserApi(req, res);
@@ -157,6 +196,12 @@ app.prepare().then(async () => {
157
196
  });
158
197
 
159
198
  server.on('upgrade', (req, socket, head) => {
199
+ // Cookie / ?token ride the same-origin upgrade → gate WS too.
200
+ if (auth.checkAccess(gateInput(req, true)).action !== 'pass') {
201
+ socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
202
+ socket.destroy();
203
+ return;
204
+ }
160
205
  if (!handleUpgrade(req, socket, head)) {
161
206
  upgradeHandler(req, socket, head);
162
207
  }
@@ -212,6 +257,9 @@ app.prepare().then(async () => {
212
257
  }
213
258
 
214
259
  const shareServer = createServer((req, res) => {
260
+ // Token gate first (share is also covered). Read the gate BEFORE we inject
261
+ // x-forwarded-for below, so the injection can't fool the local check.
262
+ if (applyHttpGate(req, res)) return;
215
263
  if (isShareAllowed(req.url || '')) {
216
264
  // 注入客户端真实 IP,供 /api/review/identify 使用
217
265
  const clientIp = req.socket.remoteAddress || '';