@apilift/cli 1.0.2
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 +13 -0
- package/dist/apilift-capture-server.js +20330 -0
- package/dist/apilift-collector.js +130701 -0
- package/dist/apilift-native-host.js +31522 -0
- package/dist/apilift-register-native-host.js +123 -0
- package/dist/apilift-session-audit.js +161955 -0
- package/dist/apilift.js +35605 -0
- package/dist/plugin/.claude-plugin/plugin.json +8 -0
- package/dist/plugin/.codex-plugin/plugin.json +24 -0
- package/dist/plugin/skills/setup/SKILL.md +10 -0
- package/package.json +51 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module"; const require = createRequire(import.meta.url);
|
|
3
|
+
|
|
4
|
+
// src/bin/register-native-host.ts
|
|
5
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
6
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
7
|
+
import { basename, dirname as dirname2, isAbsolute as isAbsolute2, join as join2, resolve as resolve2 } from "node:path";
|
|
8
|
+
|
|
9
|
+
// src/native-host/registration.ts
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
import { homedir, platform } from "node:os";
|
|
12
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
13
|
+
import { chmod, mkdir, readFile, rename, writeFile, copyFile } from "node:fs/promises";
|
|
14
|
+
var fingerprint = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
15
|
+
var hostNames = /* @__PURE__ */ new Set(["com.apilift.browser", "com.apilift.browser.dev"]);
|
|
16
|
+
var validId = /^[a-p]{32}$/;
|
|
17
|
+
var posixQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
18
|
+
function manifestDirectory(env = process.env) {
|
|
19
|
+
if (env.APILIFT_NATIVE_MESSAGING_DIR) return resolve(env.APILIFT_NATIVE_MESSAGING_DIR);
|
|
20
|
+
const home = homedir();
|
|
21
|
+
if (platform() === "darwin") return join(home, "Library", "Application Support", "Google", "Chrome", "NativeMessagingHosts");
|
|
22
|
+
if (platform() === "win32") throw new Error("Windows native-host registration needs a data-root manifest and HKCU registry entry");
|
|
23
|
+
return join(home, ".config", "google-chrome", "NativeMessagingHosts");
|
|
24
|
+
}
|
|
25
|
+
async function registerNativeHost(options2) {
|
|
26
|
+
const env = options2.env ?? process.env;
|
|
27
|
+
const hostName = options2.hostName ?? "com.apilift.browser";
|
|
28
|
+
if (!hostNames.has(hostName)) throw new Error("Unknown Apilift native host name");
|
|
29
|
+
if (!validId.test(options2.extensionId)) throw new Error("Extension id must be 32 a\u2013p characters");
|
|
30
|
+
if (![options2.root, options2.hostScript, options2.nodePath ?? process.execPath].every(isAbsolute)) throw new Error("Native host paths must be absolute");
|
|
31
|
+
const siteUrl = options2.siteUrl ? new URL(options2.siteUrl) : null;
|
|
32
|
+
if (siteUrl && (!["http:", "https:"].includes(siteUrl.protocol) || siteUrl.origin !== options2.siteUrl)) throw new Error("Native host site URL must be an http(s) origin");
|
|
33
|
+
const root = resolve(options2.root), hostScript = resolve(options2.hostScript), nodePath = resolve(options2.nodePath ?? process.execPath);
|
|
34
|
+
const nativeRoot = join(root, "native-host");
|
|
35
|
+
const manifestRoot = env.APILIFT_NATIVE_MESSAGING_DIR ? resolve(env.APILIFT_NATIVE_MESSAGING_DIR) : platform() === "win32" ? nativeRoot : manifestDirectory(env);
|
|
36
|
+
const launcher = join(nativeRoot, platform() === "win32" ? "launcher.cmd" : "launcher.sh");
|
|
37
|
+
const manifest = join(manifestRoot, `${hostName}.json`);
|
|
38
|
+
const receiptPath = join(nativeRoot, "registration.json");
|
|
39
|
+
const previous = await readFile(receiptPath, "utf8").then((value) => JSON.parse(value)).catch(() => ({}));
|
|
40
|
+
const guard = async (path) => {
|
|
41
|
+
const bytes = await readFile(path).catch(() => null);
|
|
42
|
+
if (!bytes || fingerprint(bytes) === previous[path]) return;
|
|
43
|
+
if (!options2.allowReplaceUnowned) throw new Error(`Native host registration is not owned by Apilift: ${path}`);
|
|
44
|
+
await mkdir(join(nativeRoot, "backup"), { recursive: true, mode: 448 });
|
|
45
|
+
await copyFile(path, join(nativeRoot, "backup", `${fingerprint(bytes)}-${Date.now()}`));
|
|
46
|
+
};
|
|
47
|
+
await guard(launcher);
|
|
48
|
+
await guard(manifest);
|
|
49
|
+
await mkdir(nativeRoot, { recursive: true, mode: 448 });
|
|
50
|
+
await mkdir(manifestRoot, { recursive: true, mode: 448 });
|
|
51
|
+
const launcherText = platform() === "win32" ? `@echo off\r
|
|
52
|
+
set "APILIFT_HOME=${root}"\r
|
|
53
|
+
set "APILIFT_NATIVE_HOST_NAME=${hostName}"\r
|
|
54
|
+
${siteUrl ? `set "APILIFT_URL=${siteUrl.origin}"\r
|
|
55
|
+
` : ""}${env.APILIFT_NATIVE_MESSAGING_DIR ? `set "APILIFT_NATIVE_MESSAGING_DIR=${manifestRoot}"\r
|
|
56
|
+
` : ""}"${nodePath}" "${hostScript}" %*\r
|
|
57
|
+
` : `#!/bin/sh
|
|
58
|
+
APILIFT_HOME=${posixQuote(root)} APILIFT_NATIVE_HOST_NAME=${posixQuote(hostName)} ${siteUrl ? `APILIFT_URL=${posixQuote(siteUrl.origin)} ` : ""}${env.APILIFT_NATIVE_MESSAGING_DIR ? `APILIFT_NATIVE_MESSAGING_DIR=${posixQuote(manifestRoot)} ` : ""}exec ${posixQuote(nodePath)} ${posixQuote(hostScript)} "$@"
|
|
59
|
+
`;
|
|
60
|
+
const manifestText = JSON.stringify({ name: hostName, description: "Apilift browser bridge", path: launcher, type: "stdio", allowed_origins: [`chrome-extension://${options2.extensionId}/`] }, null, 2) + "\n";
|
|
61
|
+
const atomicWrite = async (path, bytes, mode) => {
|
|
62
|
+
const temp = join(dirname(path), `.${hostName}-${process.pid}-${Date.now()}.tmp`);
|
|
63
|
+
await writeFile(temp, bytes, { mode });
|
|
64
|
+
await chmod(temp, mode);
|
|
65
|
+
await rename(temp, path);
|
|
66
|
+
};
|
|
67
|
+
await atomicWrite(launcher, launcherText, platform() === "win32" ? 384 : 448);
|
|
68
|
+
await atomicWrite(manifest, manifestText, 384);
|
|
69
|
+
const receipt = { ...previous, [launcher]: fingerprint(launcherText), [manifest]: fingerprint(manifestText) };
|
|
70
|
+
await atomicWrite(receiptPath, JSON.stringify(receipt, null, 2) + "\n", 384);
|
|
71
|
+
if (platform() === "win32" && !env.APILIFT_NATIVE_MESSAGING_DIR) {
|
|
72
|
+
const { execFileSync } = await import("node:child_process");
|
|
73
|
+
execFileSync("reg.exe", ["add", `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${hostName}`, "/ve", "/t", "REG_SZ", "/d", manifest, "/f"], { stdio: "ignore" });
|
|
74
|
+
}
|
|
75
|
+
return { launcher, manifest, hostName };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/bin/register-native-host.ts
|
|
79
|
+
function options(argv) {
|
|
80
|
+
const values = /* @__PURE__ */ new Map();
|
|
81
|
+
let replaceStaleOwned = false;
|
|
82
|
+
for (let index = 0; index < argv.length; index++) {
|
|
83
|
+
const flag = argv[index];
|
|
84
|
+
if (flag === "--replace-stale-owned") {
|
|
85
|
+
replaceStaleOwned = true;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (!["--root", "--host-script", "--extension-id", "--host-name", "--site-url"].includes(flag)) throw new Error(`Unknown registration option: ${flag}`);
|
|
89
|
+
const value = argv[++index];
|
|
90
|
+
if (!value || value.startsWith("--") || values.has(flag)) throw new Error(`Registration option ${flag} needs one value`);
|
|
91
|
+
values.set(flag, value);
|
|
92
|
+
}
|
|
93
|
+
const root = values.get("--root"), hostScript = values.get("--host-script"), extensionId = values.get("--extension-id");
|
|
94
|
+
if (!root || !hostScript || !extensionId || !isAbsolute2(root) || !isAbsolute2(hostScript)) throw new Error("Use absolute --root and --host-script paths and pass --extension-id");
|
|
95
|
+
return { root: resolve2(root), hostScript: resolve2(hostScript), extensionId, hostName: values.get("--host-name") ?? "com.apilift.browser.dev", siteUrl: values.get("--site-url"), replaceStaleOwned };
|
|
96
|
+
}
|
|
97
|
+
function staleOwnedByThisWorktree(input) {
|
|
98
|
+
if (input.hostName !== "com.apilift.browser.dev" || process.env.APILIFT_NATIVE_MESSAGING_DIR) return false;
|
|
99
|
+
const state = dirname2(input.root), runtimePath = join2(state, "runtime.json");
|
|
100
|
+
try {
|
|
101
|
+
const runtime = JSON.parse(readFileSync(runtimePath, "utf8"));
|
|
102
|
+
const currentRoot = resolve2(process.cwd());
|
|
103
|
+
const instance = createHash2("sha256").update(currentRoot).digest("hex").slice(0, 10);
|
|
104
|
+
if (runtime.schema !== "apilift.dev/v1" || resolve2(runtime.root ?? "") !== currentRoot || resolve2(runtime.state ?? "") !== state || runtime.instance !== instance || input.root !== join2(state, "cli")) return false;
|
|
105
|
+
const path = join2(manifestDirectory(process.env), `${input.hostName}.json`);
|
|
106
|
+
if (!existsSync(path)) return false;
|
|
107
|
+
const manifest = JSON.parse(readFileSync(path, "utf8"));
|
|
108
|
+
const oldPath = manifest.path ?? "";
|
|
109
|
+
return manifest.name === input.hostName && basename(oldPath) === "apilift-native-host" && realpathSync(dirname2(oldPath)) === realpathSync(state) && manifest.allowed_origins?.length === 1 && manifest.allowed_origins[0] === `chrome-extension://${input.extensionId}/`;
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const input = options(process.argv.slice(2));
|
|
116
|
+
if (input.replaceStaleOwned && !staleOwnedByThisWorktree(input)) throw new Error("The existing registration is not the former native host for this worktree");
|
|
117
|
+
const installed = await registerNativeHost({ root: input.root, hostScript: input.hostScript, extensionId: input.extensionId, hostName: input.hostName, siteUrl: input.siteUrl, allowReplaceUnowned: input.replaceStaleOwned });
|
|
118
|
+
process.stdout.write(JSON.stringify({ hostName: installed.hostName, extensionId: input.extensionId, launcher: installed.launcher, manifest: installed.manifest, receipt: join2(input.root, "native-host", "registration.json") }) + "\n");
|
|
119
|
+
} catch (error) {
|
|
120
|
+
process.stderr.write(`${error instanceof Error ? error.message : "Native host registration failed"}
|
|
121
|
+
`);
|
|
122
|
+
process.exitCode = 1;
|
|
123
|
+
}
|