tabctl 0.1.4 → 0.2.1

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 (48) hide show
  1. package/{cli → dist/cli}/lib/commands/index.js +4 -2
  2. package/dist/cli/lib/commands/meta.js +226 -0
  3. package/dist/cli/lib/commands/params-groups.js +40 -0
  4. package/dist/cli/lib/commands/params-move.js +44 -0
  5. package/{cli → dist/cli}/lib/commands/params.js +61 -125
  6. package/{cli/lib/commands/meta.js → dist/cli/lib/commands/setup.js} +26 -222
  7. package/{cli/lib/options.js → dist/cli/lib/options-commands.js} +3 -155
  8. package/dist/cli/lib/options-groups.js +41 -0
  9. package/dist/cli/lib/options.js +125 -0
  10. package/{cli → dist/cli}/lib/output.js +5 -4
  11. package/dist/cli/lib/policy-filter.js +202 -0
  12. package/dist/cli/lib/response.js +235 -0
  13. package/{cli → dist/cli}/lib/scope.js +3 -31
  14. package/dist/cli/tabctl.js +463 -0
  15. package/dist/extension/background.js +3398 -0
  16. package/dist/extension/lib/archive.js +444 -0
  17. package/dist/extension/lib/content.js +320 -0
  18. package/dist/extension/lib/deps.js +4 -0
  19. package/dist/extension/lib/groups.js +443 -0
  20. package/dist/extension/lib/inspect.js +316 -0
  21. package/dist/extension/lib/move.js +342 -0
  22. package/dist/extension/lib/screenshot.js +367 -0
  23. package/dist/extension/lib/tabs.js +395 -0
  24. package/dist/extension/lib/undo-handlers.js +439 -0
  25. package/{extension → dist/extension}/manifest.json +2 -2
  26. package/dist/host/host.js +124 -0
  27. package/{host/host.js → dist/host/lib/handlers.js} +84 -187
  28. package/{shared → dist/shared}/version.js +2 -2
  29. package/package.json +12 -10
  30. package/cli/tabctl.js +0 -841
  31. package/extension/background.js +0 -3372
  32. package/extension/manifest.template.json +0 -22
  33. /package/{cli → dist/cli}/lib/args.js +0 -0
  34. /package/{cli → dist/cli}/lib/client.js +0 -0
  35. /package/{cli → dist/cli}/lib/commands/list.js +0 -0
  36. /package/{cli → dist/cli}/lib/commands/profile.js +0 -0
  37. /package/{cli → dist/cli}/lib/constants.js +0 -0
  38. /package/{cli → dist/cli}/lib/help.js +0 -0
  39. /package/{cli → dist/cli}/lib/pagination.js +0 -0
  40. /package/{cli → dist/cli}/lib/policy.js +0 -0
  41. /package/{cli → dist/cli}/lib/report.js +0 -0
  42. /package/{cli → dist/cli}/lib/snapshot.js +0 -0
  43. /package/{cli → dist/cli}/lib/types.js +0 -0
  44. /package/{host → dist/host}/host.sh +0 -0
  45. /package/{host → dist/host}/lib/undo.js +0 -0
  46. /package/{shared → dist/shared}/config.js +0 -0
  47. /package/{shared → dist/shared}/extension-sync.js +0 -0
  48. /package/{shared → dist/shared}/profiles.js +0 -0
@@ -0,0 +1,439 @@
1
+ "use strict";
2
+ // Undo transaction handlers — extracted from background.ts (pure structural refactor).
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.undoGroupUpdate = undoGroupUpdate;
5
+ exports.undoGroupUngroup = undoGroupUngroup;
6
+ exports.undoGroupAssign = undoGroupAssign;
7
+ exports.undoMoveTab = undoMoveTab;
8
+ exports.undoMoveGroup = undoMoveGroup;
9
+ exports.undoMergeWindow = undoMergeWindow;
10
+ exports.undoArchive = undoArchive;
11
+ exports.undoClose = undoClose;
12
+ exports.undoTransaction = undoTransaction;
13
+ async function ensureWindow(windowId) {
14
+ if (windowId) {
15
+ try {
16
+ const existing = await chrome.windows.get(windowId);
17
+ if (existing) {
18
+ return windowId;
19
+ }
20
+ }
21
+ catch {
22
+ // fall through to create
23
+ }
24
+ }
25
+ const created = await chrome.windows.create({ focused: false });
26
+ return created.id;
27
+ }
28
+ async function restoreTabsFromUndo(entries, deps) {
29
+ const skipped = [];
30
+ const restored = [];
31
+ const windowMap = new Map();
32
+ for (const entry of entries) {
33
+ const tabId = Number(entry.tabId);
34
+ if (!Number.isFinite(tabId)) {
35
+ skipped.push({ tabId: entry.tabId, reason: "missing_tab" });
36
+ continue;
37
+ }
38
+ const sourceWindowId = Number(entry.windowId);
39
+ if (!Number.isFinite(sourceWindowId)) {
40
+ skipped.push({ tabId, reason: "missing_window" });
41
+ continue;
42
+ }
43
+ let targetWindowId = windowMap.get(sourceWindowId);
44
+ if (!targetWindowId) {
45
+ targetWindowId = await ensureWindow(sourceWindowId);
46
+ windowMap.set(sourceWindowId, targetWindowId);
47
+ }
48
+ try {
49
+ await chrome.tabs.move(tabId, { windowId: targetWindowId, index: -1 });
50
+ restored.push({ tabId, entry, targetWindowId });
51
+ }
52
+ catch {
53
+ skipped.push({ tabId, reason: "move_failed" });
54
+ }
55
+ }
56
+ const groupsByWindow = new Map();
57
+ for (const item of restored) {
58
+ const entry = item.entry;
59
+ const rawGroupId = typeof entry.groupId === "number" ? entry.groupId : null;
60
+ const groupId = rawGroupId != null && rawGroupId !== -1 ? rawGroupId : null;
61
+ const groupTitle = typeof entry.groupTitle === "string" ? entry.groupTitle : null;
62
+ if (!groupId && !groupTitle) {
63
+ continue;
64
+ }
65
+ const groupKey = groupId != null ? `id:${groupId}` : `title:${groupTitle}`;
66
+ const key = `${item.targetWindowId}:${groupKey}`;
67
+ if (!groupsByWindow.has(key)) {
68
+ groupsByWindow.set(key, {
69
+ windowId: item.targetWindowId,
70
+ groupId,
71
+ groupTitle,
72
+ groupColor: typeof entry.groupColor === "string" ? entry.groupColor : null,
73
+ groupCollapsed: typeof entry.groupCollapsed === "boolean" ? entry.groupCollapsed : null,
74
+ tabIds: [],
75
+ });
76
+ }
77
+ groupsByWindow.get(key)?.tabIds.push(item.tabId);
78
+ }
79
+ for (const group of groupsByWindow.values()) {
80
+ if (group.tabIds.length === 0) {
81
+ continue;
82
+ }
83
+ let targetGroupId = null;
84
+ if (group.groupId != null) {
85
+ try {
86
+ targetGroupId = await chrome.tabs.group({ groupId: group.groupId, tabIds: group.tabIds });
87
+ }
88
+ catch {
89
+ targetGroupId = null;
90
+ }
91
+ }
92
+ if (targetGroupId == null) {
93
+ try {
94
+ targetGroupId = await chrome.tabs.group({
95
+ tabIds: group.tabIds,
96
+ createProperties: { windowId: group.windowId },
97
+ });
98
+ }
99
+ catch (error) {
100
+ deps.log("Failed to regroup tabs", error);
101
+ continue;
102
+ }
103
+ }
104
+ const update = {};
105
+ if (typeof group.groupTitle === "string") {
106
+ update.title = group.groupTitle;
107
+ }
108
+ if (typeof group.groupColor === "string" && group.groupColor) {
109
+ update.color = group.groupColor;
110
+ }
111
+ if (typeof group.groupCollapsed === "boolean") {
112
+ update.collapsed = group.groupCollapsed;
113
+ }
114
+ if (Object.keys(update).length > 0) {
115
+ try {
116
+ await chrome.tabGroups.update(targetGroupId, update);
117
+ }
118
+ catch (error) {
119
+ deps.log("Failed to update restored group", error);
120
+ }
121
+ }
122
+ }
123
+ const orderByWindow = new Map();
124
+ for (const item of restored) {
125
+ const index = Number(item.entry.index);
126
+ if (!Number.isFinite(index)) {
127
+ continue;
128
+ }
129
+ if (!orderByWindow.has(item.targetWindowId)) {
130
+ orderByWindow.set(item.targetWindowId, []);
131
+ }
132
+ orderByWindow.get(item.targetWindowId)?.push({ tabId: item.tabId, index });
133
+ }
134
+ for (const [targetWindowId, items] of orderByWindow.entries()) {
135
+ const ordered = [...items].sort((a, b) => a.index - b.index);
136
+ for (const item of ordered) {
137
+ try {
138
+ await chrome.tabs.move(item.tabId, { windowId: targetWindowId, index: item.index });
139
+ }
140
+ catch {
141
+ // ignore ordering failures
142
+ }
143
+ }
144
+ }
145
+ return {
146
+ summary: {
147
+ restoredTabs: restored.length,
148
+ skippedTabs: skipped.length,
149
+ },
150
+ skipped,
151
+ };
152
+ }
153
+ async function undoGroupUpdate(undo) {
154
+ const groupId = Number(undo.groupId);
155
+ if (!Number.isFinite(groupId)) {
156
+ return {
157
+ summary: { restoredGroups: 0, skippedGroups: 1 },
158
+ skipped: [{ groupId: undo.groupId, reason: "missing_group" }],
159
+ };
160
+ }
161
+ const previous = undo.previous || {};
162
+ const update = {};
163
+ if (typeof previous.title === "string") {
164
+ update.title = previous.title;
165
+ }
166
+ if (typeof previous.color === "string" && previous.color) {
167
+ update.color = previous.color;
168
+ }
169
+ if (typeof previous.collapsed === "boolean") {
170
+ update.collapsed = previous.collapsed;
171
+ }
172
+ if (!Object.keys(update).length) {
173
+ return {
174
+ summary: { restoredGroups: 0, skippedGroups: 1 },
175
+ skipped: [{ groupId, reason: "missing_values" }],
176
+ };
177
+ }
178
+ try {
179
+ await chrome.tabGroups.update(groupId, update);
180
+ return {
181
+ summary: { restoredGroups: 1, skippedGroups: 0 },
182
+ skipped: [],
183
+ };
184
+ }
185
+ catch {
186
+ return {
187
+ summary: { restoredGroups: 0, skippedGroups: 1 },
188
+ skipped: [{ groupId, reason: "update_failed" }],
189
+ };
190
+ }
191
+ }
192
+ async function undoGroupUngroup(undo, deps) {
193
+ const tabs = undo.tabs || [];
194
+ return await restoreTabsFromUndo(tabs, deps);
195
+ }
196
+ async function undoGroupAssign(undo, deps) {
197
+ const tabs = undo.tabs || [];
198
+ return await restoreTabsFromUndo(tabs, deps);
199
+ }
200
+ async function undoMoveTab(undo, deps) {
201
+ const from = undo.from || {};
202
+ const entry = {
203
+ tabId: undo.tabId,
204
+ windowId: from.windowId,
205
+ index: from.index,
206
+ groupId: from.groupId,
207
+ groupTitle: from.groupTitle,
208
+ groupColor: from.groupColor,
209
+ groupCollapsed: from.groupCollapsed,
210
+ };
211
+ return await restoreTabsFromUndo([entry], deps);
212
+ }
213
+ async function undoMoveGroup(undo, deps) {
214
+ const tabs = undo.tabs || [];
215
+ return await restoreTabsFromUndo(tabs, deps);
216
+ }
217
+ async function undoMergeWindow(undo, deps) {
218
+ const tabs = undo.tabs || [];
219
+ return await restoreTabsFromUndo(tabs, deps);
220
+ }
221
+ async function undoArchive(undo, deps) {
222
+ const tabs = undo.tabs || [];
223
+ const restored = [];
224
+ const skipped = [];
225
+ const windowMap = new Map();
226
+ for (const entry of tabs) {
227
+ if (!entry.tabId) {
228
+ skipped.push({ tabId: entry.tabId, reason: "missing_tab" });
229
+ continue;
230
+ }
231
+ let targetWindowId = windowMap.get(entry.from.windowId);
232
+ if (!targetWindowId) {
233
+ targetWindowId = await ensureWindow(entry.from.windowId);
234
+ windowMap.set(entry.from.windowId, targetWindowId);
235
+ }
236
+ try {
237
+ await chrome.tabs.move(entry.tabId, { windowId: targetWindowId, index: -1 });
238
+ restored.push({ tabId: entry.tabId, targetWindowId });
239
+ }
240
+ catch {
241
+ skipped.push({ tabId: entry.tabId, reason: "move_failed" });
242
+ }
243
+ }
244
+ const restoredSet = new Set(restored.map((item) => item.tabId));
245
+ const groupsByWindow = new Map();
246
+ for (const entry of tabs) {
247
+ if (!restoredSet.has(entry.tabId)) {
248
+ continue;
249
+ }
250
+ const targetWindowId = windowMap.get(entry.from.windowId) || entry.from.windowId;
251
+ const hasGroupId = entry.from.groupId != null && entry.from.groupId !== -1;
252
+ const hasGroupTitle = entry.from.groupTitle != null;
253
+ if (!hasGroupId && !hasGroupTitle) {
254
+ continue;
255
+ }
256
+ const groupKey = hasGroupId ? entry.from.groupId : entry.from.groupTitle;
257
+ const key = `${targetWindowId}:${groupKey}`;
258
+ if (!groupsByWindow.has(key)) {
259
+ groupsByWindow.set(key, []);
260
+ }
261
+ groupsByWindow.get(key)?.push(entry);
262
+ }
263
+ for (const [key, groupTabs] of groupsByWindow.entries()) {
264
+ const [windowIdPart] = key.split(":");
265
+ const targetWindowId = Number(windowIdPart);
266
+ const tabIds = groupTabs.map((entry) => entry.tabId).filter(Boolean);
267
+ if (!tabIds.length) {
268
+ continue;
269
+ }
270
+ try {
271
+ const groupId = await chrome.tabs.group({ tabIds, createProperties: { windowId: targetWindowId } });
272
+ await chrome.tabGroups.update(groupId, {
273
+ title: groupTabs[0].from.groupTitle || "",
274
+ color: groupTabs[0].from.groupColor || "grey",
275
+ collapsed: groupTabs[0].from.groupCollapsed || false,
276
+ });
277
+ }
278
+ catch (error) {
279
+ deps.log("Failed to recreate group", error);
280
+ }
281
+ }
282
+ for (const [originalWindowId, targetWindowId] of windowMap.entries()) {
283
+ const windowTabs = tabs
284
+ .filter((entry) => entry.from.windowId === originalWindowId && restoredSet.has(entry.tabId))
285
+ .sort((a, b) => a.from.index - b.from.index);
286
+ for (const entry of windowTabs) {
287
+ if (!entry.tabId) {
288
+ continue;
289
+ }
290
+ try {
291
+ await chrome.tabs.move(entry.tabId, { windowId: targetWindowId, index: entry.from.index });
292
+ }
293
+ catch {
294
+ // ignore ordering failures
295
+ }
296
+ }
297
+ const activeTab = windowTabs.find((entry) => entry.active);
298
+ if (activeTab && activeTab.tabId) {
299
+ try {
300
+ await chrome.tabs.update(activeTab.tabId, { active: true });
301
+ }
302
+ catch {
303
+ // ignore
304
+ }
305
+ }
306
+ }
307
+ return {
308
+ summary: {
309
+ restoredTabs: restored.length,
310
+ skippedTabs: skipped.length,
311
+ },
312
+ skipped,
313
+ };
314
+ }
315
+ async function undoClose(undo, deps) {
316
+ const tabs = undo.tabs || [];
317
+ const restored = [];
318
+ const skipped = [];
319
+ const windowMap = new Map();
320
+ for (const entry of tabs) {
321
+ if (!entry.url) {
322
+ skipped.push({ url: entry.url, reason: "missing_url" });
323
+ continue;
324
+ }
325
+ let targetWindowId = windowMap.get(entry.from.windowId);
326
+ if (!targetWindowId) {
327
+ targetWindowId = await ensureWindow(entry.from.windowId);
328
+ windowMap.set(entry.from.windowId, targetWindowId);
329
+ }
330
+ try {
331
+ const created = await chrome.tabs.create({
332
+ windowId: targetWindowId,
333
+ url: entry.url,
334
+ active: false,
335
+ pinned: entry.pinned,
336
+ });
337
+ restored.push({ tabId: created.id, entry });
338
+ }
339
+ catch {
340
+ skipped.push({ url: entry.url, reason: "create_failed" });
341
+ }
342
+ }
343
+ const groupsByWindow = new Map();
344
+ for (const item of restored) {
345
+ const entry = item.entry;
346
+ const targetWindowId = windowMap.get(entry.from.windowId) || entry.from.windowId;
347
+ const hasGroupId = entry.from.groupId != null && entry.from.groupId !== -1;
348
+ const hasGroupTitle = entry.from.groupTitle != null;
349
+ if (!hasGroupId && !hasGroupTitle) {
350
+ continue;
351
+ }
352
+ const groupKey = hasGroupId ? entry.from.groupId : entry.from.groupTitle;
353
+ const key = `${targetWindowId}:${groupKey}`;
354
+ if (!groupsByWindow.has(key)) {
355
+ groupsByWindow.set(key, []);
356
+ }
357
+ groupsByWindow.get(key)?.push({ tabId: item.tabId, entry });
358
+ }
359
+ for (const [key, groupTabs] of groupsByWindow.entries()) {
360
+ const [windowIdPart] = key.split(":");
361
+ const targetWindowId = Number(windowIdPart);
362
+ const tabIds = groupTabs.map((item) => item.tabId).filter(Boolean);
363
+ if (!tabIds.length) {
364
+ continue;
365
+ }
366
+ try {
367
+ const groupId = await chrome.tabs.group({ tabIds, createProperties: { windowId: targetWindowId } });
368
+ await chrome.tabGroups.update(groupId, {
369
+ title: groupTabs[0].entry.from.groupTitle || "",
370
+ color: groupTabs[0].entry.from.groupColor || "grey",
371
+ collapsed: groupTabs[0].entry.from.groupCollapsed || false,
372
+ });
373
+ }
374
+ catch (error) {
375
+ deps.log("Failed to recreate group", error);
376
+ }
377
+ }
378
+ for (const [originalWindowId, targetWindowId] of windowMap.entries()) {
379
+ const windowTabs = restored
380
+ .map((item) => ({ tabId: item.tabId, entry: item.entry }))
381
+ .filter((item) => item.entry.from.windowId === originalWindowId)
382
+ .sort((a, b) => a.entry.from.index - b.entry.from.index);
383
+ for (const item of windowTabs) {
384
+ try {
385
+ await chrome.tabs.move(item.tabId, { windowId: targetWindowId, index: item.entry.from.index });
386
+ }
387
+ catch {
388
+ // ignore ordering failures
389
+ }
390
+ }
391
+ const activeTab = windowTabs.find((item) => item.entry.active);
392
+ if (activeTab) {
393
+ try {
394
+ await chrome.tabs.update(activeTab.tabId, { active: true });
395
+ }
396
+ catch {
397
+ // ignore
398
+ }
399
+ }
400
+ }
401
+ return {
402
+ summary: {
403
+ restoredTabs: restored.length,
404
+ skippedTabs: skipped.length,
405
+ },
406
+ skipped,
407
+ };
408
+ }
409
+ async function undoTransaction(params, deps) {
410
+ if (!params.record || !params.record.undo) {
411
+ throw new Error("Undo record missing");
412
+ }
413
+ const undo = params.record.undo;
414
+ if (undo.action === "archive") {
415
+ return await undoArchive(undo, deps);
416
+ }
417
+ if (undo.action === "close") {
418
+ return await undoClose(undo, deps);
419
+ }
420
+ if (undo.action === "group-update") {
421
+ return await undoGroupUpdate(undo);
422
+ }
423
+ if (undo.action === "group-ungroup") {
424
+ return await undoGroupUngroup(undo, deps);
425
+ }
426
+ if (undo.action === "group-assign") {
427
+ return await undoGroupAssign(undo, deps);
428
+ }
429
+ if (undo.action === "move-tab") {
430
+ return await undoMoveTab(undo, deps);
431
+ }
432
+ if (undo.action === "move-group") {
433
+ return await undoMoveGroup(undo, deps);
434
+ }
435
+ if (undo.action === "merge-window") {
436
+ return await undoMergeWindow(undo, deps);
437
+ }
438
+ throw new Error(`Unknown undo action: ${undo.action}`);
439
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Tab Control",
4
- "version": "0.1.4",
4
+ "version": "0.2.1",
5
5
  "description": "Archive and manage browser tabs with CLI support",
6
6
  "permissions": [
7
7
  "tabs",
@@ -19,5 +19,5 @@
19
19
  "background": {
20
20
  "service_worker": "background.js"
21
21
  },
22
- "version_name": "0.1.4"
22
+ "version_name": "0.2.1"
23
23
  }
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const net_1 = __importDefault(require("net"));
9
+ const crypto_1 = __importDefault(require("crypto"));
10
+ const config_1 = require("../shared/config");
11
+ const handlers_1 = require("./lib/handlers");
12
+ let config;
13
+ try {
14
+ config = (0, config_1.resolveConfig)();
15
+ }
16
+ catch (err) {
17
+ process.stderr.write(`[tabctl-host] Fatal: ${err.message}\n`);
18
+ process.exit(1);
19
+ }
20
+ const SOCKET_DIR = config.dataDir;
21
+ const SOCKET_PATH = config.socketPath;
22
+ const pending = new Map();
23
+ const analyses = new Map();
24
+ function log(...args) {
25
+ process.stderr.write(`[tabctl-host] ${args.join(" ")}\n`);
26
+ }
27
+ function ensureDir() {
28
+ fs_1.default.mkdirSync(SOCKET_DIR, { recursive: true, mode: 0o700 });
29
+ }
30
+ function createId(prefix) {
31
+ return `${prefix}-${Date.now()}-${crypto_1.default.randomBytes(4).toString("hex")}`;
32
+ }
33
+ function sendNative(message) {
34
+ const json = JSON.stringify(message);
35
+ const length = Buffer.byteLength(json);
36
+ const buffer = Buffer.alloc(4 + length);
37
+ buffer.writeUInt32LE(length, 0);
38
+ buffer.write(json, 4);
39
+ process.stdout.write(buffer);
40
+ }
41
+ const deps = {
42
+ pending,
43
+ analyses,
44
+ undoLog: config.undoLog,
45
+ createId,
46
+ sendNative,
47
+ log,
48
+ };
49
+ function handleNativeMessage(payload) {
50
+ (0, handlers_1.handleNativeMessage)(deps, payload);
51
+ }
52
+ function handleCliRequest(socket, request) {
53
+ (0, handlers_1.handleCliRequest)(deps, socket, request);
54
+ }
55
+ let nativeBuffer = Buffer.alloc(0);
56
+ process.stdin.on("data", (chunk) => {
57
+ nativeBuffer = Buffer.concat([nativeBuffer, chunk]);
58
+ while (nativeBuffer.length >= 4) {
59
+ const length = nativeBuffer.readUInt32LE(0);
60
+ if (nativeBuffer.length < 4 + length) {
61
+ return;
62
+ }
63
+ const payload = nativeBuffer.slice(4, 4 + length).toString("utf8");
64
+ nativeBuffer = nativeBuffer.slice(4 + length);
65
+ handleNativeMessage(payload);
66
+ }
67
+ });
68
+ process.stdin.on("end", () => {
69
+ log("Extension disconnected, exiting");
70
+ cleanupAndExit(0);
71
+ });
72
+ function startSocketServer() {
73
+ ensureDir();
74
+ if (fs_1.default.existsSync(SOCKET_PATH)) {
75
+ fs_1.default.unlinkSync(SOCKET_PATH);
76
+ }
77
+ const server = net_1.default.createServer((socket) => {
78
+ socket.setEncoding("utf8");
79
+ let buffer = "";
80
+ socket.on("data", (data) => {
81
+ buffer += data;
82
+ let index;
83
+ while ((index = buffer.indexOf("\n")) >= 0) {
84
+ const line = buffer.slice(0, index).trim();
85
+ buffer = buffer.slice(index + 1);
86
+ if (!line) {
87
+ continue;
88
+ }
89
+ let request;
90
+ try {
91
+ request = JSON.parse(line);
92
+ }
93
+ catch {
94
+ (0, handlers_1.respond)(socket, { ok: false, error: { message: "Invalid JSON" } });
95
+ continue;
96
+ }
97
+ handleCliRequest(socket, request);
98
+ }
99
+ });
100
+ socket.on("error", (error) => {
101
+ log("CLI socket error", error.message);
102
+ });
103
+ });
104
+ server.listen(SOCKET_PATH, () => {
105
+ fs_1.default.chmodSync(SOCKET_PATH, 0o600);
106
+ log(`Listening on ${SOCKET_PATH}`);
107
+ });
108
+ return server;
109
+ }
110
+ function cleanupAndExit(code) {
111
+ try {
112
+ if (fs_1.default.existsSync(SOCKET_PATH)) {
113
+ fs_1.default.unlinkSync(SOCKET_PATH);
114
+ }
115
+ }
116
+ catch {
117
+ // ignore
118
+ }
119
+ process.exit(code);
120
+ }
121
+ const server = startSocketServer();
122
+ process.on("SIGINT", () => cleanupAndExit(0));
123
+ process.on("SIGTERM", () => cleanupAndExit(0));
124
+ server.on("close", () => cleanupAndExit(0));