cloak22 2.2.0

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/dist/output.js ADDED
@@ -0,0 +1,26 @@
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.formatHeading = formatHeading;
7
+ exports.formatSuccess = formatSuccess;
8
+ exports.formatWarning = formatWarning;
9
+ exports.formatError = formatError;
10
+ exports.formatInfo = formatInfo;
11
+ const chalk_1 = __importDefault(require("chalk"));
12
+ function formatHeading(text) {
13
+ return chalk_1.default.bold.cyan(text);
14
+ }
15
+ function formatSuccess(text) {
16
+ return chalk_1.default.green(text);
17
+ }
18
+ function formatWarning(text) {
19
+ return chalk_1.default.yellow(text);
20
+ }
21
+ function formatError(text) {
22
+ return chalk_1.default.red(text);
23
+ }
24
+ function formatInfo(text) {
25
+ return chalk_1.default.cyan(text);
26
+ }
@@ -0,0 +1,232 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CloakStateDb = void 0;
4
+ function loadDatabaseConstructor() {
5
+ const sqlite = require("node:sqlite");
6
+ return sqlite.DatabaseSync;
7
+ }
8
+ function openDatabase(dbPath) {
9
+ const DatabaseSync = loadDatabaseConstructor();
10
+ return new DatabaseSync(dbPath);
11
+ }
12
+ function normalizeCookieUrls(urls) {
13
+ return [...new Set(urls)].sort((left, right) => left.localeCompare(right));
14
+ }
15
+ function initializeSchema(database) {
16
+ database.exec(`
17
+ CREATE TABLE IF NOT EXISTS settings (
18
+ key TEXT PRIMARY KEY,
19
+ value TEXT NOT NULL
20
+ );
21
+
22
+ CREATE TABLE IF NOT EXISTS profile_cookie_urls (
23
+ profile TEXT NOT NULL,
24
+ url TEXT NOT NULL,
25
+ PRIMARY KEY (profile, url)
26
+ );
27
+
28
+ CREATE TABLE IF NOT EXISTS daemon_state (
29
+ slot INTEGER PRIMARY KEY CHECK (slot = 1),
30
+ pid INTEGER NOT NULL,
31
+ profile TEXT,
32
+ cookie_urls TEXT NOT NULL,
33
+ cookie_file TEXT,
34
+ headless INTEGER NOT NULL,
35
+ started_at TEXT NOT NULL,
36
+ log_path TEXT NOT NULL
37
+ );
38
+ `);
39
+ const daemonStateColumns = database
40
+ .prepare("PRAGMA table_info(daemon_state)")
41
+ .all()
42
+ .map((row) => row.name)
43
+ .filter((value) => typeof value === "string");
44
+ if (!daemonStateColumns.includes("cookie_file")) {
45
+ database.exec("ALTER TABLE daemon_state ADD COLUMN cookie_file TEXT;");
46
+ }
47
+ }
48
+ function readJson(value) {
49
+ if (!value) {
50
+ return undefined;
51
+ }
52
+ try {
53
+ return JSON.parse(value);
54
+ }
55
+ catch {
56
+ return undefined;
57
+ }
58
+ }
59
+ class CloakStateDb {
60
+ dbPath;
61
+ constructor(dbPath) {
62
+ this.dbPath = dbPath;
63
+ }
64
+ withDatabase(operation) {
65
+ const database = openDatabase(this.dbPath);
66
+ initializeSchema(database);
67
+ try {
68
+ return operation(database);
69
+ }
70
+ finally {
71
+ database.close();
72
+ }
73
+ }
74
+ getDefaultProfile() {
75
+ return this.withDatabase((database) => {
76
+ const row = database
77
+ .prepare("SELECT value FROM settings WHERE key = ?")
78
+ .get("default_profile");
79
+ return typeof row?.value === "string" ? row.value : undefined;
80
+ });
81
+ }
82
+ setDefaultProfile(profile) {
83
+ this.withDatabase((database) => {
84
+ database
85
+ .prepare([
86
+ "INSERT INTO settings (key, value)",
87
+ "VALUES (?, ?)",
88
+ "ON CONFLICT(key) DO UPDATE SET value = excluded.value",
89
+ ].join(" "))
90
+ .run("default_profile", profile);
91
+ });
92
+ }
93
+ getRememberedCookieUrls(profile) {
94
+ return this.withDatabase((database) => {
95
+ const rows = database
96
+ .prepare([
97
+ "SELECT url",
98
+ "FROM profile_cookie_urls",
99
+ "WHERE profile = ?",
100
+ "ORDER BY url COLLATE NOCASE",
101
+ ].join(" "))
102
+ .all(profile);
103
+ return rows
104
+ .map((row) => row.url)
105
+ .filter((value) => typeof value === "string");
106
+ });
107
+ }
108
+ rememberCookieUrls(profile, urls) {
109
+ const normalized = normalizeCookieUrls(urls);
110
+ this.withDatabase((database) => {
111
+ const statement = database.prepare([
112
+ "INSERT INTO profile_cookie_urls (profile, url)",
113
+ "VALUES (?, ?)",
114
+ "ON CONFLICT(profile, url) DO NOTHING",
115
+ ].join(" "));
116
+ for (const url of normalized) {
117
+ statement.run(profile, url);
118
+ }
119
+ });
120
+ return this.getRememberedCookieUrls(profile);
121
+ }
122
+ replaceRememberedCookieUrls(profile, urls) {
123
+ const normalized = normalizeCookieUrls(urls);
124
+ this.withDatabase((database) => {
125
+ database
126
+ .prepare("DELETE FROM profile_cookie_urls WHERE profile = ?")
127
+ .run(profile);
128
+ const insert = database.prepare("INSERT INTO profile_cookie_urls (profile, url) VALUES (?, ?)");
129
+ for (const url of normalized) {
130
+ insert.run(profile, url);
131
+ }
132
+ });
133
+ return normalized;
134
+ }
135
+ getDaemonState() {
136
+ return this.withDatabase((database) => {
137
+ const row = database
138
+ .prepare([
139
+ "SELECT pid, profile, cookie_urls, cookie_file, headless, started_at, log_path",
140
+ "FROM daemon_state",
141
+ "WHERE slot = 1",
142
+ ].join(" "))
143
+ .get();
144
+ if (!row) {
145
+ return undefined;
146
+ }
147
+ const cookieUrls = readJson(typeof row.cookie_urls === "string" ? row.cookie_urls : undefined);
148
+ if (typeof row.pid !== "number" || !Array.isArray(cookieUrls)) {
149
+ return undefined;
150
+ }
151
+ return {
152
+ pid: row.pid,
153
+ profile: typeof row.profile === "string" ? row.profile : undefined,
154
+ cookieUrls,
155
+ cookieFile: typeof row.cookie_file === "string" ? row.cookie_file : undefined,
156
+ headless: row.headless === 1,
157
+ startedAt: typeof row.started_at === "string"
158
+ ? row.started_at
159
+ : new Date(0).toISOString(),
160
+ logPath: typeof row.log_path === "string" ? row.log_path : "",
161
+ };
162
+ });
163
+ }
164
+ setDaemonState(state) {
165
+ this.withDatabase((database) => {
166
+ database
167
+ .prepare([
168
+ "INSERT INTO daemon_state",
169
+ "(slot, pid, profile, cookie_urls, cookie_file, headless, started_at, log_path)",
170
+ "VALUES (1, ?, ?, ?, ?, ?, ?, ?)",
171
+ "ON CONFLICT(slot) DO UPDATE SET",
172
+ "pid = excluded.pid,",
173
+ "profile = excluded.profile,",
174
+ "cookie_urls = excluded.cookie_urls,",
175
+ "cookie_file = excluded.cookie_file,",
176
+ "headless = excluded.headless,",
177
+ "started_at = excluded.started_at,",
178
+ "log_path = excluded.log_path",
179
+ ].join(" "))
180
+ .run(state.pid, state.profile ?? null, JSON.stringify(normalizeCookieUrls(state.cookieUrls)), state.cookieFile ?? null, state.headless ? 1 : 0, state.startedAt, state.logPath);
181
+ });
182
+ }
183
+ clearDaemonState(pid) {
184
+ this.withDatabase((database) => {
185
+ if (typeof pid === "number") {
186
+ database
187
+ .prepare("DELETE FROM daemon_state WHERE slot = 1 AND pid = ?")
188
+ .run(pid);
189
+ return;
190
+ }
191
+ database.prepare("DELETE FROM daemon_state WHERE slot = 1").run();
192
+ });
193
+ }
194
+ getLastDaemonCommand() {
195
+ return this.withDatabase((database) => {
196
+ const row = database
197
+ .prepare("SELECT value FROM settings WHERE key = ?")
198
+ .get("last_daemon_command");
199
+ const command = readJson(typeof row?.value === "string" ? row.value : undefined);
200
+ if (!command ||
201
+ !Array.isArray(command.cookieUrls) ||
202
+ (command.cookieFile !== undefined &&
203
+ typeof command.cookieFile !== "string")) {
204
+ return undefined;
205
+ }
206
+ return {
207
+ headless: Boolean(command.headless),
208
+ profile: command.profile,
209
+ cookieUrls: normalizeCookieUrls(command.cookieUrls),
210
+ cookieFile: command.cookieFile,
211
+ };
212
+ });
213
+ }
214
+ setLastDaemonCommand(command) {
215
+ const normalized = {
216
+ headless: Boolean(command.headless),
217
+ profile: command.profile,
218
+ cookieUrls: normalizeCookieUrls(command.cookieUrls),
219
+ cookieFile: command.cookieFile,
220
+ };
221
+ this.withDatabase((database) => {
222
+ database
223
+ .prepare([
224
+ "INSERT INTO settings (key, value)",
225
+ "VALUES (?, ?)",
226
+ "ON CONFLICT(key) DO UPDATE SET value = excluded.value",
227
+ ].join(" "))
228
+ .run("last_daemon_command", JSON.stringify(normalized));
229
+ });
230
+ }
231
+ }
232
+ exports.CloakStateDb = CloakStateDb;
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "cloak22",
3
+ "version": "2.2.0",
4
+ "description": "Chrome sidecar for OpenCLI with daemonized Patchright startup and remembered cookie URLs",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/lmist/cloak.git"
9
+ },
10
+ "engines": {
11
+ "node": ">=22.13.0"
12
+ },
13
+ "bin": {
14
+ "cloak": "bin/cloak.js"
15
+ },
16
+ "files": [
17
+ "bin",
18
+ "dist",
19
+ ".githooks/pre-commit",
20
+ ".githooks/pre-push",
21
+ "scripts/postinstall.cjs",
22
+ "scripts/render-readme.cjs",
23
+ "scripts/setup-git-hooks.cjs",
24
+ "docs/assets/cloak-logo-readme-centered.png",
25
+ "src/app-paths.ts",
26
+ "src/chrome-cookies.ts",
27
+ "src/chrome-profile-sites.ts",
28
+ "src/chrome-profiles.ts",
29
+ "src/cli.ts",
30
+ "src/cookies.ts",
31
+ "src/daemon.ts",
32
+ "src/extension.ts",
33
+ "src/install-extension.ts",
34
+ "src/main.ts",
35
+ "src/output.ts",
36
+ "src/state-db.ts",
37
+ "README.md",
38
+ "README.org"
39
+ ],
40
+ "scripts": {
41
+ "postinstall": "node scripts/postinstall.cjs",
42
+ "build": "node scripts/build.cjs",
43
+ "render-readme": "node scripts/render-readme.cjs",
44
+ "check-readme": "node scripts/render-readme.cjs --check",
45
+ "prepack": "npm run build",
46
+ "typecheck": "node ./node_modules/typescript/bin/tsc --noEmit",
47
+ "start": "node --import tsx src/main.ts",
48
+ "test": "node --import tsx --test src/**/*.test.ts"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ },
53
+ "dependencies": {
54
+ "adm-zip": "^0.5.16",
55
+ "chalk": "^4.1.2",
56
+ "patchright": "^1.58.2",
57
+ "tsx": "^4.21.0",
58
+ "yargs": "^17.7.2"
59
+ },
60
+ "devDependencies": {
61
+ "@types/adm-zip": "^0.5.7",
62
+ "@types/node": "^20.0.0",
63
+ "@types/yargs": "^17.0.33",
64
+ "typescript": "^5.3.0"
65
+ }
66
+ }
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { existsSync } = require("node:fs");
4
+ const path = require("node:path");
5
+ const { execFileSync } = require("node:child_process");
6
+
7
+ const rootDir = path.resolve(__dirname, "..");
8
+ const distEntrypoint = path.join(rootDir, "dist", "install-extension.js");
9
+ const sourceEntrypoint = path.join(rootDir, "src", "install-extension.ts");
10
+ const tsxEntrypoint = path.join(rootDir, "node_modules", "tsx", "dist", "loader.mjs");
11
+ const patchrightCliEntrypoint = path.join(rootDir, "node_modules", "patchright", "cli.js");
12
+ const setupHooksEntrypoint = path.join(rootDir, "scripts", "setup-git-hooks.cjs");
13
+
14
+ const command = process.execPath;
15
+ const args = existsSync(sourceEntrypoint) && existsSync(tsxEntrypoint)
16
+ ? ["--import", "tsx", sourceEntrypoint]
17
+ : [distEntrypoint];
18
+
19
+ function runPostinstallStep(stepArgs) {
20
+ execFileSync(command, stepArgs, {
21
+ cwd: rootDir,
22
+ stdio: "inherit",
23
+ });
24
+ }
25
+
26
+ try {
27
+ runPostinstallStep(args);
28
+ } catch (error) {
29
+ const message = error instanceof Error ? error.message : String(error);
30
+ console.warn(
31
+ `[cloak] Warning: failed to warm the OpenCLI extension cache during postinstall. cloak will retry at runtime. (${message})`
32
+ );
33
+ }
34
+
35
+ if (existsSync(patchrightCliEntrypoint)) {
36
+ try {
37
+ runPostinstallStep([patchrightCliEntrypoint, "install", "chromium"]);
38
+ } catch (error) {
39
+ const message = error instanceof Error ? error.message : String(error);
40
+ console.warn(
41
+ `[cloak] Warning: failed to install the Patchright Chromium browser during postinstall. You can retry with 'npx patchright install chromium'. (${message})`
42
+ );
43
+ }
44
+ }
45
+
46
+ if (existsSync(setupHooksEntrypoint)) {
47
+ try {
48
+ runPostinstallStep([setupHooksEntrypoint]);
49
+ } catch (error) {
50
+ const message = error instanceof Error ? error.message : String(error);
51
+ console.warn(
52
+ `[cloak] Warning: failed to configure local git hooks. You can retry with 'node scripts/setup-git-hooks.cjs'. (${message})`
53
+ );
54
+ }
55
+ }
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const { execFileSync } = require("node:child_process");
6
+
7
+ const rootDir = path.resolve(__dirname, "..");
8
+ const sourcePath = path.join(rootDir, "README.org");
9
+ const targetPath = path.join(rootDir, "README.md");
10
+ const generatedBanner =
11
+ "<!-- Generated from README.org by scripts/render-readme.cjs. Do not edit README.md directly. -->";
12
+
13
+ function renderReadme() {
14
+ const rendered = execFileSync(
15
+ "pandoc",
16
+ ["-f", "org", "-t", "gfm", "--wrap=none", sourcePath],
17
+ {
18
+ cwd: rootDir,
19
+ encoding: "utf8",
20
+ stdio: ["ignore", "pipe", "inherit"],
21
+ }
22
+ );
23
+
24
+ const lines = rendered.split(/\r?\n/);
25
+ if (
26
+ lines[0] ===
27
+ '<span class="spurious-link" target="docs/assets/cloak-logo-readme-centered.png">*docs/assets/cloak-logo-readme-centered.png*</span>'
28
+ ) {
29
+ lines[0] = "![cloak logo](docs/assets/cloak-logo-readme-centered.png)";
30
+ }
31
+
32
+ return `${generatedBanner}\n\n${lines.join("\n").trimEnd()}\n`;
33
+ }
34
+
35
+ function main() {
36
+ const checkOnly = process.argv.includes("--check");
37
+ const content = renderReadme();
38
+ const current = fs.existsSync(targetPath) ? fs.readFileSync(targetPath, "utf8") : "";
39
+
40
+ if (checkOnly) {
41
+ if (current !== content) {
42
+ console.error("README.md is out of date. Run `node scripts/render-readme.cjs`.");
43
+ process.exit(1);
44
+ }
45
+
46
+ return;
47
+ }
48
+
49
+ if (current !== content) {
50
+ fs.writeFileSync(targetPath, content);
51
+ }
52
+ }
53
+
54
+ main();
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const { execFileSync } = require("node:child_process");
6
+
7
+ const rootDir = path.resolve(__dirname, "..");
8
+ const gitDir = path.join(rootDir, ".git");
9
+
10
+ if (!fs.existsSync(gitDir)) {
11
+ process.exit(0);
12
+ }
13
+
14
+ try {
15
+ execFileSync("git", ["config", "core.hooksPath", ".githooks"], {
16
+ cwd: rootDir,
17
+ stdio: "ignore",
18
+ });
19
+ } catch {
20
+ process.exit(0);
21
+ }
@@ -0,0 +1,39 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+
4
+ export type AppPaths = {
5
+ extensionsDir: string;
6
+ configDir: string;
7
+ stateDbPath: string;
8
+ daemonLogPath: string;
9
+ };
10
+
11
+ export function defaultAppRootDir(
12
+ dependencies: {
13
+ homedir?: () => string;
14
+ } = {}
15
+ ): string {
16
+ const homedir = dependencies.homedir ?? os.homedir;
17
+ return path.join(homedir(), ".cache", "cloak");
18
+ }
19
+
20
+ export function defaultConfigRootDir(
21
+ dependencies: {
22
+ homedir?: () => string;
23
+ } = {}
24
+ ): string {
25
+ const homedir = dependencies.homedir ?? os.homedir;
26
+ return path.join(homedir(), ".config", "cloak");
27
+ }
28
+
29
+ export function resolveAppPaths(
30
+ rootDir: string = defaultAppRootDir(),
31
+ configDir: string = defaultConfigRootDir()
32
+ ): AppPaths {
33
+ return {
34
+ extensionsDir: rootDir,
35
+ configDir,
36
+ stateDbPath: path.join(configDir, "state.sqlite"),
37
+ daemonLogPath: path.join(rootDir, "daemon.log"),
38
+ };
39
+ }