@surething/cockpit 1.0.271 → 1.0.272

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 (55) 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 +3 -3
  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/_tree.segment.rsc +1 -1
  12. package/.next-prod/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/.next-prod/server/app/_not-found.html +2 -2
  14. package/.next-prod/server/app/_not-found.rsc +2 -2
  15. package/.next-prod/server/app/_not-found.segments/_full.segment.rsc +2 -2
  16. package/.next-prod/server/app/_not-found.segments/_index.segment.rsc +2 -2
  17. package/.next-prod/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  18. package/.next-prod/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  19. package/.next-prod/server/app/api/terminal/history/route.js +1 -1
  20. package/.next-prod/server/app/m/page.js +1 -1
  21. package/.next-prod/server/app/m/page_client-reference-manifest.js +1 -1
  22. package/.next-prod/server/app/page_client-reference-manifest.js +1 -1
  23. package/.next-prod/server/app/project/page_client-reference-manifest.js +1 -1
  24. package/.next-prod/server/app/review/[id]/page.js +1 -1
  25. package/.next-prod/server/app/review/[id]/page_client-reference-manifest.js +1 -1
  26. package/.next-prod/server/app-paths-manifest.json +1 -1
  27. package/.next-prod/server/chunks/5750.js +14 -14
  28. package/.next-prod/server/chunks/6548.js +1 -1
  29. package/.next-prod/server/middleware-build-manifest.js +1 -1
  30. package/.next-prod/server/pages/404.html +2 -2
  31. package/.next-prod/server/pages/500.html +1 -1
  32. package/.next-prod/server/server-reference-manifest.json +1 -1
  33. package/.next-prod/static/chunks/{2302-5e8daad0800c3c02.js → 6024-31a42934bf55e9e9.js} +20 -20
  34. package/.next-prod/static/chunks/7184-2a5621de437121a2.js +17 -0
  35. package/.next-prod/static/chunks/{6784-8e0e9ea22ff4a60f.js → 9092-f7267cbb4deb6fd5.js} +2 -2
  36. package/.next-prod/static/chunks/app/{layout-0782cd43a14c7ee2.js → layout-4d9685b25807b4c6.js} +1 -1
  37. package/.next-prod/static/chunks/app/m/{page-241cf89338a0f255.js → page-af6600597536f9a9.js} +1 -1
  38. package/.next-prod/static/chunks/app/{page-ca2a81ab87931cdb.js → page-af99426a6c82e466.js} +1 -1
  39. package/.next-prod/static/chunks/app/project/{page-ca2a81ab87931cdb.js → page-af99426a6c82e466.js} +1 -1
  40. package/.next-prod/static/chunks/app/review/[id]/{page-556db82704d074ab.js → page-1fe42d46b22c6482.js} +1 -1
  41. package/.next-prod/trace +14 -14
  42. package/.next-prod/trace-build +1 -1
  43. package/bin/cock-browser.mjs +65 -2
  44. package/dist/{chunk-RZY32R7X.mjs → chunk-4EU4KZIO.mjs} +1 -0
  45. package/dist/{chunk-WLCT5LHX.mjs → chunk-PB4GBZQO.mjs} +191 -39
  46. package/dist/{chunk-VAOGZ6SO.mjs → chunk-TOA52B2X.mjs} +1 -1
  47. package/dist/httpApi.mjs +58 -2
  48. package/dist/scheduledTasks.mjs +6 -4
  49. package/dist/wsServer.mjs +3 -3
  50. package/package.json +1 -1
  51. package/public/html-lib/markdown.js +56 -56
  52. package/skills/cc/SKILL.md +26 -1
  53. package/.next-prod/static/chunks/7184-b6fb5d3287fc073c.js +0 -17
  54. /package/.next-prod/static/{BRgWyE48IVXSuUJ4BLHQb → ZqxwSTxy2HJcALhnBemS7}/_buildManifest.js +0 -0
  55. /package/.next-prod/static/{BRgWyE48IVXSuUJ4BLHQb → ZqxwSTxy2HJcALhnBemS7}/_ssgManifest.js +0 -0
@@ -1 +1 @@
1
- [{"name":"run-webpack","duration":15005902,"timestamp":578684146,"id":14,"parentId":1,"tags":{},"startTime":1788495156847,"traceId":"d890b4c69e09d4b7"},{"name":"run-typescript","duration":4981789,"timestamp":593695697,"id":1471,"parentId":1,"tags":{},"startTime":1788495171858,"traceId":"d890b4c69e09d4b7"},{"name":"static-check","duration":1366631,"timestamp":598718150,"id":1474,"parentId":1,"tags":{},"startTime":1788495176881,"traceId":"d890b4c69e09d4b7"},{"name":"static-generation","duration":5321119,"timestamp":600247809,"id":1784,"parentId":1,"tags":{},"startTime":1788495178410,"traceId":"d890b4c69e09d4b7"},{"name":"collect-build-traces","duration":24985253,"timestamp":600085530,"id":1781,"parentId":1,"tags":{},"startTime":1788495178248,"traceId":"d890b4c69e09d4b7"},{"name":"telemetry-flush","duration":57,"timestamp":625074596,"id":1793,"parentId":1,"tags":{},"startTime":1788495203237,"traceId":"d890b4c69e09d4b7"},{"name":"next-build","duration":46568909,"timestamp":578505755,"id":1,"tags":{"buildMode":"default","version":"16.3.0","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1788495156668,"traceId":"d890b4c69e09d4b7"}]
1
+ [{"name":"run-webpack","duration":16432612,"timestamp":243518100,"id":14,"parentId":1,"tags":{},"startTime":1788784539677,"traceId":"a61d8bd9748a4eff"},{"name":"run-typescript","duration":5161102,"timestamp":259975966,"id":1471,"parentId":1,"tags":{},"startTime":1788784556135,"traceId":"a61d8bd9748a4eff"},{"name":"static-check","duration":1368014,"timestamp":265181806,"id":1474,"parentId":1,"tags":{},"startTime":1788784561341,"traceId":"a61d8bd9748a4eff"},{"name":"static-generation","duration":5917610,"timestamp":266672543,"id":1784,"parentId":1,"tags":{},"startTime":1788784562831,"traceId":"a61d8bd9748a4eff"},{"name":"collect-build-traces","duration":25357913,"timestamp":266550538,"id":1781,"parentId":1,"tags":{},"startTime":1788784562709,"traceId":"a61d8bd9748a4eff"},{"name":"telemetry-flush","duration":62,"timestamp":291912488,"id":1793,"parentId":1,"tags":{},"startTime":1788784588071,"traceId":"a61d8bd9748a4eff"},{"name":"next-build","duration":48581077,"timestamp":243331486,"id":1,"tags":{"buildMode":"default","version":"16.3.0","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1788784539490,"traceId":"a61d8bd9748a4eff"}]
@@ -81,6 +81,16 @@ re-snapshot OR use \`click --text\` / \`click --selector\` /
81
81
 
82
82
  ──────────────────────────────────────────────────────
83
83
 
84
+ Bubbles (create / destroy — no <id> needed for open):
85
+ cockpit browser open <url> [--cwd PATH] [--wait-ms N]
86
+ Open a console browser bubble in the project that
87
+ owns --cwd (default: shell cwd), auto-register its
88
+ bridge, and print the shortId to drive it with.
89
+ Exit 2 if no Cockpit UI picked it up (bubble is
90
+ persisted, but nothing is driving it yet).
91
+ cockpit browser <id> close [--cwd PATH]
92
+ Close the bubble and remove it from the console.
93
+
84
94
  Navigation:
85
95
  navigate <url> Navigate to URL
86
96
  reload [--noCache] Reload page
@@ -165,6 +175,13 @@ let id, action;
165
175
  if (args[0] === 'list') {
166
176
  id = null;
167
177
  action = 'list';
178
+ } else if (args[0] === 'open') {
179
+ // `open` creates a bubble rather than addressing one, so the first token is
180
+ // the action, not an id. (A shortId is also four lowercase letters, so `open`
181
+ // and `list` are in principle collidable — the same trade-off `list` already
182
+ // makes; a colliding bubble is still reachable via every other action.)
183
+ id = null;
184
+ action = 'open';
168
185
  } else {
169
186
  id = args[0];
170
187
  action = args[1];
@@ -213,12 +230,13 @@ function parseFlags(flagArgs) {
213
230
  return params;
214
231
  }
215
232
 
216
- const params = parseFlags(args.slice(action === 'list' ? 1 : 2));
233
+ const params = parseFlags(args.slice(action === 'list' || action === 'open' ? 1 : 2));
217
234
 
218
235
  // Positional argument handling: some commands treat the first positional as a special value
219
236
  if (params._positional?.length) {
220
237
  const pos = params._positional;
221
238
  if (action === 'navigate' && !params.url) params.url = pos[0];
239
+ if (action === 'open' && !params.url) params.url = pos[0];
222
240
  // click: positional is the ref (or text fallback for convenience if it does
223
241
  // not look like a ref). Refs match e<N>#v<M>; anything else is treated as
224
242
  // visible text so `click "Sign in"` Just Works.
@@ -276,6 +294,7 @@ if (params['console-no-errors']) { params.consoleNoErrors = true; delete params[
276
294
  if (params['same-site']) { params.sameSite = params['same-site']; delete params['same-site']; }
277
295
  if (params['http-only']) { params.httpOnly = true; delete params['http-only']; }
278
296
  if (params['verify-ms'] != null) { params.verifyMs = Number(params['verify-ms']); delete params['verify-ms']; }
297
+ if (params['wait-ms'] != null) { params.waitMs = Number(params['wait-ms']); delete params['wait-ms']; }
279
298
 
280
299
  // kebab → camel for flags that the extension expects camel.
281
300
  if (params['include-hidden-text']) { params.includeHiddenText = true; delete params['include-hidden-text']; }
@@ -301,9 +320,24 @@ if (!process.env.COCKPIT_PORT) {
301
320
  }
302
321
  delete params.port;
303
322
  const baseUrl = `http://localhost:${port}`;
304
- const timeout = params.timeout || 15000;
323
+ // `open` blocks server-side until the bubble registers (--wait-ms); the HTTP
324
+ // timeout has to outlast that wait or the CLI aborts a request that was about
325
+ // to succeed.
326
+ const timeout = params.timeout
327
+ || (action === 'open' && params.waitMs > 10000 ? params.waitMs + 5000 : 15000);
305
328
  delete params.timeout;
306
329
 
330
+ // The CLI name to suggest in output: dev server (3456) is only reachable via
331
+ // `cockpit-dev`, which has no short alias.
332
+ const cockBin = String(port) === '3456' ? 'cockpit-dev' : 'cockpit';
333
+
334
+ // `open` / `close` address a project on disk rather than a live page, so they
335
+ // need a cwd. Default to where the shell is standing; the server walks up from
336
+ // there to the project that actually owns the console.
337
+ if ((action === 'open' || action === 'close') && !params.cwd) {
338
+ params.cwd = process.cwd();
339
+ }
340
+
307
341
  // Quickly fetch browser url and title (2s timeout, silently return empty on failure)
308
342
  async function fetchBrowserInfo(shortId) {
309
343
  try {
@@ -665,6 +699,35 @@ async function formatOutput(action, data) {
665
699
 
666
700
  // Special formatting
667
701
  switch (action) {
702
+ case 'open':
703
+ console.log(
704
+ data.registered
705
+ ? `${data.shortId} → registered and ready`
706
+ : `${data.shortId} → created, but NOT registered`
707
+ );
708
+ console.log(` url: ${data.url}`);
709
+ console.log(` project: ${data.projectCwd}`);
710
+ if (data.registered) {
711
+ console.log(` next: ${cockBin} browser ${data.shortId} snapshot`);
712
+ console.log(` close: ${cockBin} browser ${data.shortId} close`);
713
+ } else {
714
+ console.log(
715
+ `\n No Cockpit UI applied the bubble, so nothing is driving it.\n` +
716
+ ` The entry is persisted — it will appear when this project is next\n` +
717
+ ` opened in Cockpit. Open the project (or check --cwd) and retry.`
718
+ );
719
+ process.exit(2);
720
+ }
721
+ return;
722
+
723
+ case 'close':
724
+ console.log(
725
+ data.removed
726
+ ? `closed ${data.shortId}`
727
+ : `closed ${data.shortId} (no persisted entry — nothing to remove)`
728
+ );
729
+ return;
730
+
668
731
  case 'snapshot':
669
732
  // a11y tree: output as plain text
670
733
  console.log(typeof data === 'string' ? data : JSON.stringify(data, null, 2));
@@ -30851,6 +30851,7 @@ export {
30851
30851
  DEEPSEEK_CREDENTIALS_FILE,
30852
30852
  KIMI_CREDENTIALS_FILE,
30853
30853
  GLM_CREDENTIALS_FILE,
30854
+ getCockpitProjectDir,
30854
30855
  getSessionFilePath,
30855
30856
  getTerminalHistoryPath,
30856
30857
  getTerminalOutputPath,
@@ -9,11 +9,12 @@ import {
9
9
  ensureParentDir,
10
10
  errorToStatus,
11
11
  getBubbleOrderPath,
12
+ getCockpitProjectDir,
12
13
  getTerminalHistoryPath,
13
14
  getTerminalOutputPath,
14
15
  readJsonFile,
15
16
  writeJsonFile
16
- } from "./chunk-RZY32R7X.mjs";
17
+ } from "./chunk-4EU4KZIO.mjs";
17
18
 
18
19
  // packages/feature/console/src/server/plugins/browser/BrowserBridge.ts
19
20
  import { WebSocket } from "ws";
@@ -75,13 +76,13 @@ function listBrowsers() {
75
76
  }
76
77
  var pendingRequests = g_browser.__cockpitBrowserPending ?? (g_browser.__cockpitBrowserPending = /* @__PURE__ */ new Map());
77
78
  function createPendingRequest(reqId, timeout) {
78
- return new Promise((resolve, reject) => {
79
+ return new Promise((resolve2, reject) => {
79
80
  const timer = setTimeout(() => {
80
81
  pendingRequests.delete(reqId);
81
82
  recordCommandResolved(reqId, false);
82
83
  reject(new Error(`Timeout after ${timeout}ms`));
83
84
  }, timeout);
84
- pendingRequests.set(reqId, { resolve, reject, timer });
85
+ pendingRequests.set(reqId, { resolve: resolve2, reject, timer });
85
86
  });
86
87
  }
87
88
  function resolvePendingRequest(reqId, ok2, data, error) {
@@ -154,6 +155,177 @@ function getBrowserHealth(shortId) {
154
155
  };
155
156
  }
156
157
 
158
+ // packages/feature/console/src/server/plugins/browser/browserBubbleControl.ts
159
+ import { existsSync } from "fs";
160
+ import { dirname, resolve } from "path";
161
+
162
+ // packages/feature/console/src/server/terminal/historyStore.ts
163
+ import fs from "fs/promises";
164
+ var OUTPUT_FILE_THRESHOLD = 4096;
165
+ var MAX_ENTRIES = 100;
166
+ async function appendHistoryEntry(cwd, tabId, entry) {
167
+ const historyPath = getTerminalHistoryPath(cwd, tabId);
168
+ await ensureParentDir(historyPath);
169
+ const entryToSave = { ...entry };
170
+ if (entry.output && entry.output.length > OUTPUT_FILE_THRESHOLD) {
171
+ const outputPath = getTerminalOutputPath(cwd, entry.id);
172
+ await fs.writeFile(outputPath, entry.output, "utf-8");
173
+ entryToSave.output = "";
174
+ entryToSave.outputFile = outputPath;
175
+ }
176
+ let existingLines = [];
177
+ try {
178
+ const content = await fs.readFile(historyPath, "utf-8");
179
+ existingLines = content.trim().split("\n").filter(Boolean);
180
+ } catch {
181
+ }
182
+ if (entry.id) {
183
+ const alreadyExists = existingLines.some((line) => {
184
+ try {
185
+ return JSON.parse(line).id === entry.id;
186
+ } catch {
187
+ return false;
188
+ }
189
+ });
190
+ if (alreadyExists) return { success: true, skipped: true };
191
+ }
192
+ if (existingLines.length >= MAX_ENTRIES) {
193
+ const removedLines = existingLines.slice(
194
+ 0,
195
+ existingLines.length - (MAX_ENTRIES - 1)
196
+ );
197
+ for (const line of removedLines) {
198
+ try {
199
+ const old = JSON.parse(line);
200
+ if (old.outputFile) await fs.unlink(old.outputFile).catch(() => {
201
+ });
202
+ } catch {
203
+ }
204
+ }
205
+ existingLines = existingLines.slice(-(MAX_ENTRIES - 1));
206
+ }
207
+ existingLines.push(JSON.stringify(entryToSave));
208
+ await fs.writeFile(historyPath, existingLines.join("\n") + "\n", "utf-8");
209
+ return { success: true };
210
+ }
211
+ async function removeHistoryEntry(cwd, tabId, id, onMatch) {
212
+ const historyPath = getTerminalHistoryPath(cwd, tabId);
213
+ let removed = null;
214
+ try {
215
+ const content = await fs.readFile(historyPath, "utf-8");
216
+ const lines = content.trim().split("\n").filter(Boolean);
217
+ const remaining = [];
218
+ for (const line of lines) {
219
+ try {
220
+ const entry = JSON.parse(line);
221
+ if (entry.id === id) {
222
+ removed = entry;
223
+ if (onMatch) await onMatch(entry);
224
+ continue;
225
+ }
226
+ } catch {
227
+ }
228
+ remaining.push(line);
229
+ }
230
+ if (remaining.length > 0) {
231
+ await fs.writeFile(historyPath, remaining.join("\n") + "\n", "utf-8");
232
+ } else {
233
+ await fs.unlink(historyPath).catch(() => {
234
+ });
235
+ }
236
+ } catch (e) {
237
+ if (e.code !== "ENOENT") throw e;
238
+ }
239
+ return removed;
240
+ }
241
+ async function readHistoryEntries(cwd, tabId) {
242
+ const historyPath = getTerminalHistoryPath(cwd, tabId);
243
+ try {
244
+ const content = await fs.readFile(historyPath, "utf-8");
245
+ return content.trim().split("\n").filter(Boolean).flatMap((line) => {
246
+ try {
247
+ return [JSON.parse(line)];
248
+ } catch {
249
+ return [];
250
+ }
251
+ });
252
+ } catch {
253
+ return [];
254
+ }
255
+ }
256
+
257
+ // packages/feature/console/src/server/terminal/consoleBroadcast.ts
258
+ var WS_OPEN = 1;
259
+ var g = globalThis;
260
+ function broadcastConsoleDelta(cwd, tabId, delta, sourceId) {
261
+ const set = g.__cockpitGlobalStateClients;
262
+ if (!set || set.size === 0) return;
263
+ const data = JSON.stringify({
264
+ type: "console-delta",
265
+ cwd,
266
+ tabId,
267
+ sourceId,
268
+ ...delta
269
+ });
270
+ for (const ws of set) {
271
+ if (ws.readyState === WS_OPEN) {
272
+ try {
273
+ ws.send(data);
274
+ } catch {
275
+ }
276
+ }
277
+ }
278
+ }
279
+
280
+ // packages/feature/console/src/server/plugins/browser/browserBubbleControl.ts
281
+ var CONSOLE_TAB_ID = "default";
282
+ function resolveProjectCwd(cwd) {
283
+ const start = resolve(cwd);
284
+ let dir = start;
285
+ for (; ; ) {
286
+ if (existsSync(getCockpitProjectDir(dir))) return dir;
287
+ const parent = dirname(dir);
288
+ if (parent === dir) return start;
289
+ dir = parent;
290
+ }
291
+ }
292
+ async function openBrowserBubble(opts) {
293
+ const projectCwd = resolveProjectCwd(opts.cwd);
294
+ const tabId = opts.tabId ?? CONSOLE_TAB_ID;
295
+ const fullId = `browser-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
296
+ const entry = {
297
+ type: "browser",
298
+ id: fullId,
299
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
300
+ url: opts.url,
301
+ // Base for a RELATIVE url. Deliberately the caller's raw cwd, not
302
+ // projectCwd: `cockpit browser open ./report.html` should resolve against
303
+ // the directory the user is standing in, mirroring what typing the same
304
+ // path into the console after a `cd` does.
305
+ cwd: resolve(opts.cwd),
306
+ autoConnect: true
307
+ };
308
+ await appendHistoryEntry(projectCwd, tabId, entry);
309
+ broadcastConsoleDelta(projectCwd, tabId, { op: "add", entry });
310
+ return { fullId, shortId: toShortId(fullId), projectCwd, tabId };
311
+ }
312
+ async function closeBrowserBubble(opts) {
313
+ const tabId = opts.tabId ?? CONSOLE_TAB_ID;
314
+ const removed = await removeHistoryEntry(opts.projectCwd, tabId, opts.fullId);
315
+ broadcastConsoleDelta(opts.projectCwd, tabId, {
316
+ op: "delete",
317
+ id: opts.fullId
318
+ });
319
+ return !!removed;
320
+ }
321
+ async function findBrowserBubbleByShortId(projectCwd, shortId, tabId = CONSOLE_TAB_ID) {
322
+ const entries = await readHistoryEntries(projectCwd, tabId);
323
+ for (const e of entries) {
324
+ if (e.type === "browser" && e.id && toShortId(e.id) === shortId) return e.id;
325
+ }
326
+ return null;
327
+ }
328
+
157
329
  // packages/feature/console/src/server/terminal/TerminalBridge.ts
158
330
  var REGISTRY_KEY = /* @__PURE__ */ Symbol.for("terminal_bridge_registry");
159
331
  var REVERSE_KEY = /* @__PURE__ */ Symbol.for("terminal_bridge_reverse");
@@ -257,36 +429,11 @@ function notifyExitListeners(commandId, exitCode) {
257
429
  }
258
430
 
259
431
  // packages/feature/console/src/server/terminal/RunningCommandRegistry.ts
260
- import fs from "fs/promises";
432
+ import fs2 from "fs/promises";
261
433
  import { writeFileSync, mkdirSync } from "fs";
262
- import { dirname } from "path";
263
-
264
- // packages/feature/console/src/server/terminal/consoleBroadcast.ts
265
- var WS_OPEN = 1;
266
- var g = globalThis;
267
- function broadcastConsoleDelta(cwd, tabId, delta, sourceId) {
268
- const set = g.__cockpitGlobalStateClients;
269
- if (!set || set.size === 0) return;
270
- const data = JSON.stringify({
271
- type: "console-delta",
272
- cwd,
273
- tabId,
274
- sourceId,
275
- ...delta
276
- });
277
- for (const ws of set) {
278
- if (ws.readyState === WS_OPEN) {
279
- try {
280
- ws.send(data);
281
- } catch {
282
- }
283
- }
284
- }
285
- }
286
-
287
- // packages/feature/console/src/server/terminal/RunningCommandRegistry.ts
434
+ import { dirname as dirname2 } from "path";
288
435
  var MAX_OUTPUT_LINES = 5e3;
289
- var OUTPUT_FILE_THRESHOLD = 4096;
436
+ var OUTPUT_FILE_THRESHOLD2 = 4096;
290
437
  var PTY_RING_BUFFER_MAX = 2 * 1024 * 1024;
291
438
  function countNewlines(s) {
292
439
  let n = 0;
@@ -497,7 +644,7 @@ async function writeHistoryPlaceholder(commandId, command, timestamp, cwd, proje
497
644
  };
498
645
  let existingLines = [];
499
646
  try {
500
- const content = await fs.readFile(historyPath, "utf-8");
647
+ const content = await fs2.readFile(historyPath, "utf-8");
501
648
  existingLines = content.trim().split("\n").filter(Boolean);
502
649
  } catch {
503
650
  }
@@ -518,7 +665,7 @@ async function writeHistoryPlaceholder(commandId, command, timestamp, cwd, proje
518
665
  for (const line of removedLines) {
519
666
  try {
520
667
  const old = JSON.parse(line);
521
- if (old.outputFile) await fs.unlink(old.outputFile).catch(() => {
668
+ if (old.outputFile) await fs2.unlink(old.outputFile).catch(() => {
522
669
  });
523
670
  } catch {
524
671
  }
@@ -527,7 +674,7 @@ async function writeHistoryPlaceholder(commandId, command, timestamp, cwd, proje
527
674
  }
528
675
  existingLines.push(JSON.stringify(entry));
529
676
  }
530
- await fs.writeFile(historyPath, existingLines.join("\n") + "\n", "utf-8");
677
+ await fs2.writeFile(historyPath, existingLines.join("\n") + "\n", "utf-8");
531
678
  broadcastConsoleDelta(
532
679
  projectCwd,
533
680
  tabId,
@@ -562,16 +709,16 @@ async function finalizeCommand(commandId, exitCode, pid) {
562
709
  };
563
710
  const historyPath = getTerminalHistoryPath(cmd.projectCwd, cmd.tabId);
564
711
  await ensureParentDir(historyPath);
565
- if (output.length > OUTPUT_FILE_THRESHOLD) {
712
+ if (output.length > OUTPUT_FILE_THRESHOLD2) {
566
713
  const outputPath = getTerminalOutputPath(cmd.projectCwd, cmd.commandId);
567
- await fs.writeFile(outputPath, output, "utf-8");
714
+ await fs2.writeFile(outputPath, output, "utf-8");
568
715
  entry.outputFile = outputPath;
569
716
  } else {
570
717
  entry.output = output;
571
718
  }
572
719
  let existingLines = [];
573
720
  try {
574
- const content = await fs.readFile(historyPath, "utf-8");
721
+ const content = await fs2.readFile(historyPath, "utf-8");
575
722
  existingLines = content.trim().split("\n").filter(Boolean);
576
723
  } catch {
577
724
  }
@@ -589,7 +736,7 @@ async function finalizeCommand(commandId, exitCode, pid) {
589
736
  if (!replaced) {
590
737
  existingLines.push(JSON.stringify(entry));
591
738
  }
592
- await fs.writeFile(historyPath, existingLines.join("\n") + "\n", "utf-8");
739
+ await fs2.writeFile(historyPath, existingLines.join("\n") + "\n", "utf-8");
593
740
  broadcastConsoleDelta(cmd.projectCwd, cmd.tabId, {
594
741
  op: "update",
595
742
  id: cmd.commandId,
@@ -603,7 +750,7 @@ function flushAllRunningSync() {
603
750
  if (!buf || buf.length === 0) continue;
604
751
  try {
605
752
  const outputPath = getTerminalOutputPath(cmd.projectCwd, cmd.commandId);
606
- mkdirSync(dirname(outputPath), { recursive: true });
753
+ mkdirSync(dirname2(outputPath), { recursive: true });
607
754
  writeFileSync(outputPath, buf.snapshot(), "utf-8");
608
755
  } catch {
609
756
  }
@@ -973,6 +1120,11 @@ export {
973
1120
  resolvePendingRequest,
974
1121
  sendCommandToBrowser,
975
1122
  getBrowserHealth,
1123
+ CONSOLE_TAB_ID,
1124
+ resolveProjectCwd,
1125
+ openBrowserBubble,
1126
+ closeBrowserBubble,
1127
+ findBrowserBubbleByShortId,
976
1128
  registerTerminal,
977
1129
  unregisterTerminal,
978
1130
  getTerminalByShortId,
@@ -14,7 +14,7 @@ import {
14
14
  readJsonFile,
15
15
  withFileLock,
16
16
  writeJsonFile
17
- } from "./chunk-RZY32R7X.mjs";
17
+ } from "./chunk-4EU4KZIO.mjs";
18
18
 
19
19
  // packages/feature/agent/src/server/sessionRunHub.ts
20
20
  var GRACE_MS = 6e4;
package/dist/httpApi.mjs CHANGED
@@ -1,7 +1,10 @@
1
1
  import {
2
+ CONSOLE_TAB_ID,
2
3
  addExitListener,
3
4
  addOutputListener,
5
+ closeBrowserBubble,
4
6
  createPendingRequest,
7
+ findBrowserBubbleByShortId,
5
8
  flushAllRunningSync,
6
9
  getBrowserByShortId,
7
10
  getBrowserHealth,
@@ -11,21 +14,23 @@ import {
11
14
  grepOutput,
12
15
  listBrowsers,
13
16
  listTerminals,
17
+ openBrowserBubble,
14
18
  readAround,
15
19
  readBubbleTitles,
16
20
  readHead,
17
21
  readSince,
18
22
  readTail,
19
23
  registerTerminal,
24
+ resolveProjectCwd,
20
25
  sendCommandToBrowser,
21
26
  stripAnsi,
22
27
  unregisterBrowser,
23
28
  unregisterTerminal,
24
29
  writeStdinToCommand
25
- } from "./chunk-WLCT5LHX.mjs";
30
+ } from "./chunk-PB4GBZQO.mjs";
26
31
  import {
27
32
  getTerminalHistoryPath
28
- } from "./chunk-RZY32R7X.mjs";
33
+ } from "./chunk-4EU4KZIO.mjs";
29
34
  import "./chunk-7P6ASYW6.mjs";
30
35
 
31
36
  // src/lib/httpApi.ts
@@ -378,6 +383,29 @@ async function handleBrowserApi(req, res) {
378
383
  sendJson(200, { ok: true, data: listBrowsers() });
379
384
  return true;
380
385
  }
386
+ if (action === "open") {
387
+ const p = cmdParams;
388
+ const url = typeof p.url === "string" ? p.url.trim() : "";
389
+ if (!url) {
390
+ sendJson(400, { ok: false, error: "Missing url" });
391
+ return true;
392
+ }
393
+ const cwd = typeof p.cwd === "string" && p.cwd ? p.cwd : process.cwd();
394
+ const waitMs = typeof p.waitMs === "number" ? p.waitMs : 8e3;
395
+ try {
396
+ const opened = await openBrowserBubble({ cwd, url });
397
+ const deadline = Date.now() + Math.max(0, waitMs);
398
+ let registered = !!getBrowserByShortId(opened.shortId);
399
+ while (!registered && Date.now() < deadline) {
400
+ await new Promise((r) => setTimeout(r, 100));
401
+ registered = !!getBrowserByShortId(opened.shortId);
402
+ }
403
+ sendJson(200, { ok: true, data: { ...opened, url, registered } });
404
+ } catch (err) {
405
+ sendJson(500, { ok: false, error: err.message });
406
+ }
407
+ return true;
408
+ }
381
409
  if (action === "unregister") {
382
410
  if (!id) {
383
411
  sendJson(400, { ok: false, error: "Missing browser id" });
@@ -405,6 +433,34 @@ async function handleBrowserApi(req, res) {
405
433
  sendJson(400, { ok: false, error: "Missing browser id" });
406
434
  return true;
407
435
  }
436
+ if (action === "close") {
437
+ const p = cmdParams;
438
+ const known = getBrowserByShortId(id);
439
+ let projectCwd = known?.projectCwd;
440
+ let tabId = known?.tabId;
441
+ let fullId = known?.fullId ?? null;
442
+ if (!fullId || !projectCwd) {
443
+ const cwd = typeof p.cwd === "string" && p.cwd ? p.cwd : process.cwd();
444
+ projectCwd = resolveProjectCwd(cwd);
445
+ tabId = tabId ?? CONSOLE_TAB_ID;
446
+ fullId = await findBrowserBubbleByShortId(projectCwd, id, tabId);
447
+ }
448
+ if (!fullId || !projectCwd) {
449
+ sendJson(404, {
450
+ ok: false,
451
+ error: `No browser bubble "${id}" found in this project.
452
+ Pass --cwd if you are not standing in the project that owns it.`
453
+ });
454
+ return true;
455
+ }
456
+ try {
457
+ const removed = await closeBrowserBubble({ projectCwd, tabId, fullId });
458
+ sendJson(200, { ok: true, data: { shortId: id, fullId, removed } });
459
+ } catch (err) {
460
+ sendJson(500, { ok: false, error: err.message });
461
+ }
462
+ return true;
463
+ }
408
464
  const browser = getBrowserByShortId(id);
409
465
  if (!browser) {
410
466
  const all = listBrowsers().map((b) => b.shortId);
@@ -15,7 +15,7 @@ import {
15
15
  stashCodexRollout,
16
16
  stashTranscript,
17
17
  updateGlobalState
18
- } from "./chunk-VAOGZ6SO.mjs";
18
+ } from "./chunk-TOA52B2X.mjs";
19
19
  import {
20
20
  AgentError,
21
21
  AppRuntime,
@@ -46,7 +46,7 @@ import {
46
46
  settlePendingRecord,
47
47
  withFileLock,
48
48
  writeJsonFile
49
- } from "./chunk-RZY32R7X.mjs";
49
+ } from "./chunk-4EU4KZIO.mjs";
50
50
  import {
51
51
  __export
52
52
  } from "./chunk-7P6ASYW6.mjs";
@@ -17507,7 +17507,8 @@ var en_default2 = {
17507
17507
  hidden: "{{count}} lines hidden",
17508
17508
  more: "\xB7\xB7\xB7 more +{{count}} \xB7\xB7\xB7",
17509
17509
  expandUp: "Expand {{count}} lines up",
17510
- expandDown: "Expand {{count}} lines down"
17510
+ expandDown: "Expand {{count}} lines down",
17511
+ expandAll: "Click to expand {{count}} lines"
17511
17512
  },
17512
17513
  loadingSnapshots: "Loading snapshots\u2026",
17513
17514
  noChanges: "No file changes detected in this turn",
@@ -18772,7 +18773,8 @@ var zh_default = {
18772
18773
  hidden: "\u5DF2\u9690\u85CF {{count}} \u884C",
18773
18774
  more: "\xB7\xB7\xB7 \u66F4\u591A +{{count}} \xB7\xB7\xB7",
18774
18775
  expandUp: "\u5411\u4E0A\u5C55\u5F00 {{count}} \u884C",
18775
- expandDown: "\u5411\u4E0B\u5C55\u5F00 {{count}} \u884C"
18776
+ expandDown: "\u5411\u4E0B\u5C55\u5F00 {{count}} \u884C",
18777
+ expandAll: "\u70B9\u51FB\u5C55\u5F00 {{count}} \u884C"
18776
18778
  },
18777
18779
  loadingSnapshots: "\u6B63\u5728\u52A0\u8F7D\u5FEB\u7167\u2026",
18778
18780
  noChanges: "\u672C\u8F6E\u672A\u68C0\u6D4B\u5230\u6587\u4EF6\u53D8\u66F4",
package/dist/wsServer.mjs CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  resolvePendingRequest,
14
14
  unregisterBrowser,
15
15
  writeStdinToCommand
16
- } from "./chunk-WLCT5LHX.mjs";
16
+ } from "./chunk-PB4GBZQO.mjs";
17
17
  import {
18
18
  addRunListener,
19
19
  getGlobalSessionsSnapshot,
@@ -21,7 +21,7 @@ import {
21
21
  recoverStashedCodexRollouts,
22
22
  recoverStashedTranscripts,
23
23
  sanitizedSpawnEnv
24
- } from "./chunk-VAOGZ6SO.mjs";
24
+ } from "./chunk-TOA52B2X.mjs";
25
25
  import {
26
26
  Effect_exports,
27
27
  FSError,
@@ -35,7 +35,7 @@ import {
35
35
  Stream_exports,
36
36
  ValidationError,
37
37
  WSError
38
- } from "./chunk-RZY32R7X.mjs";
38
+ } from "./chunk-4EU4KZIO.mjs";
39
39
  import "./chunk-7P6ASYW6.mjs";
40
40
 
41
41
  // src/lib/wsServer.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@surething/cockpit",
3
- "version": "1.0.271",
3
+ "version": "1.0.272",
4
4
  "description": "OpenCockpit is the open-source Claude Code GUI — an IDE-like workbench for parallel AI coding. Drive Claude (default), Codex, DeepSeek, Kimi, or local Ollama in tabs. Terminal, browser & DB bubbles, code review. Self-host on a shared dev box — a seat for every teammate. Local-first, MIT.",
5
5
  "author": "Robert",
6
6
  "license": "MIT",