@apilift/cli 1.4.28 → 1.4.29
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/apilift-capture-server.js +68 -25
- package/dist/apilift-collector.js +18 -6
- package/dist/apilift-native-host.js +7 -6
- package/dist/apilift-register-native-host.js +108 -27
- package/dist/apilift-session-audit.js +18 -6
- package/dist/apilift.js +995 -738
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/.codex-plugin/plugin.json +1 -1
- package/package.json +3 -2
|
@@ -21,7 +21,7 @@ function dataRoot(env) {
|
|
|
21
21
|
const home = homedir();
|
|
22
22
|
if (platform() === "darwin") return join(home, "Library", "Application Support", "apilift");
|
|
23
23
|
if (platform() === "win32") return join(env.LOCALAPPDATA || join(home, "AppData", "Local"), "apilift");
|
|
24
|
-
return join(env.XDG_DATA_HOME
|
|
24
|
+
return join(env.XDG_DATA_HOME && isAbsolute(env.XDG_DATA_HOME) ? env.XDG_DATA_HOME : join(home, ".local", "share"), "apilift");
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// src/capture-server/server.ts
|
|
@@ -19868,7 +19868,7 @@ var extensionPageMessageSchema = external_exports.discriminatedUnion("operation"
|
|
|
19868
19868
|
|
|
19869
19869
|
// src/capture-server/store.ts
|
|
19870
19870
|
import { randomUUID, createHash } from "node:crypto";
|
|
19871
|
-
import { mkdirSync, chmodSync } from "node:fs";
|
|
19871
|
+
import { mkdirSync, chmodSync, readFileSync as readFileSync2 } from "node:fs";
|
|
19872
19872
|
import { join as join2 } from "node:path";
|
|
19873
19873
|
import { execFileSync } from "node:child_process";
|
|
19874
19874
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
@@ -19881,28 +19881,66 @@ var CaptureError = class extends Error {
|
|
|
19881
19881
|
var json3 = (value) => JSON.stringify(value);
|
|
19882
19882
|
var readJson = (value) => JSON.parse(String(value));
|
|
19883
19883
|
var scopeTtl = { inspect: 3e4, execute: 12e4, capture: 72e5 };
|
|
19884
|
-
|
|
19885
|
-
|
|
19886
|
-
|
|
19887
|
-
|
|
19888
|
-
|
|
19889
|
-
|
|
19890
|
-
|
|
19884
|
+
var pidAlive = (candidate2) => {
|
|
19885
|
+
try {
|
|
19886
|
+
process.kill(candidate2, 0);
|
|
19887
|
+
return true;
|
|
19888
|
+
} catch {
|
|
19889
|
+
return false;
|
|
19890
|
+
}
|
|
19891
|
+
};
|
|
19892
|
+
var inspectProcess = (command, args, timeout) => execFileSync(command, args, { encoding: "utf8", timeout, windowsHide: true, env: { ...process.env, LC_ALL: "C", TZ: "UTC" }, stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
19893
|
+
var linuxClockTicks;
|
|
19894
|
+
function createOwnerVerifier(probe = {}) {
|
|
19895
|
+
const cached2 = /* @__PURE__ */ new Map();
|
|
19896
|
+
const alive = probe.alive ?? pidAlive, now = probe.now ?? Date.now;
|
|
19897
|
+
return (pid, startedAt, fresh = false) => {
|
|
19898
|
+
if (!Number.isInteger(pid) || pid <= 0 || !Number.isFinite(startedAt) || !alive(pid)) {
|
|
19899
|
+
cached2.delete(pid);
|
|
19891
19900
|
return false;
|
|
19892
19901
|
}
|
|
19893
|
-
|
|
19894
|
-
|
|
19902
|
+
if ((probe.platform ?? process.platform) === "linux") return inspectOwner(pid, startedAt, probe);
|
|
19903
|
+
const prior = cached2.get(pid), time3 = now();
|
|
19904
|
+
if (!fresh && prior && prior.startedAt === startedAt && time3 >= prior.checkedAt && time3 - prior.checkedAt < 2e3) return true;
|
|
19905
|
+
cached2.delete(pid);
|
|
19906
|
+
if (!inspectOwner(pid, startedAt, probe)) return false;
|
|
19907
|
+
if (cached2.size >= 256) cached2.delete(cached2.keys().next().value);
|
|
19908
|
+
cached2.set(pid, { startedAt, checkedAt: now() });
|
|
19909
|
+
return true;
|
|
19910
|
+
};
|
|
19911
|
+
}
|
|
19912
|
+
var defaultOwnerVerifier = createOwnerVerifier();
|
|
19913
|
+
function ownerProcessAlive(pid, expectedStartedAt, probe) {
|
|
19914
|
+
return typeof probe === "object" ? inspectOwner(pid, expectedStartedAt, probe) : defaultOwnerVerifier(pid, expectedStartedAt, probe);
|
|
19915
|
+
}
|
|
19916
|
+
function inspectOwner(pid, expectedStartedAt, probe) {
|
|
19917
|
+
if (!Number.isInteger(pid) || pid <= 0 || !Number.isFinite(expectedStartedAt)) return false;
|
|
19918
|
+
const alive = probe.alive ?? pidAlive;
|
|
19919
|
+
const inspect = probe.inspect ?? inspectProcess;
|
|
19895
19920
|
const platform2 = probe.platform ?? process.platform;
|
|
19896
19921
|
const now = probe.now ?? Date.now;
|
|
19897
19922
|
const started = now();
|
|
19898
19923
|
const windows = platform2 === "win32";
|
|
19924
|
+
if (platform2 === "linux") {
|
|
19925
|
+
try {
|
|
19926
|
+
const read = probe.read ?? ((path) => readFileSync2(path, "utf8"));
|
|
19927
|
+
const stat = read(`/proc/${pid}/stat`), fields = stat.slice(stat.lastIndexOf(")") + 2).trim().split(/\s+/u);
|
|
19928
|
+
if (["Z", "X"].includes(fields[0])) return false;
|
|
19929
|
+
const ticks = Number(fields[19]), boot = Number(/^btime (\d+)$/mu.exec(read("/proc/stat"))?.[1]);
|
|
19930
|
+
const hz = probe.inspect ? Number(inspect("getconf", ["CLK_TCK"], 2e3)) : linuxClockTicks ??= Number(inspect("getconf", ["CLK_TCK"], 2e3));
|
|
19931
|
+
const observed = boot * 1e3 + ticks * 1e3 / hz;
|
|
19932
|
+
return Number.isFinite(observed) && hz > 0 && Math.abs(observed - expectedStartedAt) <= 5e3;
|
|
19933
|
+
} catch {
|
|
19934
|
+
return false;
|
|
19935
|
+
}
|
|
19936
|
+
}
|
|
19899
19937
|
for (let attempt = 0; attempt < (windows ? 2 : 1); attempt++) {
|
|
19900
19938
|
if (!alive(pid)) return false;
|
|
19901
19939
|
const remaining = windows ? 8e3 - (now() - started) : 2e3;
|
|
19902
19940
|
if (remaining <= 0) return false;
|
|
19903
19941
|
try {
|
|
19904
|
-
const raw = windows ? inspect("powershell.exe", ["-NoProfile", "-Command", `(Get-Process -Id ${pid}).StartTime.ToUniversalTime().ToString('o')`], Math.min(4e3, remaining)) : inspect("ps", ["-o", "lstart=", "-p", String(pid)], 2e3);
|
|
19905
|
-
const observed = Date.parse(raw.trim());
|
|
19942
|
+
const raw = windows ? inspect("powershell.exe", ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", `(Get-Process -Id ${pid} -ErrorAction Stop).StartTime.ToUniversalTime().ToString('o')`], Math.min(4e3, remaining)) : inspect("ps", ["-o", "lstart=", "-p", String(pid)], 2e3);
|
|
19943
|
+
const observed = Date.parse(windows ? raw.trim() : `${raw.trim()} UTC`);
|
|
19906
19944
|
return Number.isFinite(observed) && Math.abs(observed - expectedStartedAt) <= 5e3;
|
|
19907
19945
|
} catch (error62) {
|
|
19908
19946
|
if (!windows || error62.code !== "ETIMEDOUT") return false;
|
|
@@ -19972,18 +20010,22 @@ var CaptureStore = class {
|
|
|
19972
20010
|
const now = this.clock();
|
|
19973
20011
|
const rows = this.db.prepare("SELECT id,owner_pid,owner_started_at,expires_at FROM scopes WHERE retained_at IS NULL").all();
|
|
19974
20012
|
const stale = rows.filter((row) => row.expires_at <= now || !this.alive(row.owner_pid, row.owner_started_at));
|
|
19975
|
-
for (const row of stale)
|
|
19976
|
-
const capture = this.db.prepare("SELECT kind,window_json FROM scopes WHERE id=?").get(row.id);
|
|
19977
|
-
if (capture.kind === "capture" && !capture.window_json && this.db.prepare("SELECT 1 FROM capture_candidates WHERE scope_id=? LIMIT 1").get(row.id)) this.retainScope(row.id);
|
|
19978
|
-
else this.db.prepare("DELETE FROM scopes WHERE id=?").run(row.id);
|
|
19979
|
-
}
|
|
20013
|
+
for (const row of stale) this.releaseStaleScope(row.id);
|
|
19980
20014
|
this.db.prepare("DELETE FROM jobs WHERE status='delivered' AND delivered_at < ?").run(now - 6e5);
|
|
19981
20015
|
this.db.prepare("DELETE FROM bridge_demands WHERE expires_at <= ?").run(now);
|
|
19982
|
-
if (stale.length) this.emit();
|
|
19983
20016
|
}
|
|
19984
|
-
|
|
19985
|
-
this.
|
|
19986
|
-
|
|
20017
|
+
releaseStaleScope(id2) {
|
|
20018
|
+
const capture = this.db.prepare("SELECT kind,window_json FROM scopes WHERE id=?").get(id2);
|
|
20019
|
+
if (capture?.kind === "capture" && !capture.window_json && this.db.prepare("SELECT 1 FROM capture_candidates WHERE scope_id=? LIMIT 1").get(id2)) this.retainScope(id2);
|
|
20020
|
+
else this.db.prepare("DELETE FROM scopes WHERE id=?").run(id2);
|
|
20021
|
+
this.emit();
|
|
20022
|
+
}
|
|
20023
|
+
row(id2, freshOwner = false) {
|
|
20024
|
+
let row = this.db.prepare("SELECT * FROM scopes WHERE id=?").get(id2);
|
|
20025
|
+
if (row && row.retained_at === null && (Number(row.expires_at) <= this.clock() || !(freshOwner ? this.alive(Number(row.owner_pid), Number(row.owner_started_at), true) : this.alive(Number(row.owner_pid), Number(row.owner_started_at))))) {
|
|
20026
|
+
this.releaseStaleScope(id2);
|
|
20027
|
+
row = this.db.prepare("SELECT * FROM scopes WHERE id=?").get(id2);
|
|
20028
|
+
}
|
|
19987
20029
|
if (!row) throw new CaptureError("scope-expired", "Browser scope is missing or its owner exited");
|
|
19988
20030
|
return row;
|
|
19989
20031
|
}
|
|
@@ -20013,7 +20055,7 @@ var CaptureStore = class {
|
|
|
20013
20055
|
throw new CaptureError("invalid-scope", "Browser tab URL must belong to the page origin");
|
|
20014
20056
|
}
|
|
20015
20057
|
}
|
|
20016
|
-
if (!this.alive(input2.ownerPid, input2.ownerStartedAt)) throw new CaptureError("owner-exited", "Scope owner process is no longer running");
|
|
20058
|
+
if (!this.alive(input2.ownerPid, input2.ownerStartedAt, true)) throw new CaptureError("owner-exited", "Scope owner process is no longer running");
|
|
20017
20059
|
this.prune();
|
|
20018
20060
|
return this.transaction(() => {
|
|
20019
20061
|
if (input2.kind !== "inspect") {
|
|
@@ -20067,7 +20109,7 @@ var CaptureStore = class {
|
|
|
20067
20109
|
return row ? { seq: row.seq, entry: readJson(row.entry_json) } : null;
|
|
20068
20110
|
}
|
|
20069
20111
|
enqueueJob(scopeId, envelope) {
|
|
20070
|
-
const scope2 = this.row(scopeId);
|
|
20112
|
+
const scope2 = this.row(scopeId, true);
|
|
20071
20113
|
if (scope2.retained_at !== null) throw new CaptureError("scope-released", "Browser ownership was released; retained evidence cannot run jobs");
|
|
20072
20114
|
if (scope2.kind === "inspect") throw new CaptureError("inspect-only", "Inspect scopes cannot run browser jobs");
|
|
20073
20115
|
const parsed = nativeJobSchema.safeParse(envelope.job);
|
|
@@ -20201,6 +20243,7 @@ var CaptureStore = class {
|
|
|
20201
20243
|
case "jobs.poll": {
|
|
20202
20244
|
const job = this.db.prepare("SELECT id,envelope_json FROM jobs WHERE scope_id=? AND status='queued' ORDER BY created_at LIMIT 1").get(scopeId);
|
|
20203
20245
|
if (!job) return {};
|
|
20246
|
+
this.row(scopeId, true);
|
|
20204
20247
|
this.db.prepare("UPDATE jobs SET status='dispatched',dispatched_at=? WHERE id=?").run(now, job.id);
|
|
20205
20248
|
return readJson(job.envelope_json);
|
|
20206
20249
|
}
|
|
@@ -20371,7 +20414,7 @@ async function startCaptureServer(options) {
|
|
|
20371
20414
|
});
|
|
20372
20415
|
const address = server.address();
|
|
20373
20416
|
if (!address || typeof address === "string") throw new Error("Capture server did not bind a TCP port");
|
|
20374
|
-
const receipt = { pid: process.pid, port: address.port, token, version: options.version, startedAt: (
|
|
20417
|
+
const receipt = { pid: process.pid, port: address.port, token, version: options.version, startedAt: new Date(Date.now() - process.uptime() * 1e3).toISOString() };
|
|
20375
20418
|
const receiptPath = join3(root, "server.json");
|
|
20376
20419
|
const temp = join3(root, `.server-${process.pid}-${randomBytes(4).toString("hex")}.json`);
|
|
20377
20420
|
await writeFile(temp, JSON.stringify(receipt), { mode: 384 });
|
|
@@ -7182,7 +7182,7 @@ function dataRoot(env) {
|
|
|
7182
7182
|
const home = homedir();
|
|
7183
7183
|
if (platform() === "darwin") return join(home, "Library", "Application Support", "apilift");
|
|
7184
7184
|
if (platform() === "win32") return join(env.LOCALAPPDATA || join(home, "AppData", "Local"), "apilift");
|
|
7185
|
-
return join(env.XDG_DATA_HOME
|
|
7185
|
+
return join(env.XDG_DATA_HOME && isAbsolute(env.XDG_DATA_HOME) ? env.XDG_DATA_HOME : join(home, ".local", "share"), "apilift");
|
|
7186
7186
|
}
|
|
7187
7187
|
var LocalState = class {
|
|
7188
7188
|
db;
|
|
@@ -27558,27 +27558,38 @@ function readable(v2) {
|
|
|
27558
27558
|
}
|
|
27559
27559
|
function commandIdentity(command) {
|
|
27560
27560
|
let source = command;
|
|
27561
|
+
let shell = null;
|
|
27561
27562
|
try {
|
|
27562
27563
|
const args = obj(JSON.parse(command));
|
|
27563
27564
|
source = text(args.cmd) || text(args.command) || command;
|
|
27565
|
+
if (/(?:^|[\\/])(?:powershell|pwsh|cmd)(?:\.exe)?$/iu.test(text(args.shell))) shell = "windows";
|
|
27566
|
+
else if (/(?:^|[\\/])(?:bash|zsh|sh)(?:\.exe)?$/iu.test(text(args.shell))) shell = "posix";
|
|
27564
27567
|
} catch {
|
|
27565
27568
|
}
|
|
27566
27569
|
source = source.replace(/\s+2>&1\s*$/u, "");
|
|
27570
|
+
const windows = shell ? shell === "windows" : process.platform === "win32" || /^\s*&\s+/u.test(source) || /^\s*(?:["']?(?:[A-Za-z]:\\|\\\\)|node\.exe\s+|node\s+["']?[A-Za-z]:\\|apilift\.(?:cmd|exe|ps1)\b)/iu.test(source);
|
|
27571
|
+
if (windows) source = source.replace(/^\s*&\s+/u, "");
|
|
27567
27572
|
const words = [];
|
|
27568
27573
|
let word = "", quote = "", escaped = false, started = false;
|
|
27569
|
-
for (
|
|
27574
|
+
for (let offset = 0; offset < source.length; offset++) {
|
|
27575
|
+
const char = source[offset];
|
|
27570
27576
|
if (escaped) {
|
|
27571
27577
|
word += char;
|
|
27572
27578
|
escaped = false;
|
|
27573
27579
|
continue;
|
|
27574
27580
|
}
|
|
27575
|
-
if (char === "\\" && quote !== "'") {
|
|
27581
|
+
if (char === "\\" && quote !== "'" && !windows) {
|
|
27576
27582
|
escaped = true;
|
|
27577
27583
|
started = true;
|
|
27578
27584
|
continue;
|
|
27579
27585
|
}
|
|
27580
27586
|
if (quote) {
|
|
27581
|
-
if (quote === '"
|
|
27587
|
+
if (windows && quote === "'" && char === "'" && source[offset + 1] === "'") {
|
|
27588
|
+
word += "'";
|
|
27589
|
+
offset++;
|
|
27590
|
+
continue;
|
|
27591
|
+
}
|
|
27592
|
+
if (quote === '"' && (char === "$" || char === "`" || windows && (char === "%" || char === "!"))) return null;
|
|
27582
27593
|
if (char === quote) quote = "";
|
|
27583
27594
|
else word += char;
|
|
27584
27595
|
continue;
|
|
@@ -27589,6 +27600,7 @@ function commandIdentity(command) {
|
|
|
27589
27600
|
continue;
|
|
27590
27601
|
}
|
|
27591
27602
|
if (/[;&|`$()]/u.test(char)) return null;
|
|
27603
|
+
if (windows && /[%!^<>]/u.test(char)) return null;
|
|
27592
27604
|
if (/\s/u.test(char)) {
|
|
27593
27605
|
if (started) {
|
|
27594
27606
|
words.push(word);
|
|
@@ -27605,9 +27617,9 @@ function commandIdentity(command) {
|
|
|
27605
27617
|
let index = 0;
|
|
27606
27618
|
if (/(?:^|\/)env$/u.test(words[index] ?? "")) index++;
|
|
27607
27619
|
while (/^[A-Za-z_][A-Za-z0-9_]*=/u.test(words[index] ?? "")) index++;
|
|
27608
|
-
if (/(
|
|
27620
|
+
if (/(?:^|[\\/])node(?:\.exe)?$/iu.test(words[index] ?? "")) index++;
|
|
27609
27621
|
if (words[index] === "./dev" && words[index + 1] === "apilift") index++;
|
|
27610
|
-
if (!/(
|
|
27622
|
+
if (!/(?:^|[\\/])apilift(?:\.(?:js|cmd|exe|ps1))?$/iu.test(words[index] ?? "")) return null;
|
|
27611
27623
|
let argv = words.slice(index + 1);
|
|
27612
27624
|
if (argv[0] === "--session") argv = argv.slice(2);
|
|
27613
27625
|
if (!argv.length) return null;
|
|
@@ -9185,7 +9185,7 @@ function dataRoot(env) {
|
|
|
9185
9185
|
const home = homedir();
|
|
9186
9186
|
if (platform() === "darwin") return join(home, "Library", "Application Support", "apilift");
|
|
9187
9187
|
if (platform() === "win32") return join(env.LOCALAPPDATA || join(home, "AppData", "Local"), "apilift");
|
|
9188
|
-
return join(env.XDG_DATA_HOME
|
|
9188
|
+
return join(env.XDG_DATA_HOME && isAbsolute(env.XDG_DATA_HOME) ? env.XDG_DATA_HOME : join(home, ".local", "share"), "apilift");
|
|
9189
9189
|
}
|
|
9190
9190
|
var LocalState = class {
|
|
9191
9191
|
db;
|
|
@@ -31740,12 +31740,13 @@ function probeState(root, now = Date.now()) {
|
|
|
31740
31740
|
// src/native-host/registration.ts
|
|
31741
31741
|
import { homedir as homedir2, platform as platform2 } from "node:os";
|
|
31742
31742
|
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join5, resolve as resolve2 } from "node:path";
|
|
31743
|
-
|
|
31743
|
+
var absoluteEnv = (value) => value && isAbsolute2(value) ? value : void 0;
|
|
31744
|
+
function manifestDirectory(env = process.env, system = { platform: platform2(), home: homedir2() }) {
|
|
31744
31745
|
if (env.APILIFT_NATIVE_MESSAGING_DIR) return resolve2(env.APILIFT_NATIVE_MESSAGING_DIR);
|
|
31745
|
-
const home =
|
|
31746
|
-
if (
|
|
31747
|
-
if (
|
|
31748
|
-
return join5(home, ".config", "google-chrome", "NativeMessagingHosts");
|
|
31746
|
+
const home = system.home;
|
|
31747
|
+
if (system.platform === "darwin") return join5(home, "Library", "Application Support", "Google", "Chrome", "NativeMessagingHosts");
|
|
31748
|
+
if (system.platform === "win32") throw new Error("Windows native-host registration needs a data-root manifest and HKCU registry entry");
|
|
31749
|
+
return join5(absoluteEnv(env.CHROME_CONFIG_HOME) || absoluteEnv(env.XDG_CONFIG_HOME) || join5(home, ".config"), "google-chrome", "NativeMessagingHosts");
|
|
31749
31750
|
}
|
|
31750
31751
|
|
|
31751
31752
|
// src/bin/native-host.ts
|
|
@@ -7,20 +7,84 @@ import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
|
7
7
|
import { basename, dirname as dirname2, isAbsolute as isAbsolute2, join as join2, resolve as resolve2 } from "node:path";
|
|
8
8
|
|
|
9
9
|
// src/native-host/registration.ts
|
|
10
|
-
import { createHash } from "node:crypto";
|
|
10
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
11
|
+
import { execFileSync } from "node:child_process";
|
|
12
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
11
13
|
import { homedir, platform } from "node:os";
|
|
12
14
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
13
|
-
import { chmod, mkdir, readFile, rename, writeFile, copyFile } from "node:fs/promises";
|
|
15
|
+
import { chmod, mkdir, readFile, rename, writeFile, copyFile, rm, stat } from "node:fs/promises";
|
|
14
16
|
var fingerprint = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
15
17
|
var hostNames = /* @__PURE__ */ new Set(["com.apilift.browser", "com.apilift.browser.dev"]);
|
|
16
18
|
var validId = /^[a-p]{32}$/;
|
|
17
19
|
var posixQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
18
|
-
|
|
20
|
+
var absoluteEnv = (value) => value && isAbsolute(value) ? value : void 0;
|
|
21
|
+
function batchLiteral(value) {
|
|
22
|
+
if (/["\r\n\0]/u.test(value)) throw new Error("Native host launcher values contain unsupported control characters");
|
|
23
|
+
return value.replaceAll("%", "%%");
|
|
24
|
+
}
|
|
25
|
+
function nativeLauncher(values, windows) {
|
|
26
|
+
const environment = { APILIFT_HOME: values.root, APILIFT_NATIVE_HOST_NAME: values.hostName, APILIFT_NATIVE_MESSAGING_DIR: values.manifestRoot, ...values.site ? { APILIFT_URL: values.site } : {} };
|
|
27
|
+
if (windows) return `@echo off\r
|
|
28
|
+
chcp 65001 >nul 2>&1\r
|
|
29
|
+
setlocal DisableDelayedExpansion\r
|
|
30
|
+
${Object.entries(environment).map(([key, value]) => `set "${key}=${batchLiteral(value)}"\r
|
|
31
|
+
`).join("")}"${batchLiteral(values.nodePath)}" "${batchLiteral(values.hostScript)}" %*\r
|
|
32
|
+
exit /b %errorlevel%\r
|
|
33
|
+
`;
|
|
34
|
+
return `#!/bin/sh
|
|
35
|
+
${Object.entries(environment).map(([key, value]) => `${key}=${posixQuote(value)}`).join(" ")} exec ${posixQuote(values.nodePath)} ${posixQuote(values.hostScript)} "$@"
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
38
|
+
function readWindowsHostValues(hostName, run = execFileSync) {
|
|
39
|
+
if (!/^[a-z0-9_]+(?:\.[a-z0-9_]+)*$/u.test(hostName)) throw new Error("Invalid native host name");
|
|
40
|
+
const script = `$ErrorActionPreference='Stop'; $results=@(); foreach($view in @('32','64')) {
|
|
41
|
+
$base=[Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::CurrentUser,[Microsoft.Win32.RegistryView]('Registry'+$view));
|
|
42
|
+
try {
|
|
43
|
+
$key=$base.OpenSubKey('Software\\Google\\Chrome\\NativeMessagingHosts\\${hostName}');
|
|
44
|
+
$kind=$null; $data=$null;
|
|
45
|
+
if($null -ne $key) { try {
|
|
46
|
+
$value=$key.GetValue('',$null,[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames);
|
|
47
|
+
if($null -ne $value) { $kind=$key.GetValueKind('').ToString(); $data=[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes((ConvertTo-Json -InputObject $value -Compress))); }
|
|
48
|
+
} finally { $key.Dispose(); } }
|
|
49
|
+
$results+=@{view=$view;present=($null -ne $key);kind=$kind;data=$data};
|
|
50
|
+
} finally { $base.Dispose(); }
|
|
51
|
+
}; ConvertTo-Json -InputObject @($results) -Compress`;
|
|
52
|
+
try {
|
|
53
|
+
const output = run("powershell.exe", ["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")], { encoding: "utf8", timeout: 1e4, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
54
|
+
const values = JSON.parse(String(output));
|
|
55
|
+
if (!Array.isArray(values) || values.length !== 2) throw new Error("Invalid registry snapshot");
|
|
56
|
+
return ["32", "64"].map((view) => {
|
|
57
|
+
const items = values.filter((item2) => item2?.view === view);
|
|
58
|
+
const item = items[0];
|
|
59
|
+
if (items.length !== 1 || typeof item.present !== "boolean" || !(item.kind === null || typeof item.kind === "string") || !(item.data === null || typeof item.data === "string")) throw new Error("Invalid registry value");
|
|
60
|
+
return { view, present: item.present, kind: item.kind, value: item.data === null ? null : JSON.parse(Buffer.from(item.data, "base64").toString("utf8")) };
|
|
61
|
+
});
|
|
62
|
+
} catch {
|
|
63
|
+
throw new Error("Could not inspect the current user's Chrome native host registration");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function registerWindowsHost(hostName, manifest, owned, run = execFileSync, write = true, backup) {
|
|
67
|
+
const key = `HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${hostName}`;
|
|
68
|
+
const options2 = { encoding: "utf8", timeout: 1e4, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] };
|
|
69
|
+
const present = /* @__PURE__ */ new Set();
|
|
70
|
+
for (const value of readWindowsHostValues(hostName, run)) {
|
|
71
|
+
if (!value.present) continue;
|
|
72
|
+
const { view } = value;
|
|
73
|
+
present.add(view);
|
|
74
|
+
const current = value.kind === "String" && typeof value.value === "string" ? value.value : null;
|
|
75
|
+
if (!owned || current?.toLowerCase() !== manifest.toLowerCase()) {
|
|
76
|
+
if (!backup) throw new Error(`Native host registration is not owned by Apilift: ${key}`);
|
|
77
|
+
backup(view, JSON.stringify(value));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (write) for (const view of /* @__PURE__ */ new Set(["32", ...present])) run("reg.exe", ["add", key, "/ve", "/t", "REG_SZ", "/d", manifest, "/f", `/reg:${view}`], options2);
|
|
81
|
+
}
|
|
82
|
+
function manifestDirectory(env = process.env, system = { platform: platform(), home: homedir() }) {
|
|
19
83
|
if (env.APILIFT_NATIVE_MESSAGING_DIR) return resolve(env.APILIFT_NATIVE_MESSAGING_DIR);
|
|
20
|
-
const home =
|
|
21
|
-
if (platform
|
|
22
|
-
if (platform
|
|
23
|
-
return join(home, ".config", "google-chrome", "NativeMessagingHosts");
|
|
84
|
+
const home = system.home;
|
|
85
|
+
if (system.platform === "darwin") return join(home, "Library", "Application Support", "Google", "Chrome", "NativeMessagingHosts");
|
|
86
|
+
if (system.platform === "win32") throw new Error("Windows native-host registration needs a data-root manifest and HKCU registry entry");
|
|
87
|
+
return join(absoluteEnv(env.CHROME_CONFIG_HOME) || absoluteEnv(env.XDG_CONFIG_HOME) || join(home, ".config"), "google-chrome", "NativeMessagingHosts");
|
|
24
88
|
}
|
|
25
89
|
async function registerNativeHost(options2) {
|
|
26
90
|
const env = options2.env ?? process.env;
|
|
@@ -35,7 +99,24 @@ async function registerNativeHost(options2) {
|
|
|
35
99
|
const manifestRoot = env.APILIFT_NATIVE_MESSAGING_DIR ? resolve(env.APILIFT_NATIVE_MESSAGING_DIR) : platform() === "win32" ? nativeRoot : manifestDirectory(env);
|
|
36
100
|
const launcher = join(nativeRoot, platform() === "win32" ? "launcher.cmd" : "launcher.sh");
|
|
37
101
|
const manifest = join(manifestRoot, `${hostName}.json`);
|
|
102
|
+
const manifests = [manifest];
|
|
103
|
+
if (platform() === "linux" && !env.APILIFT_NATIVE_MESSAGING_DIR) {
|
|
104
|
+
for (const browser of ["google-chrome-beta", "google-chrome-unstable", "chromium"]) {
|
|
105
|
+
const profile = join(dirname(dirname(manifestRoot)), browser);
|
|
106
|
+
if (await stat(profile).then((value) => value.isDirectory()).catch(() => false)) manifests.push(join(profile, "NativeMessagingHosts", `${hostName}.json`));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
38
109
|
const receiptPath = join(nativeRoot, "registration.json");
|
|
110
|
+
const registryBackup = options2.allowReplaceUnowned ? (view, value) => {
|
|
111
|
+
const directory = join(nativeRoot, "backup");
|
|
112
|
+
mkdirSync(directory, { recursive: true, mode: 448 });
|
|
113
|
+
const path = join(directory, `${hostName}-registry-${view}-${fingerprint(value)}.json`);
|
|
114
|
+
try {
|
|
115
|
+
writeFileSync(path, JSON.stringify({ hostName, view, query: value }), { flag: "wx", mode: 384 });
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error.code !== "EEXIST") throw error;
|
|
118
|
+
}
|
|
119
|
+
} : void 0;
|
|
39
120
|
const previous = await readFile(receiptPath, "utf8").then((value) => JSON.parse(value)).catch(() => ({}));
|
|
40
121
|
const guard = async (path) => {
|
|
41
122
|
const bytes = await readFile(path).catch(() => null);
|
|
@@ -45,33 +126,33 @@ async function registerNativeHost(options2) {
|
|
|
45
126
|
await copyFile(path, join(nativeRoot, "backup", `${fingerprint(bytes)}-${Date.now()}`));
|
|
46
127
|
};
|
|
47
128
|
await guard(launcher);
|
|
48
|
-
await guard(
|
|
129
|
+
for (const path of manifests) await guard(path);
|
|
130
|
+
if (platform() === "win32" && !env.APILIFT_NATIVE_MESSAGING_DIR) {
|
|
131
|
+
const bytes = await readFile(manifest).catch(() => null);
|
|
132
|
+
registerWindowsHost(hostName, manifest, Boolean(bytes && previous[manifest] === fingerprint(bytes)), execFileSync, false, registryBackup);
|
|
133
|
+
}
|
|
49
134
|
await mkdir(nativeRoot, { recursive: true, mode: 448 });
|
|
50
135
|
await mkdir(manifestRoot, { recursive: true, mode: 448 });
|
|
51
|
-
const launcherText = platform() === "win32"
|
|
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
|
-
`;
|
|
136
|
+
const launcherText = nativeLauncher({ root, hostName, nodePath, hostScript, manifestRoot, site: siteUrl?.origin }, platform() === "win32");
|
|
60
137
|
const manifestText = JSON.stringify({ name: hostName, description: "Apilift browser bridge", path: launcher, type: "stdio", allowed_origins: [`chrome-extension://${options2.extensionId}/`] }, null, 2) + "\n";
|
|
61
138
|
const atomicWrite = async (path, bytes, mode) => {
|
|
62
|
-
const temp = join(dirname(path), `.${hostName}-${
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
139
|
+
const temp = join(dirname(path), `.${hostName}-${randomUUID()}.tmp`);
|
|
140
|
+
try {
|
|
141
|
+
await writeFile(temp, bytes, { mode, flag: "wx" });
|
|
142
|
+
await chmod(temp, mode);
|
|
143
|
+
await rename(temp, path);
|
|
144
|
+
} finally {
|
|
145
|
+
await rm(temp, { force: true });
|
|
146
|
+
}
|
|
66
147
|
};
|
|
67
148
|
await atomicWrite(launcher, launcherText, platform() === "win32" ? 384 : 448);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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" });
|
|
149
|
+
for (const path of manifests) {
|
|
150
|
+
await mkdir(dirname(path), { recursive: true, mode: 448 });
|
|
151
|
+
await atomicWrite(path, manifestText, 384);
|
|
74
152
|
}
|
|
153
|
+
const receipt = { ...previous, [launcher]: fingerprint(launcherText), ...Object.fromEntries(manifests.map((path) => [path, fingerprint(manifestText)])) };
|
|
154
|
+
await atomicWrite(receiptPath, JSON.stringify(receipt, null, 2) + "\n", 384);
|
|
155
|
+
if (platform() === "win32" && !env.APILIFT_NATIVE_MESSAGING_DIR) registerWindowsHost(hostName, manifest, true, execFileSync, true, registryBackup);
|
|
75
156
|
return { launcher, manifest, hostName };
|
|
76
157
|
}
|
|
77
158
|
|
|
@@ -31861,7 +31861,7 @@ function dataRoot(env) {
|
|
|
31861
31861
|
const home = homedir();
|
|
31862
31862
|
if (platform() === "darwin") return join(home, "Library", "Application Support", "apilift");
|
|
31863
31863
|
if (platform() === "win32") return join(env.LOCALAPPDATA || join(home, "AppData", "Local"), "apilift");
|
|
31864
|
-
return join(env.XDG_DATA_HOME
|
|
31864
|
+
return join(env.XDG_DATA_HOME && isAbsolute(env.XDG_DATA_HOME) ? env.XDG_DATA_HOME : join(home, ".local", "share"), "apilift");
|
|
31865
31865
|
}
|
|
31866
31866
|
|
|
31867
31867
|
// src/usage/measurement.ts
|
|
@@ -134465,27 +134465,38 @@ function readable(v2) {
|
|
|
134465
134465
|
}
|
|
134466
134466
|
function commandIdentity(command) {
|
|
134467
134467
|
let source = command;
|
|
134468
|
+
let shell = null;
|
|
134468
134469
|
try {
|
|
134469
134470
|
const args = obj(JSON.parse(command));
|
|
134470
134471
|
source = text(args.cmd) || text(args.command) || command;
|
|
134472
|
+
if (/(?:^|[\\/])(?:powershell|pwsh|cmd)(?:\.exe)?$/iu.test(text(args.shell))) shell = "windows";
|
|
134473
|
+
else if (/(?:^|[\\/])(?:bash|zsh|sh)(?:\.exe)?$/iu.test(text(args.shell))) shell = "posix";
|
|
134471
134474
|
} catch {
|
|
134472
134475
|
}
|
|
134473
134476
|
source = source.replace(/\s+2>&1\s*$/u, "");
|
|
134477
|
+
const windows = shell ? shell === "windows" : process.platform === "win32" || /^\s*&\s+/u.test(source) || /^\s*(?:["']?(?:[A-Za-z]:\\|\\\\)|node\.exe\s+|node\s+["']?[A-Za-z]:\\|apilift\.(?:cmd|exe|ps1)\b)/iu.test(source);
|
|
134478
|
+
if (windows) source = source.replace(/^\s*&\s+/u, "");
|
|
134474
134479
|
const words = [];
|
|
134475
134480
|
let word = "", quote = "", escaped = false, started = false;
|
|
134476
|
-
for (
|
|
134481
|
+
for (let offset = 0; offset < source.length; offset++) {
|
|
134482
|
+
const char = source[offset];
|
|
134477
134483
|
if (escaped) {
|
|
134478
134484
|
word += char;
|
|
134479
134485
|
escaped = false;
|
|
134480
134486
|
continue;
|
|
134481
134487
|
}
|
|
134482
|
-
if (char === "\\" && quote !== "'") {
|
|
134488
|
+
if (char === "\\" && quote !== "'" && !windows) {
|
|
134483
134489
|
escaped = true;
|
|
134484
134490
|
started = true;
|
|
134485
134491
|
continue;
|
|
134486
134492
|
}
|
|
134487
134493
|
if (quote) {
|
|
134488
|
-
if (quote === '"
|
|
134494
|
+
if (windows && quote === "'" && char === "'" && source[offset + 1] === "'") {
|
|
134495
|
+
word += "'";
|
|
134496
|
+
offset++;
|
|
134497
|
+
continue;
|
|
134498
|
+
}
|
|
134499
|
+
if (quote === '"' && (char === "$" || char === "`" || windows && (char === "%" || char === "!"))) return null;
|
|
134489
134500
|
if (char === quote) quote = "";
|
|
134490
134501
|
else word += char;
|
|
134491
134502
|
continue;
|
|
@@ -134496,6 +134507,7 @@ function commandIdentity(command) {
|
|
|
134496
134507
|
continue;
|
|
134497
134508
|
}
|
|
134498
134509
|
if (/[;&|`$()]/u.test(char)) return null;
|
|
134510
|
+
if (windows && /[%!^<>]/u.test(char)) return null;
|
|
134499
134511
|
if (/\s/u.test(char)) {
|
|
134500
134512
|
if (started) {
|
|
134501
134513
|
words.push(word);
|
|
@@ -134512,9 +134524,9 @@ function commandIdentity(command) {
|
|
|
134512
134524
|
let index = 0;
|
|
134513
134525
|
if (/(?:^|\/)env$/u.test(words[index] ?? "")) index++;
|
|
134514
134526
|
while (/^[A-Za-z_][A-Za-z0-9_]*=/u.test(words[index] ?? "")) index++;
|
|
134515
|
-
if (/(
|
|
134527
|
+
if (/(?:^|[\\/])node(?:\.exe)?$/iu.test(words[index] ?? "")) index++;
|
|
134516
134528
|
if (words[index] === "./dev" && words[index + 1] === "apilift") index++;
|
|
134517
|
-
if (!/(
|
|
134529
|
+
if (!/(?:^|[\\/])apilift(?:\.(?:js|cmd|exe|ps1))?$/iu.test(words[index] ?? "")) return null;
|
|
134518
134530
|
let argv = words.slice(index + 1);
|
|
134519
134531
|
if (argv[0] === "--session") argv = argv.slice(2);
|
|
134520
134532
|
if (!argv.length) return null;
|