@apilift/cli 1.4.35 → 1.4.37
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.
|
@@ -140,10 +140,51 @@ var init_state = __esm({
|
|
|
140
140
|
});
|
|
141
141
|
|
|
142
142
|
// src/usage/ownership.ts
|
|
143
|
-
import { mkdirSync as mkdirSync2, chmodSync as chmodSync2, existsSync, readFileSync, writeFileSync, renameSync, unlinkSync } from "node:fs";
|
|
144
|
-
import { randomUUID } from "node:crypto";
|
|
145
|
-
import { join as join2 } from "node:path";
|
|
143
|
+
import { mkdirSync as mkdirSync2, chmodSync as chmodSync2, existsSync, readFileSync, writeFileSync, renameSync, unlinkSync, statSync } from "node:fs";
|
|
144
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
145
|
+
import { join as join2, dirname, isAbsolute as isAbsolute2 } from "node:path";
|
|
146
146
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
147
|
+
function requestCollectorTarget(root2, entry, env) {
|
|
148
|
+
const owner = readOwner(root2);
|
|
149
|
+
if (!owner) return;
|
|
150
|
+
const pending = join2(root2, "usage", `.collector-target-${randomUUID()}`);
|
|
151
|
+
try {
|
|
152
|
+
const target = { owner: owner.id, entry, interpreter: process.execPath, fingerprint: sourceFingerprint(entry), env: Object.fromEntries(runtimeKeys.map((key) => [key, env[key] ?? null])) };
|
|
153
|
+
writeFileSync(pending, JSON.stringify(target), { mode: 384 });
|
|
154
|
+
renameSync(pending, targetFile(root2));
|
|
155
|
+
} finally {
|
|
156
|
+
try {
|
|
157
|
+
unlinkSync(pending);
|
|
158
|
+
} catch {
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function collectorTarget(root2, owner, loadedFingerprint) {
|
|
163
|
+
try {
|
|
164
|
+
const target = JSON.parse(readFileSync(targetFile(root2), "utf8"));
|
|
165
|
+
if (target.owner !== owner || target.fingerprint === loadedFingerprint || !isAbsolute2(target.entry) || !isAbsolute2(target.interpreter) || !target.entry.endsWith("apilift-collector.js")) return null;
|
|
166
|
+
const pkg = JSON.parse(readFileSync(join2(dirname(target.entry), "..", "package.json"), "utf8"));
|
|
167
|
+
if (pkg.name !== "@apilift/cli" || sourceFingerprint(target.entry) !== target.fingerprint) return null;
|
|
168
|
+
if (!target.env || Object.entries(target.env).some(([key, value]) => !runtimeKeys.includes(key) || value !== null && typeof value !== "string")) return null;
|
|
169
|
+
return target;
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function watchCollectorSource(entry) {
|
|
175
|
+
const loaded = sourceFingerprint(entry);
|
|
176
|
+
let last = statSync(entry);
|
|
177
|
+
return { fingerprint: loaded, changed: () => {
|
|
178
|
+
try {
|
|
179
|
+
const current = statSync(entry);
|
|
180
|
+
if (current.ino === last.ino && current.size === last.size && current.mtimeMs === last.mtimeMs) return false;
|
|
181
|
+
last = current;
|
|
182
|
+
return sourceFingerprint(entry) !== loaded;
|
|
183
|
+
} catch {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
} };
|
|
187
|
+
}
|
|
147
188
|
function readOwner(root2) {
|
|
148
189
|
try {
|
|
149
190
|
const value = JSON.parse(readFileSync(ownerFile(root2), "utf8"));
|
|
@@ -180,7 +221,7 @@ function collectorLock(root2) {
|
|
|
180
221
|
}
|
|
181
222
|
};
|
|
182
223
|
refresh();
|
|
183
|
-
return { exec: (sql) => db.exec(sql), refresh, close: () => {
|
|
224
|
+
return { id, exec: (sql) => db.exec(sql), refresh, close: () => {
|
|
184
225
|
if (closed) return;
|
|
185
226
|
closed = true;
|
|
186
227
|
try {
|
|
@@ -193,11 +234,38 @@ function collectorLock(root2) {
|
|
|
193
234
|
}
|
|
194
235
|
} };
|
|
195
236
|
}
|
|
196
|
-
|
|
237
|
+
function collectorActive(root2) {
|
|
238
|
+
const owner = readOwner(root2), now = Date.now();
|
|
239
|
+
if (owner && owner.expiresAt > now && owner.expiresAt <= now + ownerLifetimeMs) {
|
|
240
|
+
try {
|
|
241
|
+
process.kill(owner.pid, 0);
|
|
242
|
+
return true;
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const file2 = join2(root2, "usage", "collector-lock.sqlite");
|
|
247
|
+
if (!existsSync(file2)) return false;
|
|
248
|
+
try {
|
|
249
|
+
const db = new DatabaseSync2(file2, { readOnly: true });
|
|
250
|
+
try {
|
|
251
|
+
db.exec("PRAGMA busy_timeout=0");
|
|
252
|
+
db.prepare("PRAGMA schema_version").get();
|
|
253
|
+
return false;
|
|
254
|
+
} finally {
|
|
255
|
+
db.close();
|
|
256
|
+
}
|
|
257
|
+
} catch {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
var ownerLifetimeMs, targetFile, sourceFingerprint, runtimeKeys, ownerFile;
|
|
197
262
|
var init_ownership = __esm({
|
|
198
263
|
"src/usage/ownership.ts"() {
|
|
199
264
|
"use strict";
|
|
200
265
|
ownerLifetimeMs = 6e4;
|
|
266
|
+
targetFile = (root2) => join2(root2, "usage", "collector-target.json");
|
|
267
|
+
sourceFingerprint = (entry) => createHash("sha256").update(readFileSync(entry)).digest("hex");
|
|
268
|
+
runtimeKeys = ["APILIFT_URL", "CODEX_HOME", "CLAUDE_CONFIG_DIR", "APILIFT_BIN", "APILIFT_DEV_PLUGIN_ROOT"];
|
|
201
269
|
ownerFile = (root2) => join2(root2, "usage", "collector-owner.json");
|
|
202
270
|
}
|
|
203
271
|
});
|
|
@@ -28441,8 +28509,8 @@ var init_baselines = __esm({
|
|
|
28441
28509
|
});
|
|
28442
28510
|
|
|
28443
28511
|
// src/usage/transcript.ts
|
|
28444
|
-
import { createHash } from "node:crypto";
|
|
28445
|
-
import { closeSync, openSync, readSync, readdirSync, statSync } from "node:fs";
|
|
28512
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
28513
|
+
import { closeSync, openSync, readSync, readdirSync, statSync as statSync2 } from "node:fs";
|
|
28446
28514
|
import { homedir as homedir2, platform as platform2 } from "node:os";
|
|
28447
28515
|
import { basename, join as join5 } from "node:path";
|
|
28448
28516
|
import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
@@ -28528,7 +28596,7 @@ function commandIdentity(command) {
|
|
|
28528
28596
|
let argv = words.slice(index + 1);
|
|
28529
28597
|
if (argv[0] === "--session") argv = argv.slice(2);
|
|
28530
28598
|
if (!argv.length) return null;
|
|
28531
|
-
return { key: argv.slice(0, 3).join(" "), argumentHash:
|
|
28599
|
+
return { key: argv.slice(0, 3).join(" "), argumentHash: createHash2("sha256").update(JSON.stringify(argv)).digest("hex") };
|
|
28532
28600
|
}
|
|
28533
28601
|
function commandKey(command) {
|
|
28534
28602
|
return commandIdentity(command)?.key ?? null;
|
|
@@ -28591,7 +28659,7 @@ function transcriptPaths(agent2, nativeId, env, limit = 64) {
|
|
|
28591
28659
|
for (const root2 of roots) walk(root2, token, limit, found);
|
|
28592
28660
|
return [...new Set(found)].slice(0, limit).filter((path2) => {
|
|
28593
28661
|
try {
|
|
28594
|
-
return
|
|
28662
|
+
return statSync2(path2).isFile();
|
|
28595
28663
|
} catch {
|
|
28596
28664
|
return false;
|
|
28597
28665
|
}
|
|
@@ -28627,7 +28695,7 @@ var init_transcript = __esm({
|
|
|
28627
28695
|
provider = null;
|
|
28628
28696
|
thread = null;
|
|
28629
28697
|
lastAt = null;
|
|
28630
|
-
hash =
|
|
28698
|
+
hash = createHash2("sha256");
|
|
28631
28699
|
head = Buffer.alloc(0);
|
|
28632
28700
|
tail = Buffer.alloc(0);
|
|
28633
28701
|
bytes(bytes) {
|
|
@@ -28791,7 +28859,7 @@ var init_transcript = __esm({
|
|
|
28791
28859
|
const command = Array.isArray(item.command) ? item.command.map(text) : [];
|
|
28792
28860
|
const args = command.some((arg) => /^-[a-z]*c$/u.test(arg)) ? command.at(-1) : command.length ? command.join(" ") : text(item.command);
|
|
28793
28861
|
const start = num(payload.started_at_ms) ?? at, stop = num(payload.completed_at_ms) ?? at;
|
|
28794
|
-
const parents = [...this.tools.values()].filter((t) => !t.structured && t.name === "exec" &&
|
|
28862
|
+
const parents = [...this.tools.values()].filter((t) => !t.structured && t.name === "exec" && (t.calledAt ?? Infinity) <= (start ?? -Infinity) && (t.returnedAt === null || start !== null && start <= t.returnedAt));
|
|
28795
28863
|
const parent = parents.length === 1 ? parents[0] : null;
|
|
28796
28864
|
const req = parent ? this.facts.requests[parent.request] : this.current ?? this.request(start);
|
|
28797
28865
|
const tool = this.call(id, "exec_command", args, start, req, true, parent?.id ?? null);
|
|
@@ -28860,8 +28928,8 @@ var init_transcript = __esm({
|
|
|
28860
28928
|
}
|
|
28861
28929
|
this.facts.records.sort((a, b2) => (a.startedAt ?? Infinity) - (b2.startedAt ?? Infinity));
|
|
28862
28930
|
this.facts.sourceHash = this.hash.digest("hex");
|
|
28863
|
-
this.facts.headHash =
|
|
28864
|
-
this.facts.tailHash =
|
|
28931
|
+
this.facts.headHash = createHash2("sha256").update(this.head).digest("hex");
|
|
28932
|
+
this.facts.tailHash = createHash2("sha256").update(this.tail).digest("hex");
|
|
28865
28933
|
return this.facts;
|
|
28866
28934
|
}
|
|
28867
28935
|
};
|
|
@@ -163473,7 +163541,7 @@ var init_pricing = __esm({
|
|
|
163473
163541
|
});
|
|
163474
163542
|
|
|
163475
163543
|
// src/usage/measurement.ts
|
|
163476
|
-
import { createHash as
|
|
163544
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
163477
163545
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
163478
163546
|
function largestRemainder(total, weights) {
|
|
163479
163547
|
const positive = weights.map((v2) => Math.max(0, Math.floor(v2))), sum = positive.reduce((a, b2) => a + b2, 0);
|
|
@@ -163539,11 +163607,13 @@ function matchesCommand(row, tool) {
|
|
|
163539
163607
|
return false;
|
|
163540
163608
|
}
|
|
163541
163609
|
const expected = argv[0] === "--session" ? argv.slice(2) : argv;
|
|
163542
|
-
return tool.category === "apilift" && tool.argumentHash ===
|
|
163610
|
+
return tool.category === "apilift" && tool.argumentHash === createHash3("sha256").update(JSON.stringify(expected)).digest("hex");
|
|
163543
163611
|
}
|
|
163544
163612
|
function invocationMatches(row, facts) {
|
|
163545
163613
|
if (row.agent !== facts.runtime || row.session_id && !row.session_id.includes(":label:") && facts.sessionId && row.session_id !== `${facts.runtime}:${facts.sessionId}`) return [];
|
|
163546
|
-
|
|
163614
|
+
const matches = facts.requests.flatMap((request) => request.tools.filter((t) => t.accounting && t.receipts.includes(row.id) && (!facts.sessionId || !t.thread || t.thread === facts.sessionId)).map((tool) => ({ request, tool })));
|
|
163615
|
+
const originating = matches.filter(({ tool }) => tool.structured && matchesCommand(row, tool));
|
|
163616
|
+
return originating.length ? originating : matches;
|
|
163547
163617
|
}
|
|
163548
163618
|
function sessionMatches(sessionId, facts, rows) {
|
|
163549
163619
|
if (facts.sessionId && sessionId === `${facts.runtime}:${facts.sessionId}`) return true;
|
|
@@ -163750,13 +163820,33 @@ __export(collector_exports, {
|
|
|
163750
163820
|
collectorLock: () => collectorLock,
|
|
163751
163821
|
countPending: () => countPending,
|
|
163752
163822
|
deliverPending: () => deliverPending,
|
|
163823
|
+
hasPendingUsage: () => hasPendingUsage,
|
|
163753
163824
|
largestRemainder: () => largestRemainder,
|
|
163754
163825
|
reconcile: () => reconcile,
|
|
163755
163826
|
reconcileBaselines: () => reconcileBaselines,
|
|
163756
|
-
reconcilePending: () => reconcilePending
|
|
163827
|
+
reconcilePending: () => reconcilePending,
|
|
163828
|
+
upgradeMeasurementAdapter: () => upgradeMeasurementAdapter
|
|
163757
163829
|
});
|
|
163758
|
-
import { rmSync as rmSync2, statSync as
|
|
163759
|
-
import { createHash as
|
|
163830
|
+
import { rmSync as rmSync2, statSync as statSync3 } from "node:fs";
|
|
163831
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
163832
|
+
function upgradeMeasurementAdapter(ledger) {
|
|
163833
|
+
const previous = ledger.db.prepare("SELECT value FROM usage_meta WHERE key='measurement_adapter_revision'").get();
|
|
163834
|
+
if (previous && Number(previous.value) >= ADAPTER_REVISION) return;
|
|
163835
|
+
ledger.db.exec("BEGIN IMMEDIATE");
|
|
163836
|
+
try {
|
|
163837
|
+
ledger.db.prepare("UPDATE invocation SET revision=revision+1,measurement_json=NULL,status='pending',attempts=0,next_attempt_at=0 WHERE execution_id IS NOT NULL AND finished_at>=? AND measurement_json IS NOT NULL AND (session_id LIKE '%:label:%' OR json_extract(measurement_json,'$.coverage')='unavailable' OR json_extract(measurement_json,'$.timeCoverage')='unavailable' OR json_extract(measurement_json,'$.provider') IS NULL OR json_extract(measurement_json,'$.model') IS NULL)").run(new Date(Date.now() - 7 * 864e5).toISOString());
|
|
163838
|
+
ledger.db.prepare("INSERT INTO usage_meta(key,value) VALUES ('measurement_adapter_revision',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value").run(String(ADAPTER_REVISION));
|
|
163839
|
+
ledger.db.exec("COMMIT");
|
|
163840
|
+
} catch (error62) {
|
|
163841
|
+
ledger.db.exec("ROLLBACK");
|
|
163842
|
+
throw error62;
|
|
163843
|
+
}
|
|
163844
|
+
}
|
|
163845
|
+
function hasPendingUsage(ledger) {
|
|
163846
|
+
if (ledger.db.prepare("SELECT count(*) AS n FROM invocation WHERE output_path IS NOT NULL OR (execution_id IS NOT NULL AND status='pending')").get().n) return true;
|
|
163847
|
+
if (!ledger.db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='baseline_candidate'").get()) return false;
|
|
163848
|
+
return new BaselineStore(ledger).rows().some((row) => row.status === "pending" && baselineEligible(row));
|
|
163849
|
+
}
|
|
163760
163850
|
function countPending(ledger) {
|
|
163761
163851
|
for (const row of ledger.pendingOutput()) {
|
|
163762
163852
|
if (!row.output_path) continue;
|
|
@@ -163793,7 +163883,8 @@ function reconcilePending(ledger, env, now = Date.now(), scan = new TranscriptSc
|
|
|
163793
163883
|
if (!facts && (scan.deferred || now - Date.parse(row.finished_at) < THIRTY_MINUTES)) continue;
|
|
163794
163884
|
const actual = reconcile(row, facts);
|
|
163795
163885
|
const purpose = row.command_key === "run" && row.session_id ? "productive" : row.purpose;
|
|
163796
|
-
const
|
|
163886
|
+
const identity = actual.linked && facts?.sessionId ? `${row.agent}:${facts.sessionId}` : row.session_id;
|
|
163887
|
+
const payload = { id: row.id, executionId: row.execution_id, revision: row.revision, purpose, session: ledger.pseudonym(row.agent, identity), worker: ledger.pseudonym(row.agent, identity), durationMs: actual.durationMs, tokens: actual.input !== null && actual.output !== null ? { input: actual.input, output: actual.output } : null, coverage: actual.coverage, timeCoverage: actual.timeCoverage, provider: actual.provider, model: actual.model };
|
|
163797
163888
|
const checked = executionMeasurementSchema.safeParse(payload);
|
|
163798
163889
|
if (checked.success) ledger.db.prepare("UPDATE invocation SET measurement_json=?,status='pending' WHERE id=?").run(JSON.stringify(checked.data), row.id);
|
|
163799
163890
|
else ledger.db.prepare("UPDATE invocation SET status='rejected' WHERE id=?").run(row.id);
|
|
@@ -163878,6 +163969,7 @@ async function collectOnce(dataRoot2, env, fetcher = fetch) {
|
|
|
163878
163969
|
if (!installation) return;
|
|
163879
163970
|
await flushExecutionReports(createCliApi(new URL(env.APILIFT_URL || "https://apilift.dev").origin, fetcher, installation.token), state);
|
|
163880
163971
|
ledger.clearForAccountChange(installation.accountId);
|
|
163972
|
+
upgradeMeasurementAdapter(ledger);
|
|
163881
163973
|
countPending(ledger);
|
|
163882
163974
|
const scan = new TranscriptScan(ledger);
|
|
163883
163975
|
reconcilePending(ledger, env, Date.now(), scan);
|
|
@@ -163889,7 +163981,7 @@ async function collectOnce(dataRoot2, env, fetcher = fetch) {
|
|
|
163889
163981
|
state.close();
|
|
163890
163982
|
}
|
|
163891
163983
|
}
|
|
163892
|
-
var THIRTY_MINUTES, scans, TranscriptScan;
|
|
163984
|
+
var THIRTY_MINUTES, ADAPTER_REVISION, scans, TranscriptScan;
|
|
163893
163985
|
var init_collector = __esm({
|
|
163894
163986
|
"src/usage/collector.ts"() {
|
|
163895
163987
|
"use strict";
|
|
@@ -163904,6 +163996,7 @@ var init_collector = __esm({
|
|
|
163904
163996
|
init_measurement();
|
|
163905
163997
|
init_ownership();
|
|
163906
163998
|
THIRTY_MINUTES = 30 * 6e4;
|
|
163999
|
+
ADAPTER_REVISION = 2;
|
|
163907
164000
|
scans = /* @__PURE__ */ new Map();
|
|
163908
164001
|
TranscriptScan = class {
|
|
163909
164002
|
constructor(ledger) {
|
|
@@ -163916,7 +164009,7 @@ var init_collector = __esm({
|
|
|
163916
164009
|
if (this.cache.has(path2)) return this.cache.get(path2);
|
|
163917
164010
|
let stat;
|
|
163918
164011
|
try {
|
|
163919
|
-
stat =
|
|
164012
|
+
stat = statSync3(path2);
|
|
163920
164013
|
} catch {
|
|
163921
164014
|
return null;
|
|
163922
164015
|
}
|
|
@@ -163953,7 +164046,7 @@ var init_collector = __esm({
|
|
|
163953
164046
|
entry.mtime = stat.mtimeMs;
|
|
163954
164047
|
}
|
|
163955
164048
|
const facts = entry.facts;
|
|
163956
|
-
const pathHash =
|
|
164049
|
+
const pathHash = createHash4("sha256").update(path2).digest("hex");
|
|
163957
164050
|
const previous = this.ledger.db.prepare("SELECT source_hash,head_hash,bytes,gap FROM source_observation WHERE path_hash=?").get(pathHash);
|
|
163958
164051
|
if (previous) {
|
|
163959
164052
|
const replaced = facts.bytes < previous.bytes || facts.bytes === previous.bytes && facts.sourceHash !== previous.source_hash || previous.head_hash !== null && previous.bytes >= 4096 && facts.headHash !== previous.head_hash;
|
|
@@ -163973,9 +164066,51 @@ var init_collector = __esm({
|
|
|
163973
164066
|
// src/bin/collector.ts
|
|
163974
164067
|
init_state();
|
|
163975
164068
|
init_ownership();
|
|
163976
|
-
init_ledger();
|
|
163977
164069
|
import { enableCompileCache } from "node:module";
|
|
163978
164070
|
|
|
164071
|
+
// src/usage/process.ts
|
|
164072
|
+
init_ownership();
|
|
164073
|
+
init_state();
|
|
164074
|
+
|
|
164075
|
+
// src/process.ts
|
|
164076
|
+
import { spawn } from "node:child_process";
|
|
164077
|
+
function backgroundOptions(env) {
|
|
164078
|
+
return { detached: true, windowsHide: true, stdio: "ignore", env };
|
|
164079
|
+
}
|
|
164080
|
+
function startBackground(script, env, onError) {
|
|
164081
|
+
const child = spawn(process.execPath, [script], backgroundOptions(env));
|
|
164082
|
+
child.once("error", onError);
|
|
164083
|
+
child.unref();
|
|
164084
|
+
return child;
|
|
164085
|
+
}
|
|
164086
|
+
|
|
164087
|
+
// src/usage/process.ts
|
|
164088
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
164089
|
+
import { fileURLToPath } from "node:url";
|
|
164090
|
+
function startCollector(env) {
|
|
164091
|
+
if (process.env.VITEST || env.VITEST || env.APILIFT_COLLECTOR_DISABLED === "1") return;
|
|
164092
|
+
const entry = fileURLToPath(new URL("./apilift-collector.js", import.meta.url));
|
|
164093
|
+
if (!existsSync2(entry)) return;
|
|
164094
|
+
const root2 = dataRoot(env);
|
|
164095
|
+
if (collectorActive(root2)) {
|
|
164096
|
+
try {
|
|
164097
|
+
requestCollectorTarget(root2, entry, env);
|
|
164098
|
+
} catch {
|
|
164099
|
+
}
|
|
164100
|
+
return;
|
|
164101
|
+
}
|
|
164102
|
+
try {
|
|
164103
|
+
startBackground(entry, env, () => {
|
|
164104
|
+
});
|
|
164105
|
+
} catch {
|
|
164106
|
+
}
|
|
164107
|
+
}
|
|
164108
|
+
|
|
164109
|
+
// src/bin/collector.ts
|
|
164110
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
164111
|
+
init_ledger();
|
|
164112
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
164113
|
+
|
|
163979
164114
|
// src/analytics.ts
|
|
163980
164115
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
163981
164116
|
|
|
@@ -164157,10 +164292,22 @@ enableCompileCache();
|
|
|
164157
164292
|
var root = dataRoot(process.env);
|
|
164158
164293
|
var held = collectorLock(root);
|
|
164159
164294
|
if (held) {
|
|
164295
|
+
let restart = false;
|
|
164296
|
+
let target = null;
|
|
164160
164297
|
let lastActivity = Date.now();
|
|
164161
164298
|
try {
|
|
164162
|
-
const
|
|
164299
|
+
const source = watchCollectorSource(fileURLToPath2(import.meta.url));
|
|
164300
|
+
const { collectOnce: collectOnce2, hasPendingUsage: hasPendingUsage2 } = await Promise.resolve().then(() => (init_collector(), collector_exports));
|
|
164163
164301
|
for (; ; ) {
|
|
164302
|
+
target = collectorTarget(root, held.id, source.fingerprint);
|
|
164303
|
+
if (target) {
|
|
164304
|
+
restart = true;
|
|
164305
|
+
break;
|
|
164306
|
+
}
|
|
164307
|
+
if (source.changed()) {
|
|
164308
|
+
restart = true;
|
|
164309
|
+
break;
|
|
164310
|
+
}
|
|
164164
164311
|
held.refresh();
|
|
164165
164312
|
await collectOnce2(root, process.env);
|
|
164166
164313
|
const state = new LocalState(root), ledger = new UsageLedger(root);
|
|
@@ -164173,8 +164320,7 @@ if (held) {
|
|
|
164173
164320
|
}
|
|
164174
164321
|
if (refresh === "unauthorized") break;
|
|
164175
164322
|
await analytics.flush();
|
|
164176
|
-
|
|
164177
|
-
if (waiting) lastActivity = Date.now();
|
|
164323
|
+
if (hasPendingUsage2(ledger)) lastActivity = Date.now();
|
|
164178
164324
|
} finally {
|
|
164179
164325
|
ledger.close();
|
|
164180
164326
|
state.close();
|
|
@@ -164187,4 +164333,18 @@ if (held) {
|
|
|
164187
164333
|
held.exec("ROLLBACK");
|
|
164188
164334
|
held.close();
|
|
164189
164335
|
}
|
|
164336
|
+
if (restart && target) {
|
|
164337
|
+
const env = { ...process.env, APILIFT_HOME: root };
|
|
164338
|
+
for (const [key, value] of Object.entries(target.env)) {
|
|
164339
|
+
if (value === null) delete env[key];
|
|
164340
|
+
else env[key] = value;
|
|
164341
|
+
}
|
|
164342
|
+
try {
|
|
164343
|
+
const next = spawn2(target.interpreter, [target.entry], backgroundOptions(env));
|
|
164344
|
+
next.once("error", () => {
|
|
164345
|
+
});
|
|
164346
|
+
next.unref();
|
|
164347
|
+
} catch {
|
|
164348
|
+
}
|
|
164349
|
+
} else if (restart) startCollector(process.env);
|
|
164190
164350
|
}
|
|
@@ -134731,7 +134731,7 @@ var TranscriptParser = class {
|
|
|
134731
134731
|
const command = Array.isArray(item.command) ? item.command.map(text) : [];
|
|
134732
134732
|
const args = command.some((arg) => /^-[a-z]*c$/u.test(arg)) ? command.at(-1) : command.length ? command.join(" ") : text(item.command);
|
|
134733
134733
|
const start = num(payload.started_at_ms) ?? at, stop = num(payload.completed_at_ms) ?? at;
|
|
134734
|
-
const parents = [...this.tools.values()].filter((t) => !t.structured && t.name === "exec" &&
|
|
134734
|
+
const parents = [...this.tools.values()].filter((t) => !t.structured && t.name === "exec" && (t.calledAt ?? Infinity) <= (start ?? -Infinity) && (t.returnedAt === null || start !== null && start <= t.returnedAt));
|
|
134735
134735
|
const parent = parents.length === 1 ? parents[0] : null;
|
|
134736
134736
|
const req = parent ? this.facts.requests[parent.request] : this.current ?? this.request(start);
|
|
134737
134737
|
const tool = this.call(id, "exec_command", args, start, req, true, parent?.id ?? null);
|
|
@@ -134978,7 +134978,9 @@ function matchesCommand(row, tool) {
|
|
|
134978
134978
|
}
|
|
134979
134979
|
function invocationMatches(row, facts) {
|
|
134980
134980
|
if (row.agent !== facts.runtime || row.session_id && !row.session_id.includes(":label:") && facts.sessionId && row.session_id !== `${facts.runtime}:${facts.sessionId}`) return [];
|
|
134981
|
-
|
|
134981
|
+
const matches = facts.requests.flatMap((request) => request.tools.filter((t) => t.accounting && t.receipts.includes(row.id) && (!facts.sessionId || !t.thread || t.thread === facts.sessionId)).map((tool) => ({ request, tool })));
|
|
134982
|
+
const originating = matches.filter(({ tool }) => tool.structured && matchesCommand(row, tool));
|
|
134983
|
+
return originating.length ? originating : matches;
|
|
134982
134984
|
}
|
|
134983
134985
|
function sessionMatches(sessionId, facts, rows) {
|
|
134984
134986
|
if (facts.sessionId && sessionId === `${facts.runtime}:${facts.sessionId}`) return true;
|
package/dist/apilift.js
CHANGED
|
@@ -35949,9 +35949,25 @@ var init_analytics2 = __esm({
|
|
|
35949
35949
|
});
|
|
35950
35950
|
|
|
35951
35951
|
// src/usage/ownership.ts
|
|
35952
|
-
import { mkdirSync as mkdirSync11, chmodSync as chmodSync7, existsSync as existsSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync10, renameSync as renameSync4, unlinkSync as unlinkSync4 } from "node:fs";
|
|
35953
|
-
import {
|
|
35952
|
+
import { mkdirSync as mkdirSync11, chmodSync as chmodSync7, existsSync as existsSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync10, renameSync as renameSync4, unlinkSync as unlinkSync4, statSync as statSync3 } from "node:fs";
|
|
35953
|
+
import { createHash as createHash6, randomUUID as randomUUID12 } from "node:crypto";
|
|
35954
|
+
import { join as join15, dirname as dirname9, isAbsolute as isAbsolute2 } from "node:path";
|
|
35954
35955
|
import { DatabaseSync as DatabaseSync6 } from "node:sqlite";
|
|
35956
|
+
function requestCollectorTarget(root, entry2, env) {
|
|
35957
|
+
const owner = readOwner(root);
|
|
35958
|
+
if (!owner) return;
|
|
35959
|
+
const pending = join15(root, "usage", `.collector-target-${randomUUID12()}`);
|
|
35960
|
+
try {
|
|
35961
|
+
const target = { owner: owner.id, entry: entry2, interpreter: process.execPath, fingerprint: sourceFingerprint(entry2), env: Object.fromEntries(runtimeKeys.map((key) => [key, env[key] ?? null])) };
|
|
35962
|
+
writeFileSync10(pending, JSON.stringify(target), { mode: 384 });
|
|
35963
|
+
renameSync4(pending, targetFile(root));
|
|
35964
|
+
} finally {
|
|
35965
|
+
try {
|
|
35966
|
+
unlinkSync4(pending);
|
|
35967
|
+
} catch {
|
|
35968
|
+
}
|
|
35969
|
+
}
|
|
35970
|
+
}
|
|
35955
35971
|
function readOwner(root) {
|
|
35956
35972
|
try {
|
|
35957
35973
|
const value = JSON.parse(readFileSync10(ownerFile(root), "utf8"));
|
|
@@ -35984,11 +36000,14 @@ function collectorActive(root) {
|
|
|
35984
36000
|
return true;
|
|
35985
36001
|
}
|
|
35986
36002
|
}
|
|
35987
|
-
var ownerLifetimeMs, ownerFile;
|
|
36003
|
+
var ownerLifetimeMs, targetFile, sourceFingerprint, runtimeKeys, ownerFile;
|
|
35988
36004
|
var init_ownership = __esm({
|
|
35989
36005
|
"src/usage/ownership.ts"() {
|
|
35990
36006
|
"use strict";
|
|
35991
36007
|
ownerLifetimeMs = 6e4;
|
|
36008
|
+
targetFile = (root) => join15(root, "usage", "collector-target.json");
|
|
36009
|
+
sourceFingerprint = (entry2) => createHash6("sha256").update(readFileSync10(entry2)).digest("hex");
|
|
36010
|
+
runtimeKeys = ["APILIFT_URL", "CODEX_HOME", "CLAUDE_CONFIG_DIR", "APILIFT_BIN", "APILIFT_DEV_PLUGIN_ROOT"];
|
|
35992
36011
|
ownerFile = (root) => join15(root, "usage", "collector-owner.json");
|
|
35993
36012
|
}
|
|
35994
36013
|
});
|
|
@@ -35999,7 +36018,15 @@ import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
|
35999
36018
|
function startCollector(env) {
|
|
36000
36019
|
if (process.env.VITEST || env.VITEST || env.APILIFT_COLLECTOR_DISABLED === "1") return;
|
|
36001
36020
|
const entry2 = fileURLToPath3(new URL("./apilift-collector.js", import.meta.url));
|
|
36002
|
-
if (!existsSync7(entry2)
|
|
36021
|
+
if (!existsSync7(entry2)) return;
|
|
36022
|
+
const root = dataRoot(env);
|
|
36023
|
+
if (collectorActive(root)) {
|
|
36024
|
+
try {
|
|
36025
|
+
requestCollectorTarget(root, entry2, env);
|
|
36026
|
+
} catch {
|
|
36027
|
+
}
|
|
36028
|
+
return;
|
|
36029
|
+
}
|
|
36003
36030
|
try {
|
|
36004
36031
|
startBackground(entry2, env, () => {
|
|
36005
36032
|
});
|
|
@@ -38050,11 +38077,11 @@ var init_compat2 = __esm({
|
|
|
38050
38077
|
});
|
|
38051
38078
|
|
|
38052
38079
|
// src/native-host/registration.ts
|
|
38053
|
-
import { createHash as
|
|
38080
|
+
import { createHash as createHash7, randomUUID as randomUUID13 } from "node:crypto";
|
|
38054
38081
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
38055
38082
|
import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync11 } from "node:fs";
|
|
38056
38083
|
import { homedir as homedir3, platform as platform2 } from "node:os";
|
|
38057
|
-
import { dirname as
|
|
38084
|
+
import { dirname as dirname10, isAbsolute as isAbsolute3, join as join16, resolve as resolve8 } from "node:path";
|
|
38058
38085
|
import { chmod as chmod2, mkdir, readFile, rename as rename2, writeFile, copyFile, rm, stat } from "node:fs/promises";
|
|
38059
38086
|
function batchLiteral(value) {
|
|
38060
38087
|
if (/["\r\n\0]/u.test(value)) throw new Error("Native host launcher values contain unsupported control characters");
|
|
@@ -38129,7 +38156,7 @@ async function registerNativeHost(options2) {
|
|
|
38129
38156
|
const hostName = options2.hostName ?? "com.apilift.browser";
|
|
38130
38157
|
if (!hostNames.has(hostName)) throw new Error("Unknown Apilift native host name");
|
|
38131
38158
|
if (!validId.test(options2.extensionId)) throw new Error("Extension id must be 32 a\u2013p characters");
|
|
38132
|
-
if (![options2.root, options2.hostScript, options2.nodePath ?? process.execPath].every(
|
|
38159
|
+
if (![options2.root, options2.hostScript, options2.nodePath ?? process.execPath].every(isAbsolute3)) throw new Error("Native host paths must be absolute");
|
|
38133
38160
|
const siteUrl2 = options2.siteUrl ? new URL(options2.siteUrl) : null;
|
|
38134
38161
|
if (siteUrl2 && (!["http:", "https:"].includes(siteUrl2.protocol) || siteUrl2.origin !== options2.siteUrl)) throw new Error("Native host site URL must be an http(s) origin");
|
|
38135
38162
|
const root = resolve8(options2.root), hostScript = resolve8(options2.hostScript), nodePath = resolve8(options2.nodePath ?? process.execPath);
|
|
@@ -38140,7 +38167,7 @@ async function registerNativeHost(options2) {
|
|
|
38140
38167
|
const manifests = [manifest];
|
|
38141
38168
|
if (platform2() === "linux" && !env.APILIFT_NATIVE_MESSAGING_DIR) {
|
|
38142
38169
|
for (const browser of ["google-chrome-beta", "google-chrome-unstable", "chromium"]) {
|
|
38143
|
-
const profile = join16(
|
|
38170
|
+
const profile = join16(dirname10(dirname10(manifestRoot)), browser);
|
|
38144
38171
|
if (await stat(profile).then((value) => value.isDirectory()).catch(() => false)) manifests.push(join16(profile, "NativeMessagingHosts", `${hostName}.json`));
|
|
38145
38172
|
}
|
|
38146
38173
|
}
|
|
@@ -38174,7 +38201,7 @@ async function registerNativeHost(options2) {
|
|
|
38174
38201
|
const launcherText = nativeLauncher({ root, hostName, nodePath, hostScript, manifestRoot, site: siteUrl2?.origin }, platform2() === "win32");
|
|
38175
38202
|
const manifestText = JSON.stringify({ name: hostName, description: "Apilift browser bridge", path: launcher, type: "stdio", allowed_origins: [`chrome-extension://${options2.extensionId}/`] }, null, 2) + "\n";
|
|
38176
38203
|
const atomicWrite = async (path2, bytes, mode) => {
|
|
38177
|
-
const temp = join16(
|
|
38204
|
+
const temp = join16(dirname10(path2), `.${hostName}-${randomUUID13()}.tmp`);
|
|
38178
38205
|
try {
|
|
38179
38206
|
await writeFile(temp, bytes, { mode, flag: "wx" });
|
|
38180
38207
|
await chmod2(temp, mode);
|
|
@@ -38185,7 +38212,7 @@ async function registerNativeHost(options2) {
|
|
|
38185
38212
|
};
|
|
38186
38213
|
await atomicWrite(launcher, launcherText, platform2() === "win32" ? 384 : 448);
|
|
38187
38214
|
for (const path2 of manifests) {
|
|
38188
|
-
await mkdir(
|
|
38215
|
+
await mkdir(dirname10(path2), { recursive: true, mode: 448 });
|
|
38189
38216
|
await atomicWrite(path2, manifestText, 384);
|
|
38190
38217
|
}
|
|
38191
38218
|
const receipt = { ...previous, [launcher]: fingerprint(launcherText), ...Object.fromEntries(manifests.map((path2) => [path2, fingerprint(manifestText)])) };
|
|
@@ -38197,17 +38224,17 @@ var fingerprint, hostNames, validId, posixQuote, absoluteEnv;
|
|
|
38197
38224
|
var init_registration = __esm({
|
|
38198
38225
|
"src/native-host/registration.ts"() {
|
|
38199
38226
|
"use strict";
|
|
38200
|
-
fingerprint = (bytes) =>
|
|
38227
|
+
fingerprint = (bytes) => createHash7("sha256").update(bytes).digest("hex");
|
|
38201
38228
|
hostNames = /* @__PURE__ */ new Set(["com.apilift.browser", "com.apilift.browser.dev"]);
|
|
38202
38229
|
validId = /^[a-p]{32}$/;
|
|
38203
38230
|
posixQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
38204
|
-
absoluteEnv = (value) => value &&
|
|
38231
|
+
absoluteEnv = (value) => value && isAbsolute3(value) ? value : void 0;
|
|
38205
38232
|
}
|
|
38206
38233
|
});
|
|
38207
38234
|
|
|
38208
38235
|
// src/update.ts
|
|
38209
38236
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
38210
|
-
import { dirname as
|
|
38237
|
+
import { dirname as dirname11, join as join17, resolve as resolve9, sep, isAbsolute as isAbsolute4 } from "node:path";
|
|
38211
38238
|
import { mkdirSync as mkdirSync13, realpathSync as realpathSync2, writeFileSync as writeFileSync12, readFileSync as readFileSync11 } from "node:fs";
|
|
38212
38239
|
import { DatabaseSync as DatabaseSync7 } from "node:sqlite";
|
|
38213
38240
|
function globalPackage(script2, prefix) {
|
|
@@ -38230,7 +38257,7 @@ async function pnpmInstallations(runner, env) {
|
|
|
38230
38257
|
if (!Array.isArray(groups)) return [];
|
|
38231
38258
|
return groups.flatMap((group) => {
|
|
38232
38259
|
const pkg = group?.dependencies?.["@apilift/cli"];
|
|
38233
|
-
return pkg && typeof pkg.path === "string" &&
|
|
38260
|
+
return pkg && typeof pkg.path === "string" && isAbsolute4(pkg.path) && typeof pkg.version === "string" ? [{ entrypoint: join17(pkg.path, "dist", "apilift.js"), version: pkg.version }] : [];
|
|
38234
38261
|
});
|
|
38235
38262
|
} catch {
|
|
38236
38263
|
return [];
|
|
@@ -38324,10 +38351,10 @@ async function updateCli(options2, onInstalled) {
|
|
|
38324
38351
|
const candidates = (await pnpmInstallations(runner, options2.env)).filter((candidate2) => candidate2.version === latest);
|
|
38325
38352
|
if (candidates.length !== 1) throw new Error("Updated pnpm installation is ambiguous or unavailable");
|
|
38326
38353
|
entrypoint = candidates[0].entrypoint;
|
|
38327
|
-
const pkg = JSON.parse(readFileSync11(join17(
|
|
38354
|
+
const pkg = JSON.parse(readFileSync11(join17(dirname11(entrypoint), "..", "package.json"), "utf8"));
|
|
38328
38355
|
if (pkg.name !== "@apilift/cli" || pkg.version !== latest) throw new Error("Updated pnpm package could not be verified");
|
|
38329
38356
|
}
|
|
38330
|
-
await registerNativeHost({ root: options2.root, hostScript: join17(
|
|
38357
|
+
await registerNativeHost({ root: options2.root, hostScript: join17(dirname11(entrypoint), "apilift-native-host.js"), extensionId: options2.env.APILIFT_DEV_EXTENSION_ID || STORE_EXTENSION_ID, hostName: options2.env.APILIFT_NATIVE_HOST_NAME, siteUrl: options2.site, env: options2.env });
|
|
38331
38358
|
for (const agent2 of linkedAgents) {
|
|
38332
38359
|
if (!ensureAgentPlugin(agent2, options2.env).ready) throw new Error("Updated agent plugin activation is unverified");
|
|
38333
38360
|
}
|
|
@@ -38368,9 +38395,9 @@ var init_update = __esm({
|
|
|
38368
38395
|
});
|
|
38369
38396
|
|
|
38370
38397
|
// src/setup-check.ts
|
|
38371
|
-
import { createHash as
|
|
38398
|
+
import { createHash as createHash8, randomUUID as randomUUID14 } from "node:crypto";
|
|
38372
38399
|
import { existsSync as existsSync8, readFileSync as readFileSync12, realpathSync as realpathSync3, writeFileSync as writeFileSync13 } from "node:fs";
|
|
38373
|
-
import { dirname as
|
|
38400
|
+
import { dirname as dirname12, join as join18 } from "node:path";
|
|
38374
38401
|
async function setupCheckBody(client, scopeId, value) {
|
|
38375
38402
|
if (typeof value.body === "string") return Buffer.byteLength(value.body, "utf8") <= maxCheckBodyBytes ? value.body : null;
|
|
38376
38403
|
if (value.body != null) return null;
|
|
@@ -38378,7 +38405,7 @@ async function setupCheckBody(client, scopeId, value) {
|
|
|
38378
38405
|
if (!artifact || typeof artifact.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(artifact.sha256) || !Number.isInteger(artifact.bytes) || Number(artifact.bytes) < 1 || Number(artifact.bytes) > maxCheckBodyBytes || artifact.encoding !== "utf-8") return null;
|
|
38379
38406
|
try {
|
|
38380
38407
|
const bytes = await client.blob(scopeId, artifact.sha256);
|
|
38381
|
-
if (!bytes || bytes.length !== artifact.bytes ||
|
|
38408
|
+
if (!bytes || bytes.length !== artifact.bytes || createHash8("sha256").update(bytes).digest("hex") !== artifact.sha256) return null;
|
|
38382
38409
|
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
38383
38410
|
} catch {
|
|
38384
38411
|
return null;
|
|
@@ -38386,7 +38413,7 @@ async function setupCheckBody(client, scopeId, value) {
|
|
|
38386
38413
|
}
|
|
38387
38414
|
async function registerSetupHost(root, site, env) {
|
|
38388
38415
|
const script2 = realpathSync3(process.argv[1]);
|
|
38389
|
-
const hostScript = join18(
|
|
38416
|
+
const hostScript = join18(dirname12(script2), "apilift-native-host.js");
|
|
38390
38417
|
if (!existsSync8(hostScript)) throw new Error("The native host binary is missing beside the Apilift CLI");
|
|
38391
38418
|
await registerNativeHost({ root, hostScript, extensionId: env.APILIFT_DEV_EXTENSION_ID || storeExtensionId, hostName: env.APILIFT_NATIVE_HOST_NAME, siteUrl: site, env });
|
|
38392
38419
|
}
|
|
@@ -38400,13 +38427,13 @@ function setupChallenge(root, agent2, site) {
|
|
|
38400
38427
|
if (new URL(saved.url).origin === site && Number.isFinite(saved.createdAt) && Date.now() - saved.createdAt < 10 * 6e4) return saved;
|
|
38401
38428
|
} catch {
|
|
38402
38429
|
}
|
|
38403
|
-
const challenge = { url: `${site}/setup-check?challenge=${
|
|
38430
|
+
const challenge = { url: `${site}/setup-check?challenge=${randomUUID14()}`, createdAt: Date.now() };
|
|
38404
38431
|
writeFileSync13(file2, JSON.stringify(challenge), { mode: 384 });
|
|
38405
38432
|
return challenge;
|
|
38406
38433
|
}
|
|
38407
38434
|
async function setupBridge(root) {
|
|
38408
38435
|
const startedAt = Date.now();
|
|
38409
|
-
const script2 = join18(
|
|
38436
|
+
const script2 = join18(dirname12(realpathSync3(process.argv[1])), "apilift-capture-server.js");
|
|
38410
38437
|
const client = await ensureCaptureServer({ root, script: script2, version: cliVersion });
|
|
38411
38438
|
if (!client.legacyBridge) await waitForBridge(root, cliVersion, Math.max(0, 9e4 - (Date.now() - startedAt)), () => client.bridgeDemand(), client.receipt.version);
|
|
38412
38439
|
return client;
|
|
@@ -38446,7 +38473,7 @@ async function verifySetupCheck(root, challenge) {
|
|
|
38446
38473
|
}
|
|
38447
38474
|
if (candidates.length !== 1) return { ok: false, reason: candidates.length ? "More than one exact check tab matched" : "The extension did not report the exact check tab within 10 seconds" };
|
|
38448
38475
|
const execute = await client.openScope({ kind: "execute", tabSelector: candidates[0].selector, ...scopeInput });
|
|
38449
|
-
const id2 =
|
|
38476
|
+
const id2 = randomUUID14();
|
|
38450
38477
|
try {
|
|
38451
38478
|
await client.enqueueJob(execute.id, { job: { id: id2, kind: "replay", effect: "read", method: "GET", url: challenge.url, headers: {}, pageOrigin, allowedRequestOrigin: pageOrigin, timeoutMs: 2e4 } });
|
|
38452
38479
|
const deadline = Date.now() + 3e4;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apilift/cli",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.37",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"engines": {
|
|
37
37
|
"node": ">=24.16.0 <27"
|
|
38
38
|
},
|
|
39
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "e08008c496f41cb177195b7f8afca5e6f0373c82",
|
|
40
40
|
"repository": {
|
|
41
41
|
"type": "git",
|
|
42
42
|
"url": "git+https://github.com/egor-sergeev/apilift.git",
|