tabctl 0.5.2 → 0.6.0-alpha.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.
- package/README.md +138 -20
- package/dist/extension/background.js +2 -0
- package/dist/extension/manifest.json +2 -2
- package/package.json +13 -5
- package/dist/cli/lib/args.js +0 -141
- package/dist/cli/lib/client.js +0 -83
- package/dist/cli/lib/commands/doctor.js +0 -134
- package/dist/cli/lib/commands/index.js +0 -51
- package/dist/cli/lib/commands/list.js +0 -159
- package/dist/cli/lib/commands/meta.js +0 -229
- package/dist/cli/lib/commands/params-groups.js +0 -48
- package/dist/cli/lib/commands/params-move.js +0 -44
- package/dist/cli/lib/commands/params.js +0 -314
- package/dist/cli/lib/commands/profile.js +0 -91
- package/dist/cli/lib/commands/setup.js +0 -283
- package/dist/cli/lib/constants.js +0 -30
- package/dist/cli/lib/help.js +0 -205
- package/dist/cli/lib/options-commands.js +0 -274
- package/dist/cli/lib/options-groups.js +0 -41
- package/dist/cli/lib/options.js +0 -125
- package/dist/cli/lib/output.js +0 -147
- package/dist/cli/lib/pagination.js +0 -55
- package/dist/cli/lib/policy-filter.js +0 -202
- package/dist/cli/lib/policy.js +0 -91
- package/dist/cli/lib/report.js +0 -61
- package/dist/cli/lib/response.js +0 -235
- package/dist/cli/lib/scope.js +0 -250
- package/dist/cli/lib/snapshot.js +0 -216
- package/dist/cli/lib/types.js +0 -2
- package/dist/cli/tabctl.js +0 -475
- package/dist/host/host.bundle.js +0 -670
- package/dist/host/host.js +0 -143
- package/dist/host/host.sh +0 -5
- package/dist/host/launcher/go.mod +0 -3
- package/dist/host/launcher/main.go +0 -109
- package/dist/host/lib/handlers.js +0 -327
- package/dist/host/lib/undo.js +0 -60
- package/dist/shared/config.js +0 -134
- package/dist/shared/extension-sync.js +0 -170
- package/dist/shared/profiles.js +0 -78
- package/dist/shared/version.js +0 -8
- package/dist/shared/wrapper-health.js +0 -132
package/dist/host/host.js
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
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 node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
-
const node_net_1 = __importDefault(require("node:net"));
|
|
9
|
-
const node_crypto_1 = __importDefault(require("node: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
|
-
node_fs_1.default.mkdirSync(SOCKET_DIR, { recursive: true, mode: 0o700 });
|
|
29
|
-
}
|
|
30
|
-
function createId(prefix) {
|
|
31
|
-
return `${prefix}-${Date.now()}-${node_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
|
-
// Named pipes on Windows don't use filesystem paths; skip cleanup
|
|
75
|
-
if (process.platform !== "win32" && node_fs_1.default.existsSync(SOCKET_PATH)) {
|
|
76
|
-
node_fs_1.default.unlinkSync(SOCKET_PATH);
|
|
77
|
-
}
|
|
78
|
-
const server = node_net_1.default.createServer((socket) => {
|
|
79
|
-
socket.setEncoding("utf8");
|
|
80
|
-
let buffer = "";
|
|
81
|
-
socket.on("data", (data) => {
|
|
82
|
-
buffer += data;
|
|
83
|
-
let index;
|
|
84
|
-
while ((index = buffer.indexOf("\n")) >= 0) {
|
|
85
|
-
const line = buffer.slice(0, index).trim();
|
|
86
|
-
buffer = buffer.slice(index + 1);
|
|
87
|
-
if (!line) {
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
let request;
|
|
91
|
-
try {
|
|
92
|
-
request = JSON.parse(line);
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
(0, handlers_1.respond)(socket, { ok: false, error: { message: "Invalid JSON" } });
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
handleCliRequest(socket, request);
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
socket.on("error", (error) => {
|
|
102
|
-
log("CLI socket error", error.message);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
let retries = 0;
|
|
106
|
-
const maxRetries = process.platform === "win32" ? 5 : 0;
|
|
107
|
-
server.on("error", (err) => {
|
|
108
|
-
if (err.code === "EADDRINUSE" && retries < maxRetries) {
|
|
109
|
-
retries++;
|
|
110
|
-
log(`Socket in use, retrying (${retries}/${maxRetries})…`);
|
|
111
|
-
setTimeout(() => server.listen(SOCKET_PATH), 500);
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
throw err;
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
server.listen(SOCKET_PATH, () => {
|
|
118
|
-
if (process.platform !== "win32") {
|
|
119
|
-
try {
|
|
120
|
-
node_fs_1.default.chmodSync(SOCKET_PATH, 0o600);
|
|
121
|
-
}
|
|
122
|
-
catch { /* ignore on platforms without chmod */ }
|
|
123
|
-
}
|
|
124
|
-
log(`Listening on ${SOCKET_PATH}`);
|
|
125
|
-
});
|
|
126
|
-
return server;
|
|
127
|
-
}
|
|
128
|
-
function cleanupAndExit(code) {
|
|
129
|
-
try {
|
|
130
|
-
// Named pipes on Windows don't need filesystem cleanup
|
|
131
|
-
if (process.platform !== "win32" && node_fs_1.default.existsSync(SOCKET_PATH)) {
|
|
132
|
-
node_fs_1.default.unlinkSync(SOCKET_PATH);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
// ignore
|
|
137
|
-
}
|
|
138
|
-
process.exit(code);
|
|
139
|
-
}
|
|
140
|
-
const server = startSocketServer();
|
|
141
|
-
process.on("SIGINT", () => cleanupAndExit(0));
|
|
142
|
-
process.on("SIGTERM", () => cleanupAndExit(0));
|
|
143
|
-
server.on("close", () => cleanupAndExit(0));
|
package/dist/host/host.sh
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
// Tiny native messaging host launcher for Windows.
|
|
2
|
-
//
|
|
3
|
-
// Chrome launches native messaging hosts as sub-processes. On Windows, if the
|
|
4
|
-
// host is a .cmd/.bat file, Chrome invokes it through cmd.exe which opens
|
|
5
|
-
// stdin/stdout in text mode — corrupting the binary 4-byte length-prefixed
|
|
6
|
-
// native messaging protocol.
|
|
7
|
-
//
|
|
8
|
-
// This launcher reads a config file (host-launcher.cfg) next to the exe,
|
|
9
|
-
// sets environment variables, and proxies stdin/stdout between Chrome and
|
|
10
|
-
// Node.js in binary mode.
|
|
11
|
-
//
|
|
12
|
-
// Build: GOOS=windows GOARCH=amd64 go build -o tabctl-host.exe .
|
|
13
|
-
|
|
14
|
-
package main
|
|
15
|
-
|
|
16
|
-
import (
|
|
17
|
-
"bufio"
|
|
18
|
-
"fmt"
|
|
19
|
-
"io"
|
|
20
|
-
"os"
|
|
21
|
-
"os/exec"
|
|
22
|
-
"path/filepath"
|
|
23
|
-
"strings"
|
|
24
|
-
"sync"
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
func main() {
|
|
28
|
-
exePath, err := os.Executable()
|
|
29
|
-
if err != nil {
|
|
30
|
-
fmt.Fprintf(os.Stderr, "tabctl-host: cannot resolve exe path: %v\n", err)
|
|
31
|
-
os.Exit(1)
|
|
32
|
-
}
|
|
33
|
-
exeDir := filepath.Dir(exePath)
|
|
34
|
-
cfgPath := filepath.Join(exeDir, "host-launcher.cfg")
|
|
35
|
-
|
|
36
|
-
f, err := os.Open(cfgPath)
|
|
37
|
-
if err != nil {
|
|
38
|
-
fmt.Fprintf(os.Stderr, "tabctl-host: cannot open %s: %v\n", cfgPath, err)
|
|
39
|
-
os.Exit(1)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
scanner := bufio.NewScanner(f)
|
|
43
|
-
|
|
44
|
-
if !scanner.Scan() {
|
|
45
|
-
fmt.Fprintf(os.Stderr, "tabctl-host: missing node path in %s\n", cfgPath)
|
|
46
|
-
os.Exit(1)
|
|
47
|
-
}
|
|
48
|
-
nodePath := strings.TrimSpace(scanner.Text())
|
|
49
|
-
|
|
50
|
-
if !scanner.Scan() {
|
|
51
|
-
fmt.Fprintf(os.Stderr, "tabctl-host: missing host path in %s\n", cfgPath)
|
|
52
|
-
os.Exit(1)
|
|
53
|
-
}
|
|
54
|
-
hostPath := strings.TrimSpace(scanner.Text())
|
|
55
|
-
|
|
56
|
-
for scanner.Scan() {
|
|
57
|
-
line := strings.TrimSpace(scanner.Text())
|
|
58
|
-
if line == "" {
|
|
59
|
-
continue
|
|
60
|
-
}
|
|
61
|
-
if eq := strings.IndexByte(line, '='); eq > 0 {
|
|
62
|
-
os.Setenv(line[:eq], line[eq+1:])
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
f.Close()
|
|
66
|
-
|
|
67
|
-
// Proxy stdin/stdout via pipes to preserve binary mode.
|
|
68
|
-
cmd := exec.Command(nodePath, hostPath)
|
|
69
|
-
cmd.Stderr = os.Stderr
|
|
70
|
-
|
|
71
|
-
nodeIn, err := cmd.StdinPipe()
|
|
72
|
-
if err != nil {
|
|
73
|
-
fmt.Fprintf(os.Stderr, "tabctl-host: stdin pipe: %v\n", err)
|
|
74
|
-
os.Exit(1)
|
|
75
|
-
}
|
|
76
|
-
nodeOut, err := cmd.StdoutPipe()
|
|
77
|
-
if err != nil {
|
|
78
|
-
fmt.Fprintf(os.Stderr, "tabctl-host: stdout pipe: %v\n", err)
|
|
79
|
-
os.Exit(1)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if err := cmd.Start(); err != nil {
|
|
83
|
-
fmt.Fprintf(os.Stderr, "tabctl-host: failed to start: %v\n", err)
|
|
84
|
-
os.Exit(1)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
var wg sync.WaitGroup
|
|
88
|
-
wg.Add(2)
|
|
89
|
-
|
|
90
|
-
go func() {
|
|
91
|
-
defer wg.Done()
|
|
92
|
-
io.Copy(nodeIn, os.Stdin)
|
|
93
|
-
nodeIn.Close()
|
|
94
|
-
}()
|
|
95
|
-
|
|
96
|
-
go func() {
|
|
97
|
-
defer wg.Done()
|
|
98
|
-
io.Copy(os.Stdout, nodeOut)
|
|
99
|
-
}()
|
|
100
|
-
|
|
101
|
-
wg.Wait()
|
|
102
|
-
|
|
103
|
-
if err := cmd.Wait(); err != nil {
|
|
104
|
-
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
105
|
-
os.Exit(exitErr.ExitCode())
|
|
106
|
-
}
|
|
107
|
-
os.Exit(1)
|
|
108
|
-
}
|
|
109
|
-
}
|
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LOCAL_ACTIONS = exports.UNDO_ACTIONS = void 0;
|
|
4
|
-
exports.respond = respond;
|
|
5
|
-
exports.refreshTimeout = refreshTimeout;
|
|
6
|
-
exports.forwardToExtension = forwardToExtension;
|
|
7
|
-
exports.handleNativeMessage = handleNativeMessage;
|
|
8
|
-
exports.handleCliRequest = handleCliRequest;
|
|
9
|
-
const version_1 = require("../../shared/version");
|
|
10
|
-
const undo_1 = require("./undo");
|
|
11
|
-
const REQUEST_TIMEOUT_MS = 30000;
|
|
12
|
-
const MAX_RESPONSE_BYTES = 20 * 1024 * 1024;
|
|
13
|
-
const HISTORY_LIMIT_DEFAULT = 20;
|
|
14
|
-
const RETENTION_DAYS = 30;
|
|
15
|
-
exports.UNDO_ACTIONS = new Set([
|
|
16
|
-
"archive",
|
|
17
|
-
"close",
|
|
18
|
-
"group-update",
|
|
19
|
-
"group-ungroup",
|
|
20
|
-
"group-assign",
|
|
21
|
-
"group-gather",
|
|
22
|
-
"move-tab",
|
|
23
|
-
"move-group",
|
|
24
|
-
"merge-window",
|
|
25
|
-
]);
|
|
26
|
-
exports.LOCAL_ACTIONS = new Set(["history", "undo", "version"]);
|
|
27
|
-
function respond(socket, payload) {
|
|
28
|
-
const serialized = JSON.stringify(payload);
|
|
29
|
-
if (Buffer.byteLength(serialized, "utf8") > MAX_RESPONSE_BYTES) {
|
|
30
|
-
socket.write(`${JSON.stringify({
|
|
31
|
-
ok: false,
|
|
32
|
-
action: payload.action,
|
|
33
|
-
requestId: payload.requestId,
|
|
34
|
-
component: "host",
|
|
35
|
-
version: version_1.VERSION,
|
|
36
|
-
error: { message: "Response too large", hint: "Reduce scope or use --out to write files." },
|
|
37
|
-
})}\n`);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
socket.write(`${serialized}\n`);
|
|
41
|
-
}
|
|
42
|
-
function refreshTimeout(deps, pendingRequest, requestId) {
|
|
43
|
-
clearTimeout(pendingRequest.timeout);
|
|
44
|
-
pendingRequest.timeout = setTimeout(() => {
|
|
45
|
-
deps.pending.delete(requestId);
|
|
46
|
-
respond(pendingRequest.socket, {
|
|
47
|
-
ok: false,
|
|
48
|
-
action: pendingRequest.action,
|
|
49
|
-
requestId,
|
|
50
|
-
component: "host",
|
|
51
|
-
version: version_1.VERSION,
|
|
52
|
-
error: { message: "Request timed out" },
|
|
53
|
-
});
|
|
54
|
-
}, REQUEST_TIMEOUT_MS);
|
|
55
|
-
}
|
|
56
|
-
function forwardToExtension(deps, socket, request, overrides = {}) {
|
|
57
|
-
const requestId = request.id || deps.createId("req");
|
|
58
|
-
const txid = overrides.txid || null;
|
|
59
|
-
const params = { ...(request.params || {}) };
|
|
60
|
-
if (txid) {
|
|
61
|
-
params.txid = txid;
|
|
62
|
-
}
|
|
63
|
-
if (!exports.LOCAL_ACTIONS.has(request.action)) {
|
|
64
|
-
params.client = {
|
|
65
|
-
component: "host",
|
|
66
|
-
version: version_1.VERSION,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
deps.pending.set(requestId, {
|
|
70
|
-
socket,
|
|
71
|
-
action: request.action,
|
|
72
|
-
txid,
|
|
73
|
-
timeout: setTimeout(() => {
|
|
74
|
-
deps.pending.delete(requestId);
|
|
75
|
-
respond(socket, {
|
|
76
|
-
ok: false,
|
|
77
|
-
action: request.action,
|
|
78
|
-
requestId,
|
|
79
|
-
component: "host",
|
|
80
|
-
version: version_1.VERSION,
|
|
81
|
-
error: { message: "Request timed out" },
|
|
82
|
-
});
|
|
83
|
-
}, REQUEST_TIMEOUT_MS),
|
|
84
|
-
});
|
|
85
|
-
deps.sendNative({ id: requestId, action: request.action, params });
|
|
86
|
-
}
|
|
87
|
-
function handleNativeMessage(deps, payload) {
|
|
88
|
-
let message;
|
|
89
|
-
try {
|
|
90
|
-
message = JSON.parse(payload);
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
const err = error;
|
|
94
|
-
deps.log("Failed to parse native message", err.message);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
const messageId = message.id;
|
|
98
|
-
if (!messageId) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
const pendingRequest = deps.pending.get(messageId);
|
|
102
|
-
if (!pendingRequest) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
if (message.progress) {
|
|
106
|
-
refreshTimeout(deps, pendingRequest, messageId);
|
|
107
|
-
respond(pendingRequest.socket, {
|
|
108
|
-
ok: true,
|
|
109
|
-
action: pendingRequest.action,
|
|
110
|
-
requestId: messageId,
|
|
111
|
-
progress: true,
|
|
112
|
-
component: "host",
|
|
113
|
-
version: version_1.VERSION,
|
|
114
|
-
data: message.data || {},
|
|
115
|
-
});
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
const messageData = message.data || {};
|
|
119
|
-
const extensionVersion = typeof messageData.version === "string" ? messageData.version : null;
|
|
120
|
-
const extensionComponent = typeof messageData.component === "string" ? messageData.component : null;
|
|
121
|
-
if (extensionVersion && extensionVersion !== version_1.VERSION) {
|
|
122
|
-
deps.log(`Version mismatch: host ${version_1.VERSION}, extension ${extensionVersion}`);
|
|
123
|
-
}
|
|
124
|
-
clearTimeout(pendingRequest.timeout);
|
|
125
|
-
deps.pending.delete(messageId);
|
|
126
|
-
if (!message.ok) {
|
|
127
|
-
respond(pendingRequest.socket, {
|
|
128
|
-
ok: false,
|
|
129
|
-
action: pendingRequest.action,
|
|
130
|
-
requestId: messageId,
|
|
131
|
-
component: "host",
|
|
132
|
-
version: version_1.VERSION,
|
|
133
|
-
error: message.error || { message: "Unknown error" },
|
|
134
|
-
});
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
if (pendingRequest.action === "analyze") {
|
|
138
|
-
const analysisId = deps.createId("analysis");
|
|
139
|
-
deps.analyses.set(analysisId, {
|
|
140
|
-
createdAt: Date.now(),
|
|
141
|
-
data: messageData,
|
|
142
|
-
});
|
|
143
|
-
respond(pendingRequest.socket, {
|
|
144
|
-
ok: true,
|
|
145
|
-
action: "analyze",
|
|
146
|
-
requestId: messageId,
|
|
147
|
-
component: "host",
|
|
148
|
-
version: version_1.VERSION,
|
|
149
|
-
data: {
|
|
150
|
-
...messageData,
|
|
151
|
-
extensionVersion,
|
|
152
|
-
extensionComponent,
|
|
153
|
-
hostBaseVersion: version_1.BASE_VERSION,
|
|
154
|
-
hostGitSha: version_1.GIT_SHA,
|
|
155
|
-
hostDirty: version_1.DIRTY,
|
|
156
|
-
analysisId,
|
|
157
|
-
},
|
|
158
|
-
});
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
if (exports.UNDO_ACTIONS.has(pendingRequest.action)) {
|
|
162
|
-
const record = {
|
|
163
|
-
txid: pendingRequest.txid,
|
|
164
|
-
createdAt: Date.now(),
|
|
165
|
-
action: pendingRequest.action,
|
|
166
|
-
summary: messageData.summary || {},
|
|
167
|
-
undo: messageData.undo || null,
|
|
168
|
-
};
|
|
169
|
-
if (record.undo) {
|
|
170
|
-
(0, undo_1.appendUndoRecord)(deps.undoLog, record);
|
|
171
|
-
}
|
|
172
|
-
respond(pendingRequest.socket, {
|
|
173
|
-
ok: true,
|
|
174
|
-
action: pendingRequest.action,
|
|
175
|
-
requestId: messageId,
|
|
176
|
-
component: "host",
|
|
177
|
-
version: version_1.VERSION,
|
|
178
|
-
data: {
|
|
179
|
-
...messageData,
|
|
180
|
-
extensionVersion,
|
|
181
|
-
extensionComponent,
|
|
182
|
-
hostBaseVersion: version_1.BASE_VERSION,
|
|
183
|
-
hostGitSha: version_1.GIT_SHA,
|
|
184
|
-
hostDirty: version_1.DIRTY,
|
|
185
|
-
txid: pendingRequest.txid,
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
respond(pendingRequest.socket, {
|
|
191
|
-
ok: true,
|
|
192
|
-
action: pendingRequest.action,
|
|
193
|
-
requestId: messageId,
|
|
194
|
-
component: "host",
|
|
195
|
-
version: version_1.VERSION,
|
|
196
|
-
data: {
|
|
197
|
-
...messageData,
|
|
198
|
-
extensionVersion,
|
|
199
|
-
extensionComponent,
|
|
200
|
-
hostBaseVersion: version_1.BASE_VERSION,
|
|
201
|
-
hostGitSha: version_1.GIT_SHA,
|
|
202
|
-
hostDirty: version_1.DIRTY,
|
|
203
|
-
},
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
function handleCliRequest(deps, socket, request) {
|
|
207
|
-
if (!request || typeof request !== "object") {
|
|
208
|
-
respond(socket, { ok: false, error: { message: "Invalid request" } });
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
const action = request.action;
|
|
212
|
-
if (!action) {
|
|
213
|
-
respond(socket, { ok: false, error: { message: "Missing action" } });
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
if (action === "history") {
|
|
217
|
-
const limit = Number.isFinite(request.params?.limit)
|
|
218
|
-
? Number(request.params?.limit)
|
|
219
|
-
: HISTORY_LIMIT_DEFAULT;
|
|
220
|
-
const records = (0, undo_1.readUndoRecords)(deps.undoLog);
|
|
221
|
-
const filtered = (0, undo_1.filterByRetention)(records, RETENTION_DAYS);
|
|
222
|
-
respond(socket, {
|
|
223
|
-
ok: true,
|
|
224
|
-
action,
|
|
225
|
-
requestId: request.id || null,
|
|
226
|
-
component: "host",
|
|
227
|
-
version: version_1.VERSION,
|
|
228
|
-
data: filtered.slice(-limit),
|
|
229
|
-
});
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
if (action === "version") {
|
|
233
|
-
respond(socket, {
|
|
234
|
-
ok: true,
|
|
235
|
-
action,
|
|
236
|
-
requestId: request.id || null,
|
|
237
|
-
component: "host",
|
|
238
|
-
version: version_1.VERSION,
|
|
239
|
-
data: {
|
|
240
|
-
version: version_1.VERSION,
|
|
241
|
-
baseVersion: version_1.BASE_VERSION,
|
|
242
|
-
gitSha: version_1.GIT_SHA,
|
|
243
|
-
dirty: version_1.DIRTY,
|
|
244
|
-
component: "host",
|
|
245
|
-
},
|
|
246
|
-
});
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
if (action === "undo") {
|
|
250
|
-
const txid = request.params?.txid;
|
|
251
|
-
const latest = request.params?.latest === true;
|
|
252
|
-
if (!txid && !latest) {
|
|
253
|
-
respond(socket, {
|
|
254
|
-
ok: false,
|
|
255
|
-
action,
|
|
256
|
-
component: "host",
|
|
257
|
-
version: version_1.VERSION,
|
|
258
|
-
error: {
|
|
259
|
-
message: "Missing txid",
|
|
260
|
-
hint: "Use tabctl history --json to find a txid, or run tabctl undo --latest",
|
|
261
|
-
},
|
|
262
|
-
});
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
const record = txid
|
|
266
|
-
? (0, undo_1.findUndoRecord)(deps.undoLog, txid, RETENTION_DAYS)
|
|
267
|
-
: (0, undo_1.findLatestUndoRecord)(deps.undoLog, RETENTION_DAYS);
|
|
268
|
-
if (!record) {
|
|
269
|
-
respond(socket, { ok: false, action, component: "host", version: version_1.VERSION, error: { message: "Undo record not found" } });
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
forwardToExtension(deps, socket, {
|
|
273
|
-
id: request.id,
|
|
274
|
-
action: "undo",
|
|
275
|
-
params: { record },
|
|
276
|
-
});
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
if (action === "close" && request.params?.mode === "apply") {
|
|
280
|
-
const analysisId = request.params.analysisId;
|
|
281
|
-
const analysis = analysisId ? deps.analyses.get(analysisId) : undefined;
|
|
282
|
-
if (!analysis) {
|
|
283
|
-
respond(socket, { ok: false, action, component: "host", version: version_1.VERSION, error: { message: "Unknown analysisId" } });
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
const candidates = analysis.data.candidates || [];
|
|
287
|
-
const tabIds = candidates.map((candidate) => candidate.tabId).filter(Boolean);
|
|
288
|
-
const expectedUrls = {};
|
|
289
|
-
for (const candidate of candidates) {
|
|
290
|
-
if (candidate.tabId) {
|
|
291
|
-
expectedUrls[String(candidate.tabId)] = candidate.url;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
if (!tabIds.length) {
|
|
295
|
-
respond(socket, {
|
|
296
|
-
ok: true,
|
|
297
|
-
action,
|
|
298
|
-
requestId: request.id || null,
|
|
299
|
-
component: "host",
|
|
300
|
-
version: version_1.VERSION,
|
|
301
|
-
data: {
|
|
302
|
-
txid: null,
|
|
303
|
-
summary: { closedTabs: 0, skippedTabs: 0 },
|
|
304
|
-
skipped: [],
|
|
305
|
-
},
|
|
306
|
-
});
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
const txid = deps.createId("tx");
|
|
310
|
-
forwardToExtension(deps, socket, {
|
|
311
|
-
id: request.id,
|
|
312
|
-
action: "close",
|
|
313
|
-
params: {
|
|
314
|
-
mode: "apply",
|
|
315
|
-
tabIds,
|
|
316
|
-
expectedUrls,
|
|
317
|
-
},
|
|
318
|
-
}, { txid });
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
if (exports.UNDO_ACTIONS.has(action)) {
|
|
322
|
-
const txid = deps.createId("tx");
|
|
323
|
-
forwardToExtension(deps, socket, request, { txid });
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
forwardToExtension(deps, socket, request);
|
|
327
|
-
}
|
package/dist/host/lib/undo.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.appendUndoRecord = appendUndoRecord;
|
|
7
|
-
exports.readUndoRecords = readUndoRecords;
|
|
8
|
-
exports.filterByRetention = filterByRetention;
|
|
9
|
-
exports.findUndoRecord = findUndoRecord;
|
|
10
|
-
exports.findLatestUndoRecord = findLatestUndoRecord;
|
|
11
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
-
const DEFAULT_RETENTION_DAYS = 30;
|
|
14
|
-
function appendUndoRecord(filePath, record) {
|
|
15
|
-
const dir = node_path_1.default.dirname(filePath);
|
|
16
|
-
node_fs_1.default.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
17
|
-
node_fs_1.default.appendFileSync(filePath, `${JSON.stringify(record)}\n`, "utf8");
|
|
18
|
-
}
|
|
19
|
-
function readUndoRecords(filePath) {
|
|
20
|
-
try {
|
|
21
|
-
const content = node_fs_1.default.readFileSync(filePath, "utf8");
|
|
22
|
-
const lines = content.split("\n").filter(Boolean);
|
|
23
|
-
const records = [];
|
|
24
|
-
for (const line of lines) {
|
|
25
|
-
try {
|
|
26
|
-
records.push(JSON.parse(line));
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
// ignore malformed lines
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return records;
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
return [];
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
function filterByRetention(records, retentionDays = DEFAULT_RETENTION_DAYS, now = Date.now()) {
|
|
39
|
-
const cutoff = now - retentionDays * 24 * 60 * 60 * 1000;
|
|
40
|
-
return records.filter((record) => {
|
|
41
|
-
const createdAt = record.createdAt;
|
|
42
|
-
return !createdAt || createdAt >= cutoff;
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
function findUndoRecord(filePath, txid, retentionDays = DEFAULT_RETENTION_DAYS, now = Date.now()) {
|
|
46
|
-
const records = filterByRetention(readUndoRecords(filePath), retentionDays, now);
|
|
47
|
-
for (let i = records.length - 1; i >= 0; i -= 1) {
|
|
48
|
-
if (records[i].txid === txid) {
|
|
49
|
-
return records[i];
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
function findLatestUndoRecord(filePath, retentionDays = DEFAULT_RETENTION_DAYS, now = Date.now()) {
|
|
55
|
-
const records = filterByRetention(readUndoRecords(filePath), retentionDays, now);
|
|
56
|
-
if (!records.length) {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
return records[records.length - 1] || null;
|
|
60
|
-
}
|