@erdemtuna/doc-review 0.7.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/LICENSE +21 -0
- package/README.md +117 -0
- package/package.json +57 -0
- package/src/SKILL.md +143 -0
- package/src/anchor-text.js +115 -0
- package/src/chrome-client.js +986 -0
- package/src/chrome-session.js +15 -0
- package/src/chrome.css +458 -0
- package/src/chrome.html +83 -0
- package/src/cli.js +341 -0
- package/src/click-target.js +26 -0
- package/src/editing.js +99 -0
- package/src/frame-channel.js +44 -0
- package/src/frame-policy.js +16 -0
- package/src/html-transform.js +62 -0
- package/src/markdown.js +87 -0
- package/src/paths.js +87 -0
- package/src/sdk.js +1520 -0
- package/src/serialize.js +46 -0
- package/src/server-entry.js +26 -0
- package/src/server-lock.js +101 -0
- package/src/server.js +1268 -0
- package/src/setup.js +117 -0
- package/src/state.js +491 -0
package/src/paths.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
|
|
6
|
+
// Bump this when the CLI and detached server no longer share the same request
|
|
7
|
+
// contract. A new CLI must not silently reuse an older background server.
|
|
8
|
+
export const SERVER_PROTOCOL = 11;
|
|
9
|
+
|
|
10
|
+
export function stateDir() {
|
|
11
|
+
const override = process.env.DOC_REVIEW_STATE_DIR;
|
|
12
|
+
return override ? path.resolve(override) : path.join(homedir(), ".doc-review");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function statePath() {
|
|
16
|
+
return path.join(stateDir(), "state.json");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function serverPath() {
|
|
20
|
+
return path.join(stateDir(), "server.json");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function serverLockPath() {
|
|
24
|
+
return path.join(stateDir(), "server.lock");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function ensureStateDir() {
|
|
28
|
+
// Comments and the server token live here; keep it private to the user.
|
|
29
|
+
fs.mkdirSync(stateDir(), { recursive: true, mode: 0o700 });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Return a canonical loopback URL, or null when the input is a filesystem path.
|
|
36
|
+
* HTTP-looking non-loopback targets fail loudly instead of becoming odd paths.
|
|
37
|
+
*/
|
|
38
|
+
export function localUrl(target) {
|
|
39
|
+
const value = String(target || "");
|
|
40
|
+
if (!/^https?:\/\//i.test(value)) return null;
|
|
41
|
+
let parsed;
|
|
42
|
+
try {
|
|
43
|
+
parsed = new URL(value);
|
|
44
|
+
} catch {
|
|
45
|
+
throw new Error(`Invalid localhost URL: ${value}`);
|
|
46
|
+
}
|
|
47
|
+
if (!LOOPBACK_HOSTS.has(parsed.hostname)) {
|
|
48
|
+
throw new Error("doc-review URL support is limited to localhost, 127.0.0.1, and [::1].");
|
|
49
|
+
}
|
|
50
|
+
if (parsed.username || parsed.password) {
|
|
51
|
+
throw new Error("Localhost review URLs cannot contain credentials.");
|
|
52
|
+
}
|
|
53
|
+
parsed.hash = "";
|
|
54
|
+
return parsed.href;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Canonical identity for a page: the real path of the file on disk. */
|
|
58
|
+
export function pageKey(file) {
|
|
59
|
+
let real = path.resolve(file);
|
|
60
|
+
try {
|
|
61
|
+
real = fs.realpathSync(real);
|
|
62
|
+
} catch {
|
|
63
|
+
// File may not exist yet; the resolved path is still a stable identity.
|
|
64
|
+
}
|
|
65
|
+
return createHash("sha256").update(real).digest("hex").slice(0, 16);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Stable identity for either an existing file target or a localhost URL. */
|
|
69
|
+
export function targetKey(target) {
|
|
70
|
+
const url = localUrl(target);
|
|
71
|
+
if (!url) return pageKey(target);
|
|
72
|
+
return createHash("sha256").update(`url:${url}`).digest("hex").slice(0, 16);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function canonicalTarget(target) {
|
|
76
|
+
const url = localUrl(target);
|
|
77
|
+
return url ? { kind: "url", value: url } : { kind: "file", value: realFile(target) };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function realFile(file) {
|
|
81
|
+
const resolved = path.resolve(file);
|
|
82
|
+
try {
|
|
83
|
+
return fs.realpathSync(resolved);
|
|
84
|
+
} catch {
|
|
85
|
+
return resolved;
|
|
86
|
+
}
|
|
87
|
+
}
|