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
@@ -1,32 +1,16 @@
1
- #!/usr/bin/env node
2
1
  "use strict";
3
- var __importDefault = (this && this.__importDefault) || function (mod) {
4
- return (mod && mod.__esModule) ? mod : { "default": mod };
5
- };
6
2
  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 version_1 = require("../shared/version");
11
- const config_1 = require("../shared/config");
12
- const undo_1 = require("./lib/undo");
13
- let config;
14
- try {
15
- config = (0, config_1.resolveConfig)();
16
- }
17
- catch (err) {
18
- process.stderr.write(`[tabctl-host] Fatal: ${err.message}\n`);
19
- process.exit(1);
20
- }
21
- const SOCKET_DIR = config.dataDir;
22
- const SOCKET_PATH = config.socketPath;
23
- const UNDO_LOG = config.undoLog;
3
+ exports.respond = respond;
4
+ exports.refreshTimeout = refreshTimeout;
5
+ exports.forwardToExtension = forwardToExtension;
6
+ exports.handleNativeMessage = handleNativeMessage;
7
+ exports.handleCliRequest = handleCliRequest;
8
+ const version_1 = require("../../shared/version");
9
+ const undo_1 = require("./undo");
24
10
  const REQUEST_TIMEOUT_MS = 30000;
25
11
  const MAX_RESPONSE_BYTES = 20 * 1024 * 1024;
26
12
  const HISTORY_LIMIT_DEFAULT = 20;
27
13
  const RETENTION_DAYS = 30;
28
- const pending = new Map();
29
- const analyses = new Map();
30
14
  const UNDO_ACTIONS = new Set([
31
15
  "archive",
32
16
  "close",
@@ -38,60 +22,86 @@ const UNDO_ACTIONS = new Set([
38
22
  "merge-window",
39
23
  ]);
40
24
  const LOCAL_ACTIONS = new Set(["history", "undo", "version"]);
41
- function log(...args) {
42
- process.stderr.write(`[tabctl-host] ${args.join(" ")}\n`);
43
- }
44
- function ensureDir() {
45
- fs_1.default.mkdirSync(SOCKET_DIR, { recursive: true, mode: 0o700 });
46
- }
47
- function createId(prefix) {
48
- return `${prefix}-${Date.now()}-${crypto_1.default.randomBytes(4).toString("hex")}`;
25
+ function respond(socket, payload) {
26
+ const serialized = JSON.stringify(payload);
27
+ if (Buffer.byteLength(serialized, "utf8") > MAX_RESPONSE_BYTES) {
28
+ socket.write(`${JSON.stringify({
29
+ ok: false,
30
+ action: payload.action,
31
+ requestId: payload.requestId,
32
+ component: "host",
33
+ version: version_1.VERSION,
34
+ error: { message: "Response too large", hint: "Reduce scope or use --out to write files." },
35
+ })}\n`);
36
+ return;
37
+ }
38
+ socket.write(`${serialized}\n`);
49
39
  }
50
- function sendNative(message) {
51
- const json = JSON.stringify(message);
52
- const length = Buffer.byteLength(json);
53
- const buffer = Buffer.alloc(4 + length);
54
- buffer.writeUInt32LE(length, 0);
55
- buffer.write(json, 4);
56
- process.stdout.write(buffer);
40
+ function refreshTimeout(deps, pendingRequest, requestId) {
41
+ clearTimeout(pendingRequest.timeout);
42
+ pendingRequest.timeout = setTimeout(() => {
43
+ deps.pending.delete(requestId);
44
+ respond(pendingRequest.socket, {
45
+ ok: false,
46
+ action: pendingRequest.action,
47
+ requestId,
48
+ component: "host",
49
+ version: version_1.VERSION,
50
+ error: { message: "Request timed out" },
51
+ });
52
+ }, REQUEST_TIMEOUT_MS);
57
53
  }
58
- let nativeBuffer = Buffer.alloc(0);
59
- process.stdin.on("data", (chunk) => {
60
- nativeBuffer = Buffer.concat([nativeBuffer, chunk]);
61
- while (nativeBuffer.length >= 4) {
62
- const length = nativeBuffer.readUInt32LE(0);
63
- if (nativeBuffer.length < 4 + length) {
64
- return;
65
- }
66
- const payload = nativeBuffer.slice(4, 4 + length).toString("utf8");
67
- nativeBuffer = nativeBuffer.slice(4 + length);
68
- handleNativeMessage(payload);
54
+ function forwardToExtension(deps, socket, request, overrides = {}) {
55
+ const requestId = request.id || deps.createId("req");
56
+ const txid = overrides.txid || null;
57
+ const params = { ...(request.params || {}) };
58
+ if (txid) {
59
+ params.txid = txid;
69
60
  }
70
- });
71
- process.stdin.on("end", () => {
72
- log("Extension disconnected, exiting");
73
- cleanupAndExit(0);
74
- });
75
- function handleNativeMessage(payload) {
61
+ if (!LOCAL_ACTIONS.has(request.action)) {
62
+ params.client = {
63
+ component: "host",
64
+ version: version_1.VERSION,
65
+ };
66
+ }
67
+ deps.pending.set(requestId, {
68
+ socket,
69
+ action: request.action,
70
+ txid,
71
+ timeout: setTimeout(() => {
72
+ deps.pending.delete(requestId);
73
+ respond(socket, {
74
+ ok: false,
75
+ action: request.action,
76
+ requestId,
77
+ component: "host",
78
+ version: version_1.VERSION,
79
+ error: { message: "Request timed out" },
80
+ });
81
+ }, REQUEST_TIMEOUT_MS),
82
+ });
83
+ deps.sendNative({ id: requestId, action: request.action, params });
84
+ }
85
+ function handleNativeMessage(deps, payload) {
76
86
  let message;
77
87
  try {
78
88
  message = JSON.parse(payload);
79
89
  }
80
90
  catch (error) {
81
91
  const err = error;
82
- log("Failed to parse native message", err.message);
92
+ deps.log("Failed to parse native message", err.message);
83
93
  return;
84
94
  }
85
95
  const messageId = message.id;
86
96
  if (!messageId) {
87
97
  return;
88
98
  }
89
- const pendingRequest = pending.get(messageId);
99
+ const pendingRequest = deps.pending.get(messageId);
90
100
  if (!pendingRequest) {
91
101
  return;
92
102
  }
93
103
  if (message.progress) {
94
- refreshTimeout(pendingRequest, messageId);
104
+ refreshTimeout(deps, pendingRequest, messageId);
95
105
  respond(pendingRequest.socket, {
96
106
  ok: true,
97
107
  action: pendingRequest.action,
@@ -107,10 +117,10 @@ function handleNativeMessage(payload) {
107
117
  const extensionVersion = typeof messageData.version === "string" ? messageData.version : null;
108
118
  const extensionComponent = typeof messageData.component === "string" ? messageData.component : null;
109
119
  if (extensionVersion && extensionVersion !== version_1.VERSION) {
110
- log(`Version mismatch: host ${version_1.VERSION}, extension ${extensionVersion}`);
120
+ deps.log(`Version mismatch: host ${version_1.VERSION}, extension ${extensionVersion}`);
111
121
  }
112
122
  clearTimeout(pendingRequest.timeout);
113
- pending.delete(messageId);
123
+ deps.pending.delete(messageId);
114
124
  if (!message.ok) {
115
125
  respond(pendingRequest.socket, {
116
126
  ok: false,
@@ -123,8 +133,8 @@ function handleNativeMessage(payload) {
123
133
  return;
124
134
  }
125
135
  if (pendingRequest.action === "analyze") {
126
- const analysisId = createId("analysis");
127
- analyses.set(analysisId, {
136
+ const analysisId = deps.createId("analysis");
137
+ deps.analyses.set(analysisId, {
128
138
  createdAt: Date.now(),
129
139
  data: messageData,
130
140
  });
@@ -155,7 +165,7 @@ function handleNativeMessage(payload) {
155
165
  undo: messageData.undo || null,
156
166
  };
157
167
  if (record.undo) {
158
- (0, undo_1.appendUndoRecord)(UNDO_LOG, record);
168
+ (0, undo_1.appendUndoRecord)(deps.undoLog, record);
159
169
  }
160
170
  respond(pendingRequest.socket, {
161
171
  ok: true,
@@ -191,67 +201,7 @@ function handleNativeMessage(payload) {
191
201
  },
192
202
  });
193
203
  }
194
- function respond(socket, payload) {
195
- const serialized = JSON.stringify(payload);
196
- if (Buffer.byteLength(serialized, "utf8") > MAX_RESPONSE_BYTES) {
197
- socket.write(`${JSON.stringify({
198
- ok: false,
199
- action: payload.action,
200
- requestId: payload.requestId,
201
- component: "host",
202
- version: version_1.VERSION,
203
- error: { message: "Response too large", hint: "Reduce scope or use --out to write files." },
204
- })}\n`);
205
- return;
206
- }
207
- socket.write(`${serialized}\n`);
208
- }
209
- function refreshTimeout(pendingRequest, requestId) {
210
- clearTimeout(pendingRequest.timeout);
211
- pendingRequest.timeout = setTimeout(() => {
212
- pending.delete(requestId);
213
- respond(pendingRequest.socket, {
214
- ok: false,
215
- action: pendingRequest.action,
216
- requestId,
217
- component: "host",
218
- version: version_1.VERSION,
219
- error: { message: "Request timed out" },
220
- });
221
- }, REQUEST_TIMEOUT_MS);
222
- }
223
- function forwardToExtension(socket, request, overrides = {}) {
224
- const requestId = request.id || createId("req");
225
- const txid = overrides.txid || null;
226
- const params = { ...(request.params || {}) };
227
- if (txid) {
228
- params.txid = txid;
229
- }
230
- if (!LOCAL_ACTIONS.has(request.action)) {
231
- params.client = {
232
- component: "host",
233
- version: version_1.VERSION,
234
- };
235
- }
236
- pending.set(requestId, {
237
- socket,
238
- action: request.action,
239
- txid,
240
- timeout: setTimeout(() => {
241
- pending.delete(requestId);
242
- respond(socket, {
243
- ok: false,
244
- action: request.action,
245
- requestId,
246
- component: "host",
247
- version: version_1.VERSION,
248
- error: { message: "Request timed out" },
249
- });
250
- }, REQUEST_TIMEOUT_MS),
251
- });
252
- sendNative({ id: requestId, action: request.action, params });
253
- }
254
- function handleCliRequest(socket, request) {
204
+ function handleCliRequest(deps, socket, request) {
255
205
  if (!request || typeof request !== "object") {
256
206
  respond(socket, { ok: false, error: { message: "Invalid request" } });
257
207
  return;
@@ -265,7 +215,7 @@ function handleCliRequest(socket, request) {
265
215
  const limit = Number.isFinite(request.params?.limit)
266
216
  ? Number(request.params?.limit)
267
217
  : HISTORY_LIMIT_DEFAULT;
268
- const records = (0, undo_1.readUndoRecords)(UNDO_LOG);
218
+ const records = (0, undo_1.readUndoRecords)(deps.undoLog);
269
219
  const filtered = (0, undo_1.filterByRetention)(records, RETENTION_DAYS);
270
220
  respond(socket, {
271
221
  ok: true,
@@ -311,13 +261,13 @@ function handleCliRequest(socket, request) {
311
261
  return;
312
262
  }
313
263
  const record = txid
314
- ? (0, undo_1.findUndoRecord)(UNDO_LOG, txid, RETENTION_DAYS)
315
- : (0, undo_1.findLatestUndoRecord)(UNDO_LOG, RETENTION_DAYS);
264
+ ? (0, undo_1.findUndoRecord)(deps.undoLog, txid, RETENTION_DAYS)
265
+ : (0, undo_1.findLatestUndoRecord)(deps.undoLog, RETENTION_DAYS);
316
266
  if (!record) {
317
267
  respond(socket, { ok: false, action, component: "host", version: version_1.VERSION, error: { message: "Undo record not found" } });
318
268
  return;
319
269
  }
320
- forwardToExtension(socket, {
270
+ forwardToExtension(deps, socket, {
321
271
  id: request.id,
322
272
  action: "undo",
323
273
  params: { record },
@@ -326,7 +276,7 @@ function handleCliRequest(socket, request) {
326
276
  }
327
277
  if (action === "close" && request.params?.mode === "apply") {
328
278
  const analysisId = request.params.analysisId;
329
- const analysis = analysisId ? analyses.get(analysisId) : undefined;
279
+ const analysis = analysisId ? deps.analyses.get(analysisId) : undefined;
330
280
  if (!analysis) {
331
281
  respond(socket, { ok: false, action, component: "host", version: version_1.VERSION, error: { message: "Unknown analysisId" } });
332
282
  return;
@@ -354,8 +304,8 @@ function handleCliRequest(socket, request) {
354
304
  });
355
305
  return;
356
306
  }
357
- const txid = createId("tx");
358
- forwardToExtension(socket, {
307
+ const txid = deps.createId("tx");
308
+ forwardToExtension(deps, socket, {
359
309
  id: request.id,
360
310
  action: "close",
361
311
  params: {
@@ -367,62 +317,9 @@ function handleCliRequest(socket, request) {
367
317
  return;
368
318
  }
369
319
  if (UNDO_ACTIONS.has(action)) {
370
- const txid = createId("tx");
371
- forwardToExtension(socket, request, { txid });
320
+ const txid = deps.createId("tx");
321
+ forwardToExtension(deps, socket, request, { txid });
372
322
  return;
373
323
  }
374
- forwardToExtension(socket, request);
375
- }
376
- function startSocketServer() {
377
- ensureDir();
378
- if (fs_1.default.existsSync(SOCKET_PATH)) {
379
- fs_1.default.unlinkSync(SOCKET_PATH);
380
- }
381
- const server = net_1.default.createServer((socket) => {
382
- socket.setEncoding("utf8");
383
- let buffer = "";
384
- socket.on("data", (data) => {
385
- buffer += data;
386
- let index;
387
- while ((index = buffer.indexOf("\n")) >= 0) {
388
- const line = buffer.slice(0, index).trim();
389
- buffer = buffer.slice(index + 1);
390
- if (!line) {
391
- continue;
392
- }
393
- let request;
394
- try {
395
- request = JSON.parse(line);
396
- }
397
- catch {
398
- respond(socket, { ok: false, error: { message: "Invalid JSON" } });
399
- continue;
400
- }
401
- handleCliRequest(socket, request);
402
- }
403
- });
404
- socket.on("error", (error) => {
405
- log("CLI socket error", error.message);
406
- });
407
- });
408
- server.listen(SOCKET_PATH, () => {
409
- fs_1.default.chmodSync(SOCKET_PATH, 0o600);
410
- log(`Listening on ${SOCKET_PATH}`);
411
- });
412
- return server;
413
- }
414
- function cleanupAndExit(code) {
415
- try {
416
- if (fs_1.default.existsSync(SOCKET_PATH)) {
417
- fs_1.default.unlinkSync(SOCKET_PATH);
418
- }
419
- }
420
- catch {
421
- // ignore
422
- }
423
- process.exit(code);
324
+ forwardToExtension(deps, socket, request);
424
325
  }
425
- const server = startSocketServer();
426
- process.on("SIGINT", () => cleanupAndExit(0));
427
- process.on("SIGTERM", () => cleanupAndExit(0));
428
- server.on("close", () => cleanupAndExit(0));
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DIRTY = exports.GIT_SHA = exports.VERSION = exports.BASE_VERSION = void 0;
4
- exports.BASE_VERSION = "0.1.4";
5
- exports.VERSION = "0.1.4";
4
+ exports.BASE_VERSION = "0.2.1";
5
+ exports.VERSION = "0.2.1";
6
6
  exports.GIT_SHA = null;
7
7
  exports.DIRTY = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tabctl",
3
- "version": "0.1.4",
3
+ "version": "0.2.1",
4
4
  "description": "CLI tool to manage and analyze browser tabs",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -18,26 +18,28 @@
18
18
  "node": ">=20"
19
19
  },
20
20
  "bin": {
21
- "tabctl": "cli/tabctl.js"
21
+ "tabctl": "dist/cli/tabctl.js"
22
22
  },
23
23
  "files": [
24
- "cli",
25
- "host",
26
- "shared",
27
- "extension"
24
+ "dist/cli",
25
+ "dist/host",
26
+ "dist/shared",
27
+ "dist/extension"
28
28
  ],
29
29
  "scripts": {
30
- "build": "node scripts/gen-version.js && tsc -p tsconfig.json && node scripts/sync-build.js",
30
+ "build": "node scripts/gen-version.js && tsc -p tsconfig.json && node scripts/copy-artifacts.js && node scripts/bundle-extension.js",
31
31
  "bump:major": "node scripts/bump-version.js major",
32
32
  "bump:minor": "node scripts/bump-version.js minor",
33
33
  "bump:patch": "node scripts/bump-version.js patch",
34
- "test": "npm run build && node --test tests/unit/*.js",
35
- "test:unit": "npm run build && node --test tests/unit/*.js",
36
- "test:integration": "node build/scripts/integration-test.js"
34
+ "test": "npm run build && node --test --test-timeout=5000 dist/tests/unit/*.js",
35
+ "test:unit": "npm run build && node --test --test-timeout=5000 dist/tests/unit/*.js",
36
+ "test:integration": "node dist/scripts/integration-test.js",
37
+ "clean": "rm -rf dist"
37
38
  },
38
39
  "devDependencies": {
39
40
  "@types/chrome": "^0.0.277",
40
41
  "@types/node": "^24",
42
+ "esbuild": "^0.27.3",
41
43
  "typescript": "^5.4.5"
42
44
  }
43
45
  }