@surething/cockpit 1.0.215 → 1.0.217

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 (66) hide show
  1. package/.next-prod/BUILD_ID +1 -1
  2. package/.next-prod/app-path-routes-manifest.json +2 -2
  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_client-reference-manifest.js +1 -1
  6. package/.next-prod/server/app/_global-error.html +1 -1
  7. package/.next-prod/server/app/_global-error.rsc +1 -1
  8. package/.next-prod/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next-prod/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  10. package/.next-prod/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  11. package/.next-prod/server/app/_global-error.segments/_head.segment.rsc +1 -1
  12. package/.next-prod/server/app/_global-error.segments/_index.segment.rsc +1 -1
  13. package/.next-prod/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next-prod/server/app/_not-found/page_client-reference-manifest.js +1 -1
  15. package/.next-prod/server/app/_not-found.html +1 -1
  16. package/.next-prod/server/app/_not-found.rsc +3 -3
  17. package/.next-prod/server/app/_not-found.segments/_full.segment.rsc +3 -3
  18. package/.next-prod/server/app/_not-found.segments/_head.segment.rsc +1 -1
  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/_not-found.segment.rsc +1 -1
  22. package/.next-prod/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next-prod/server/app/api/projectGraph/search/route.js +1 -1
  24. package/.next-prod/server/app/api/terminal/bubble-order/route.js +1 -1
  25. package/.next-prod/server/app/page_client-reference-manifest.js +1 -1
  26. package/.next-prod/server/app/project/page_client-reference-manifest.js +1 -1
  27. package/.next-prod/server/app/review/[id]/page_client-reference-manifest.js +1 -1
  28. package/.next-prod/server/app-paths-manifest.json +2 -2
  29. package/.next-prod/server/chunks/2939.js +1 -1
  30. package/.next-prod/server/chunks/8916.js +1 -1
  31. package/.next-prod/server/chunks/9658.js +5 -5
  32. package/.next-prod/server/chunks/9877.js +10 -1
  33. package/.next-prod/server/functions-config-manifest.json +1 -0
  34. package/.next-prod/server/middleware-build-manifest.js +1 -1
  35. package/.next-prod/server/pages/404.html +1 -1
  36. package/.next-prod/server/pages/500.html +1 -1
  37. package/.next-prod/server/server-reference-manifest.json +1 -1
  38. package/.next-prod/static/chunks/6345-d477b8d5c682b1fb.js +14 -0
  39. package/.next-prod/static/chunks/6917-0a22d7764ca45244.js +29 -0
  40. package/.next-prod/static/chunks/app/{layout-a0362651ba6e6e6f.js → layout-8e3a54b794cb35b6.js} +1 -1
  41. package/.next-prod/static/chunks/app/{project/page-1b14cabf47df9ff7.js → page-3ab0a0f28cbdc8e2.js} +1 -1
  42. package/.next-prod/static/chunks/app/{page-1b14cabf47df9ff7.js → project/page-3ab0a0f28cbdc8e2.js} +1 -1
  43. package/.next-prod/static/css/fc2730c2dbe4866e.css +1 -0
  44. package/.next-prod/trace +13 -13
  45. package/.next-prod/trace-build +1 -1
  46. package/README.md +3 -2
  47. package/README.zh.md +3 -2
  48. package/bin/cock-codegraph.mjs +28 -13
  49. package/bin/cock-connection.mjs +151 -0
  50. package/bin/cock.mjs +12 -1
  51. package/bin/setup-dev.mjs +15 -13
  52. package/dist/{chunk-CZWJPTRO.mjs → chunk-GCYLMG43.mjs} +2486 -1047
  53. package/dist/chunk-O4P2J44N.mjs +314 -0
  54. package/dist/{chunk-KRTISG5I.mjs → chunk-W6G6X3FP.mjs} +196 -9
  55. package/dist/httpApi.mjs +75 -2
  56. package/dist/scheduledTasks.mjs +9 -1158
  57. package/dist/{server-OSOMFNXR.mjs → server-ZBUZ24TC.mjs} +4 -2
  58. package/dist/wsServer.mjs +24 -19
  59. package/package.json +1 -1
  60. package/server.mjs +5 -1
  61. package/.next-prod/static/chunks/5188-415582403ef0e29c.js +0 -29
  62. package/.next-prod/static/chunks/6345-e5ceeb2aeb698eb6.js +0 -14
  63. package/.next-prod/static/css/cc6d733cdf607b30.css +0 -1
  64. package/dist/chunk-ZJ6CC3MH.mjs +0 -223
  65. /package/.next-prod/static/{dtH6UmANw3cgn-jbRuVu4 → 7pu1LXbRRLfg05VN3u39s}/_buildManifest.js +0 -0
  66. /package/.next-prod/static/{dtH6UmANw3cgn-jbRuVu4 → 7pu1LXbRRLfg05VN3u39s}/_ssgManifest.js +0 -0
@@ -0,0 +1,314 @@
1
+ import {
2
+ GLOBAL_STATE_FILE,
3
+ findCodexSessionPath,
4
+ findKimiSessionPath,
5
+ getClaude2SessionPath,
6
+ getClaudeSessionPath,
7
+ getOllamaSessionPath,
8
+ readJsonFile,
9
+ withFileLock,
10
+ writeJsonFile
11
+ } from "./chunk-GCYLMG43.mjs";
12
+
13
+ // packages/feature/agent/src/server/state/globalState.ts
14
+ import { createReadStream, existsSync } from "fs";
15
+ import { createInterface } from "readline";
16
+ var MAX_SESSIONS = 15;
17
+ var MAX_TEXT_LEN = 50;
18
+ function truncate(s) {
19
+ if (!s) return s;
20
+ const chars = [...s];
21
+ return chars.length <= MAX_TEXT_LEN ? s : chars.slice(0, MAX_TEXT_LEN).join("") + "\u2026";
22
+ }
23
+ async function updateGlobalState(cwd, sessionId, status, title, lastUserMessage) {
24
+ if (!existsSync(cwd)) {
25
+ return;
26
+ }
27
+ return withFileLock(GLOBAL_STATE_FILE, async () => {
28
+ const state = await readJsonFile(GLOBAL_STATE_FILE, { sessions: [] });
29
+ for (const s of state.sessions) {
30
+ if (!s.status) {
31
+ const legacy = s;
32
+ s.status = legacy.isLoading ? "loading" : "normal";
33
+ delete legacy.isLoading;
34
+ }
35
+ }
36
+ const existingIndex = state.sessions.findIndex(
37
+ (s) => s.cwd === cwd && s.sessionId === sessionId
38
+ );
39
+ const existed = existingIndex >= 0;
40
+ const existing = existed ? state.sessions[existingIndex] : void 0;
41
+ const newSession = {
42
+ cwd,
43
+ sessionId,
44
+ lastActive: Date.now(),
45
+ status,
46
+ title: truncate(title || existing?.title),
47
+ lastUserMessage: truncate(lastUserMessage || existing?.lastUserMessage)
48
+ };
49
+ if (existingIndex >= 0) {
50
+ state.sessions[existingIndex] = newSession;
51
+ } else {
52
+ state.sessions.push(newSession);
53
+ }
54
+ state.sessions.sort((a, b) => b.lastActive - a.lastActive);
55
+ state.sessions = state.sessions.slice(0, MAX_SESSIONS);
56
+ await writeJsonFile(GLOBAL_STATE_FILE, state);
57
+ });
58
+ }
59
+ async function getSessionTitle(cwd, sessionId) {
60
+ const claudePath = getClaudeSessionPath(cwd, sessionId);
61
+ if (existsSync(claudePath)) {
62
+ return getClaudeStyleTitle(claudePath);
63
+ }
64
+ const claude2Path = getClaude2SessionPath(cwd, sessionId);
65
+ if (existsSync(claude2Path)) {
66
+ return getClaudeStyleTitle(claude2Path);
67
+ }
68
+ const ollamaPath = getOllamaSessionPath(cwd, sessionId);
69
+ if (existsSync(ollamaPath)) {
70
+ return getClaudeStyleTitle(ollamaPath);
71
+ }
72
+ const codexPath = findCodexSessionPath(sessionId);
73
+ if (codexPath && existsSync(codexPath)) {
74
+ const title = await getCodexTitle(codexPath);
75
+ return title || "Untitled Session";
76
+ }
77
+ const kimiPath = findKimiSessionPath(sessionId);
78
+ if (kimiPath && existsSync(kimiPath)) {
79
+ const title = await getKimiTitle(kimiPath);
80
+ return title || "Untitled Session";
81
+ }
82
+ return "Untitled Session";
83
+ }
84
+ async function getLastUserMessage(cwd, sessionId) {
85
+ const claudePath = getClaudeSessionPath(cwd, sessionId);
86
+ if (existsSync(claudePath)) {
87
+ return await getClaudeStyleLastUserMessage(claudePath);
88
+ }
89
+ const claude2Path = getClaude2SessionPath(cwd, sessionId);
90
+ if (existsSync(claude2Path)) {
91
+ return await getClaudeStyleLastUserMessage(claude2Path);
92
+ }
93
+ const ollamaPath = getOllamaSessionPath(cwd, sessionId);
94
+ if (existsSync(ollamaPath)) {
95
+ return await getClaudeStyleLastUserMessage(ollamaPath);
96
+ }
97
+ const codexPath = findCodexSessionPath(sessionId);
98
+ if (codexPath && existsSync(codexPath)) {
99
+ return await getCodexLastUserMessage(codexPath);
100
+ }
101
+ const kimiPath = findKimiSessionPath(sessionId);
102
+ if (kimiPath && existsSync(kimiPath)) {
103
+ return await getKimiLastUserMessage(kimiPath);
104
+ }
105
+ return void 0;
106
+ }
107
+ function filterCommandTags(text) {
108
+ let filtered = text.replace(/<command-[^>]*>[\s\S]*?<\/command-[^>]*>/g, "");
109
+ filtered = filtered.replace(/<local-command-[^>]*>[\s\S]*?<\/local-command-[^>]*>/g, "");
110
+ filtered = filtered.trim();
111
+ return filtered;
112
+ }
113
+ function isValidUserMessage(text) {
114
+ if (text.startsWith("This session is being continued")) return false;
115
+ if (text.startsWith("Caveat: The messages below")) return false;
116
+ if (!text.trim()) return false;
117
+ return true;
118
+ }
119
+ async function getClaudeStyleTitle(filePath) {
120
+ try {
121
+ const fileStream = createReadStream(filePath);
122
+ const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
123
+ let summary = "";
124
+ const userMessages = [];
125
+ for await (const line of rl) {
126
+ if (!line.trim()) continue;
127
+ try {
128
+ const entry = JSON.parse(line);
129
+ if (entry.type === "summary" && entry.summary) {
130
+ summary = entry.summary;
131
+ }
132
+ if (entry.type === "user") {
133
+ const message = entry.message;
134
+ if (!message?.content) continue;
135
+ if (typeof message.content === "string") {
136
+ userMessages.push(message.content);
137
+ } else if (Array.isArray(message.content)) {
138
+ for (const block of message.content) {
139
+ if (block.type === "text" && block.text) userMessages.push(block.text);
140
+ }
141
+ }
142
+ }
143
+ } catch {
144
+ }
145
+ }
146
+ return generateTitle(summary, userMessages);
147
+ } catch {
148
+ return "Untitled Session";
149
+ }
150
+ }
151
+ async function getClaudeStyleLastUserMessage(filePath) {
152
+ try {
153
+ const fileStream = createReadStream(filePath);
154
+ const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
155
+ let lastUserMessage;
156
+ for await (const line of rl) {
157
+ if (!line.trim()) continue;
158
+ try {
159
+ const entry = JSON.parse(line);
160
+ if (entry.type !== "user") continue;
161
+ const message = entry.message;
162
+ if (!message?.content) continue;
163
+ let text = "";
164
+ if (typeof message.content === "string") {
165
+ text = message.content;
166
+ } else if (Array.isArray(message.content)) {
167
+ for (const block of message.content) {
168
+ if (block.type === "text" && block.text) {
169
+ text = block.text;
170
+ break;
171
+ }
172
+ }
173
+ }
174
+ if (!text) continue;
175
+ const filtered = filterCommandTags(text);
176
+ if (filtered && isValidUserMessage(filtered)) {
177
+ lastUserMessage = filtered;
178
+ }
179
+ } catch {
180
+ }
181
+ }
182
+ return lastUserMessage;
183
+ } catch {
184
+ return void 0;
185
+ }
186
+ }
187
+ async function getCodexLastUserMessage(filePath) {
188
+ try {
189
+ const fileStream = createReadStream(filePath);
190
+ const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
191
+ let last;
192
+ for await (const line of rl) {
193
+ if (!line.trim()) continue;
194
+ let entry;
195
+ try {
196
+ entry = JSON.parse(line);
197
+ } catch {
198
+ continue;
199
+ }
200
+ if (entry.type !== "response_item") continue;
201
+ const payload = entry.payload;
202
+ if (!payload || payload.type !== "message" || payload.role !== "user") continue;
203
+ const text = payload.content?.filter((c) => c.type === "input_text" && c.text).map((c) => c.text).join("") || "";
204
+ if (!text || text.startsWith("<") || text.startsWith("#")) continue;
205
+ const filtered = filterCommandTags(text);
206
+ if (filtered && isValidUserMessage(filtered)) {
207
+ last = filtered;
208
+ }
209
+ }
210
+ return last;
211
+ } catch {
212
+ return void 0;
213
+ }
214
+ }
215
+ async function getCodexTitle(filePath) {
216
+ try {
217
+ const fileStream = createReadStream(filePath);
218
+ const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
219
+ for await (const line of rl) {
220
+ if (!line.trim()) continue;
221
+ let entry;
222
+ try {
223
+ entry = JSON.parse(line);
224
+ } catch {
225
+ continue;
226
+ }
227
+ if (entry.type !== "response_item") continue;
228
+ const payload = entry.payload;
229
+ if (!payload || payload.type !== "message" || payload.role !== "user") continue;
230
+ const text = payload.content?.filter((c) => c.type === "input_text" && c.text).map((c) => c.text).join("") || "";
231
+ if (!text || text.startsWith("<") || text.startsWith("#")) continue;
232
+ return text.slice(0, 80);
233
+ }
234
+ return void 0;
235
+ } catch {
236
+ return void 0;
237
+ }
238
+ }
239
+ async function getKimiLastUserMessage(filePath) {
240
+ try {
241
+ const fileStream = createReadStream(filePath);
242
+ const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
243
+ let last;
244
+ for await (const line of rl) {
245
+ if (!line.trim()) continue;
246
+ let entry;
247
+ try {
248
+ entry = JSON.parse(line);
249
+ } catch {
250
+ continue;
251
+ }
252
+ if (entry.role !== "user") continue;
253
+ const text = typeof entry.content === "string" ? entry.content : Array.isArray(entry.content) ? entry.content.filter((c) => (c.type === "input_text" || c.type === "text") && c.text).map((c) => c.text).join("") : "";
254
+ if (!text || text.startsWith("<system") || text.startsWith("<environment") || text.startsWith("# AGENTS.md") || text.startsWith("<permissions")) {
255
+ continue;
256
+ }
257
+ const filtered = filterCommandTags(text);
258
+ if (filtered && isValidUserMessage(filtered)) {
259
+ last = filtered;
260
+ }
261
+ }
262
+ return last;
263
+ } catch {
264
+ return void 0;
265
+ }
266
+ }
267
+ async function getKimiTitle(filePath) {
268
+ try {
269
+ const fileStream = createReadStream(filePath);
270
+ const rl = createInterface({ input: fileStream, crlfDelay: Infinity });
271
+ for await (const line of rl) {
272
+ if (!line.trim()) continue;
273
+ let entry;
274
+ try {
275
+ entry = JSON.parse(line);
276
+ } catch {
277
+ continue;
278
+ }
279
+ if (entry.role !== "user") continue;
280
+ const text = typeof entry.content === "string" ? entry.content : Array.isArray(entry.content) ? entry.content.filter((c) => (c.type === "input_text" || c.type === "text") && c.text).map((c) => c.text).join("") : "";
281
+ if (!text || text.startsWith("<system") || text.startsWith("<environment") || text.startsWith("# AGENTS.md") || text.startsWith("<permissions")) {
282
+ continue;
283
+ }
284
+ return text.slice(0, 80);
285
+ }
286
+ return void 0;
287
+ } catch {
288
+ return void 0;
289
+ }
290
+ }
291
+ function generateTitle(summary, userMessages) {
292
+ if (summary) return summary;
293
+ let commandName = "";
294
+ for (const msg of userMessages) {
295
+ const filtered = filterCommandTags(msg);
296
+ if (!filtered) continue;
297
+ if (filtered.startsWith("/") && !commandName) {
298
+ commandName = filtered;
299
+ continue;
300
+ }
301
+ if (commandName) {
302
+ return `${commandName} ${filtered}`;
303
+ }
304
+ return filtered;
305
+ }
306
+ if (commandName) return commandName;
307
+ return "Untitled Session";
308
+ }
309
+
310
+ export {
311
+ updateGlobalState,
312
+ getSessionTitle,
313
+ getLastUserMessage
314
+ };
@@ -1,8 +1,19 @@
1
1
  import {
2
+ AppRuntime,
3
+ Cause_exports,
4
+ Effect_exports,
5
+ Exit_exports,
6
+ FSError,
7
+ Option_exports,
8
+ ValidationError,
2
9
  ensureParentDir,
10
+ errorToStatus,
11
+ getBubbleOrderPath,
3
12
  getTerminalHistoryPath,
4
- getTerminalOutputPath
5
- } from "./chunk-ZJ6CC3MH.mjs";
13
+ getTerminalOutputPath,
14
+ readJsonFile,
15
+ writeJsonFile
16
+ } from "./chunk-GCYLMG43.mjs";
6
17
 
7
18
  // packages/feature/console/src/server/plugins/browser/BrowserBridge.ts
8
19
  import { WebSocket } from "ws";
@@ -33,9 +44,9 @@ function toShortId(fullId) {
33
44
  var g_browser = globalThis;
34
45
  var registry = g_browser.__cockpitBrowserRegistry ?? (g_browser.__cockpitBrowserRegistry = /* @__PURE__ */ new Map());
35
46
  var fullIdToShort = g_browser.__cockpitBrowserFullIdToShort ?? (g_browser.__cockpitBrowserFullIdToShort = /* @__PURE__ */ new Map());
36
- function registerBrowser(fullId, ws) {
47
+ function registerBrowser(fullId, ws, projectCwd, tabId) {
37
48
  const shortId = toShortId(fullId);
38
- registry.set(shortId, { fullId, ws, lastSeen: Date.now() });
49
+ registry.set(shortId, { fullId, ws, lastSeen: Date.now(), projectCwd, tabId });
39
50
  fullIdToShort.set(fullId, shortId);
40
51
  return shortId;
41
52
  }
@@ -65,7 +76,9 @@ function listBrowsers() {
65
76
  result.push({
66
77
  shortId,
67
78
  fullId: entry.fullId,
68
- connected: entry.ws !== null && entry.ws.readyState === WebSocket.OPEN
79
+ connected: entry.ws !== null && entry.ws.readyState === WebSocket.OPEN,
80
+ projectCwd: entry.projectCwd,
81
+ tabId: entry.tabId
69
82
  });
70
83
  }
71
84
  return result;
@@ -80,12 +93,12 @@ function createPendingRequest(reqId, timeout) {
80
93
  pendingRequests.set(reqId, { resolve, reject, timer });
81
94
  });
82
95
  }
83
- function resolvePendingRequest(reqId, ok, data, error) {
96
+ function resolvePendingRequest(reqId, ok2, data, error) {
84
97
  const pending = pendingRequests.get(reqId);
85
98
  if (!pending) return;
86
99
  clearTimeout(pending.timer);
87
100
  pendingRequests.delete(reqId);
88
- if (ok) {
101
+ if (ok2) {
89
102
  pending.resolve(data);
90
103
  } else {
91
104
  pending.reject(new Error(error || "Browser command failed"));
@@ -440,7 +453,8 @@ function listTerminals(getRunning) {
440
453
  tabId: entry.tabId,
441
454
  command: entry.command,
442
455
  pid: cmd?.pid ?? 0,
443
- running: !!cmd
456
+ running: !!cmd,
457
+ projectCwd: entry.projectCwd
444
458
  });
445
459
  }
446
460
  return result;
@@ -926,6 +940,178 @@ function grepOutput(cmd, pattern, opts = {}) {
926
940
  };
927
941
  }
928
942
 
943
+ // packages/shared/effect-runtime/src/next.ts
944
+ var ok = (body, status = 200) => new Response(JSON.stringify(body), {
945
+ status,
946
+ headers: { "content-type": "application/json" }
947
+ });
948
+ var extractErrorMessage = (e) => {
949
+ const maybeMsg = e.message;
950
+ if (typeof maybeMsg === "string" && maybeMsg.length > 0) {
951
+ return maybeMsg;
952
+ }
953
+ const cause = e.cause;
954
+ if (cause instanceof Error && cause.message.length > 0) {
955
+ return cause.message;
956
+ }
957
+ switch (e._tag) {
958
+ case "ValidationError": {
959
+ const v = e;
960
+ return `Invalid ${v.field}: ${v.reason}`;
961
+ }
962
+ case "NotFoundError": {
963
+ const n = e;
964
+ return `${n.resource} not found: ${n.id}`;
965
+ }
966
+ case "PermissionError": {
967
+ const p = e;
968
+ return `Permission denied: ${p.action} on ${p.resource}`;
969
+ }
970
+ case "DBError": {
971
+ const d = e;
972
+ return `${d.db} ${d.op} failed`;
973
+ }
974
+ case "FSError": {
975
+ const f = e;
976
+ return `${f.op} ${f.path} failed`;
977
+ }
978
+ case "WSError": {
979
+ const w = e;
980
+ return `${w.proto} ${w.kind} failed`;
981
+ }
982
+ case "AgentError": {
983
+ const a = e;
984
+ return `${a.provider} ${a.kind} failed`;
985
+ }
986
+ default:
987
+ return e._tag;
988
+ }
989
+ };
990
+ var errorToResponse = (cause) => {
991
+ const failure = Cause_exports.failureOption(cause);
992
+ if (Option_exports.isSome(failure)) {
993
+ const e = failure.value;
994
+ const status = errorToStatus(e);
995
+ return new Response(
996
+ JSON.stringify({ error: extractErrorMessage(e), tag: e._tag }),
997
+ {
998
+ status,
999
+ headers: { "content-type": "application/json" }
1000
+ }
1001
+ );
1002
+ }
1003
+ console.error("[handler] uncaught defect:\n" + Cause_exports.pretty(cause));
1004
+ return new Response(
1005
+ JSON.stringify({ error: "Internal Server Error", tag: "InternalError" }),
1006
+ {
1007
+ status: 500,
1008
+ headers: { "content-type": "application/json" }
1009
+ }
1010
+ );
1011
+ };
1012
+ var handler = (fn) => async (req) => {
1013
+ const exit = await AppRuntime.runPromiseExit(fn(req));
1014
+ return Exit_exports.match(exit, {
1015
+ onFailure: (cause) => errorToResponse(cause),
1016
+ onSuccess: (res) => res
1017
+ });
1018
+ };
1019
+ var parseJsonRaw = (req) => Effect_exports.tryPromise({
1020
+ try: () => req.json(),
1021
+ catch: () => new ValidationError({
1022
+ field: "body",
1023
+ reason: "invalid JSON"
1024
+ })
1025
+ });
1026
+
1027
+ // packages/feature/console/src/server/api/terminal/bubble-order.ts
1028
+ async function readBubbleTitles(cwd, tabId) {
1029
+ try {
1030
+ const raw = await readJsonFile(getBubbleOrderPath(cwd, tabId), []);
1031
+ return normalise(raw).titles;
1032
+ } catch {
1033
+ return {};
1034
+ }
1035
+ }
1036
+ function normalise(raw) {
1037
+ if (Array.isArray(raw)) return { order: raw, titles: {} };
1038
+ if (raw && typeof raw === "object") {
1039
+ const r = raw;
1040
+ return {
1041
+ order: Array.isArray(r.order) ? r.order : [],
1042
+ titles: r.titles && typeof r.titles === "object" ? r.titles : {}
1043
+ };
1044
+ }
1045
+ return { order: [], titles: {} };
1046
+ }
1047
+ var GET = handler(
1048
+ (req) => Effect_exports.gen(function* () {
1049
+ const sp = new URL(req.url).searchParams;
1050
+ const cwd = sp.get("cwd");
1051
+ const tabId = sp.get("tabId");
1052
+ if (!cwd || !tabId) {
1053
+ return yield* Effect_exports.fail(
1054
+ new ValidationError({
1055
+ field: !cwd ? "cwd" : "tabId",
1056
+ reason: "missing"
1057
+ })
1058
+ );
1059
+ }
1060
+ const orderPath = getBubbleOrderPath(cwd, tabId);
1061
+ const raw = yield* Effect_exports.tryPromise({
1062
+ try: () => readJsonFile(orderPath, []),
1063
+ catch: (cause) => new FSError({ path: orderPath, op: "read", cause })
1064
+ });
1065
+ const file = normalise(raw);
1066
+ return ok({ order: file.order, titles: file.titles });
1067
+ })
1068
+ );
1069
+ var POST = handler(
1070
+ (req) => Effect_exports.gen(function* () {
1071
+ const body = yield* parseJsonRaw(req);
1072
+ if (!body.cwd || !body.tabId) {
1073
+ return yield* Effect_exports.fail(
1074
+ new ValidationError({
1075
+ field: !body.cwd ? "cwd" : "tabId",
1076
+ reason: "missing"
1077
+ })
1078
+ );
1079
+ }
1080
+ const hasOrder = Array.isArray(body.order);
1081
+ const hasTitles = body.titles && typeof body.titles === "object";
1082
+ if (!hasOrder && !hasTitles) {
1083
+ return yield* Effect_exports.fail(
1084
+ new ValidationError({ field: "order|titles", reason: "missing \u2014 provide at least one" })
1085
+ );
1086
+ }
1087
+ const orderPath = getBubbleOrderPath(body.cwd, body.tabId);
1088
+ const existing = normalise(
1089
+ yield* Effect_exports.tryPromise({
1090
+ try: () => readJsonFile(orderPath, []),
1091
+ catch: (cause) => new FSError({ path: orderPath, op: "read", cause })
1092
+ })
1093
+ );
1094
+ const next = {
1095
+ order: hasOrder ? body.order : existing.order,
1096
+ titles: hasTitles ? mergeTitles(existing.titles, body.titles) : existing.titles
1097
+ };
1098
+ yield* Effect_exports.tryPromise({
1099
+ try: () => writeJsonFile(orderPath, next),
1100
+ catch: (cause) => new FSError({ path: orderPath, op: "write", cause })
1101
+ });
1102
+ return ok({ success: true });
1103
+ })
1104
+ );
1105
+ function mergeTitles(base, patch) {
1106
+ const out = { ...base };
1107
+ for (const [k, v] of Object.entries(patch)) {
1108
+ if (typeof v !== "string") continue;
1109
+ if (v === "") delete out[k];
1110
+ else out[k] = v.slice(0, 256);
1111
+ }
1112
+ return out;
1113
+ }
1114
+
929
1115
  export {
930
1116
  registerBrowser,
931
1117
  unregisterBrowser,
@@ -965,5 +1151,6 @@ export {
965
1151
  readTail,
966
1152
  readHead,
967
1153
  readAround,
968
- grepOutput
1154
+ grepOutput,
1155
+ readBubbleTitles
969
1156
  };
package/dist/httpApi.mjs CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  listBrowsers,
11
11
  listTerminals,
12
12
  readAround,
13
+ readBubbleTitles,
13
14
  readHead,
14
15
  readSince,
15
16
  readTail,
@@ -19,10 +20,10 @@ import {
19
20
  unregisterBrowser,
20
21
  unregisterTerminal,
21
22
  writeStdinToCommand
22
- } from "./chunk-KRTISG5I.mjs";
23
+ } from "./chunk-W6G6X3FP.mjs";
23
24
  import {
24
25
  getTerminalHistoryPath
25
- } from "./chunk-ZJ6CC3MH.mjs";
26
+ } from "./chunk-GCYLMG43.mjs";
26
27
  import "./chunk-7P6ASYW6.mjs";
27
28
 
28
29
  // src/lib/httpApi.ts
@@ -30,6 +31,7 @@ import { parse } from "url";
30
31
  import { readFile } from "fs/promises";
31
32
  import { randomUUID } from "crypto";
32
33
  import { WebSocket } from "ws";
34
+ import { resolve as resolvePath } from "path";
33
35
  async function readFinishedOutput(projectCwd, tabId, commandId) {
34
36
  try {
35
37
  const historyPath = getTerminalHistoryPath(projectCwd, tabId);
@@ -419,7 +421,78 @@ async function handleBrowserApi(req, res) {
419
421
  }
420
422
  return true;
421
423
  }
424
+ async function handleConnectionApi(req, res) {
425
+ const { pathname } = parse(req.url || "", true);
426
+ const match = pathname?.match(/^\/api\/connection\/([a-z]+)$/);
427
+ if (!match || req.method !== "POST") return false;
428
+ if (match[1] !== "list") return false;
429
+ const chunks = [];
430
+ for await (const chunk of req) chunks.push(chunk);
431
+ let body = {};
432
+ try {
433
+ body = JSON.parse(Buffer.concat(chunks).toString());
434
+ } catch {
435
+ }
436
+ const sendJson = (status, data) => {
437
+ res.writeHead(status, { "Content-Type": "application/json" });
438
+ res.end(JSON.stringify(data));
439
+ };
440
+ const filterCwd = body.cwd ? resolvePath(body.cwd) : void 0;
441
+ const aliveOnly = !body.all;
442
+ const terms = listTerminals(getRunningCommand);
443
+ const browsers = listBrowsers();
444
+ const sameCwd = (entryCwd) => !filterCwd ? true : !!entryCwd && resolvePath(entryCwd) === filterCwd;
445
+ const SEP = String.fromCharCode(31);
446
+ const cwdTabPairs = /* @__PURE__ */ new Set();
447
+ for (const t of terms) {
448
+ if (t.projectCwd && t.tabId) cwdTabPairs.add(`${t.projectCwd}${SEP}${t.tabId}`);
449
+ }
450
+ for (const b of browsers) {
451
+ if (b.projectCwd && b.tabId) cwdTabPairs.add(`${b.projectCwd}${SEP}${b.tabId}`);
452
+ }
453
+ const titlesByPair = /* @__PURE__ */ new Map();
454
+ await Promise.all(
455
+ Array.from(cwdTabPairs).map(async (pair) => {
456
+ const [cwd, tabId] = pair.split(SEP);
457
+ titlesByPair.set(pair, await readBubbleTitles(cwd, tabId));
458
+ })
459
+ );
460
+ const titleOf = (cwd, tabId, key) => {
461
+ if (!cwd || !tabId) return void 0;
462
+ const t = titlesByPair.get(`${cwd}${SEP}${tabId}`)?.[key];
463
+ return t || void 0;
464
+ };
465
+ const out = [];
466
+ for (const t of terms) {
467
+ if (!sameCwd(t.projectCwd)) continue;
468
+ if (aliveOnly && !t.running) continue;
469
+ out.push({
470
+ type: "terminal",
471
+ shortId: t.shortId,
472
+ title: titleOf(t.projectCwd, t.tabId, t.commandId),
473
+ projectCwd: t.projectCwd,
474
+ tabId: t.tabId,
475
+ command: t.command,
476
+ alive: t.running
477
+ });
478
+ }
479
+ for (const b of browsers) {
480
+ if (!sameCwd(b.projectCwd)) continue;
481
+ if (aliveOnly && !b.connected) continue;
482
+ out.push({
483
+ type: "browser",
484
+ shortId: b.shortId,
485
+ title: titleOf(b.projectCwd, b.tabId, b.fullId),
486
+ projectCwd: b.projectCwd,
487
+ tabId: b.tabId,
488
+ alive: b.connected
489
+ });
490
+ }
491
+ sendJson(200, { ok: true, data: out });
492
+ return true;
493
+ }
422
494
  export {
423
495
  handleBrowserApi,
496
+ handleConnectionApi,
424
497
  handleTerminalApi
425
498
  };